Completed
Pull Request — master (#18)
by Felipe A.
01:20
created

PluginAction.__call__()   B

Complexity

Conditions 6

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 8
c 0
b 0
f 0
cc 6
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
import re
5
import sys
6
import os
7
import os.path
8
import argparse
9
import warnings
10
11
import flask
12
13
from . import app, compat
14
from . import __meta__ as meta
15
from .compat import PY_LEGACY, getdebug, get_terminal_size
16
from .transform.glob import translate
17
18
19
class HelpFormatter(argparse.RawTextHelpFormatter):
20
    def __init__(self, prog, indent_increment=2, max_help_position=24,
21
                 width=None):
22
        if width is None:
23
            try:
24
                width = get_terminal_size().columns - 2
25
            except ValueError:  # https://bugs.python.org/issue24966
26
                pass
27
        super(HelpFormatter, self).__init__(
28
            prog, indent_increment, max_help_position, width)
29
30
31
class PluginAction(argparse.Action):
32
    def __call__(self, parser, namespace, value, option_string=None):
33
        warned = '%s_warning' % self.dest
34
        if ',' in value and not getattr(namespace, warned, False):
35
            setattr(namespace, warned, True)
36
            warnings.warn(
37
                'Comma-separated --plugin value is deprecated, '
38
                'use multiple --plugin options instead.'
39
                )
40
        values = value.split(',')
41
        prev = getattr(namespace, self.dest, None)
42
        if isinstance(prev, list):
43
            values = prev + [p for p in values if p not in prev]
44
        setattr(namespace, self.dest, values)
45
46
47
class ArgParse(argparse.ArgumentParser):
48
    default_directory = os.path.abspath(compat.getcwd())
49
    default_host = os.getenv('BROWSEPY_HOST', '127.0.0.1')
50
    default_port = os.getenv('BROWSEPY_PORT', '8080')
51
    plugin_action_class = PluginAction
52
53
    defaults = {
54
        'prog': meta.app,
55
        'formatter_class': HelpFormatter,
56
        'description': 'description: starts a %s web file browser' % meta.app
57
        }
58
59
    def __init__(self, sep=os.sep):
60
        super(ArgParse, self).__init__(**self.defaults)
61
        self.add_argument(
62
            'host', nargs='?',
63
            default=self.default_host,
64
            help='address to listen (default: %(default)s)')
65
        self.add_argument(
66
            'port', nargs='?', type=int,
67
            default=self.default_port,
68
            help='port to listen (default: %(default)s)')
69
        self.add_argument(
70
            '--directory', metavar='PATH', type=self._directory,
71
            default=self.default_directory,
72
            help='serving directory (default: current path)')
73
        self.add_argument(
74
            '--initial', metavar='PATH',
75
            type=lambda x: self._directory(x) if x else None,
76
            help='default directory (default: same as --directory)')
77
        self.add_argument(
78
            '--removable', metavar='PATH', type=self._directory,
79
            default=None,
80
            help='base directory allowing remove (default: none)')
81
        self.add_argument(
82
            '--upload', metavar='PATH', type=self._directory,
83
            default=None,
84
            help='base directory allowing upload (default: none)')
85
        self.add_argument(
86
            '--exclude', metavar='PATTERN',
87
            action='append',
88
            default=[],
89
            help='exclude paths by pattern (multiple)')
90
        self.add_argument(
91
            '--exclude-from', metavar='PATH', type=self._file,
92
            action='append',
93
            default=[],
94
            help='exclude paths by pattern file (multiple)')
95
        self.add_argument(
96
            '--plugin', metavar='MODULE',
97
            action=self.plugin_action_class,
98
            default=[],
99
            help='load plugin module (multiple)')
100
        self.add_argument(
101
            '--debug', action='store_true',
102
            help=argparse.SUPPRESS)
103
104
    def _path(self, arg):
105
        if PY_LEGACY and hasattr(sys.stdin, 'encoding'):
106
            encoding = sys.stdin.encoding or sys.getdefaultencoding()
107
            arg = arg.decode(encoding)
108
        return os.path.abspath(arg)
109
110
    def _file(self, arg):
111
        path = self._path(arg)
112
        if os.path.isfile(path):
113
            return path
114
        self.error('%s is not a valid file' % arg)
115
116
    def _directory(self, arg):
117
        path = self._path(arg)
118
        if os.path.isdir(path):
119
            return path
120
        self.error('%s is not a valid directory' % arg)
121
122
123
def create_exclude_fnc(patterns, base, sep=os.sep):
124
    if patterns:
125
        regex = '|'.join(translate(pattern, sep, base) for pattern in patterns)
126
        return re.compile(regex).search
127
    return None
128
129
130
def collect_exclude_patterns(paths):
131
    patterns = []
132
    for path in paths:
133
        with open(path, 'r') as f:
134
            for line in f:
135
                line = line.split('#')[0].strip()
136
                if line:
137
                    patterns.append(line)
138
    return patterns
139
140
141
def main(argv=sys.argv[1:], app=app, parser=ArgParse, run_fnc=flask.Flask.run):
142
    plugin_manager = app.extensions['plugin_manager']
143
    args = plugin_manager.load_arguments(argv, parser())
144
    patterns = args.exclude + collect_exclude_patterns(args.exclude_from)
145
    if args.debug:
146
        os.environ['DEBUG'] = 'true'
147
    app.config.update(
148
        directory_base=args.directory,
149
        directory_start=args.initial or args.directory,
150
        directory_remove=args.removable,
151
        directory_upload=args.upload,
152
        plugin_modules=args.plugin,
153
        exclude_fnc=create_exclude_fnc(patterns, args.directory)
154
        )
155
    plugin_manager.reload()
156
    run_fnc(
157
        app,
158
        host=args.host,
159
        port=args.port,
160
        debug=getdebug(),
161
        use_reloader=False,
162
        threaded=True
163
        )
164
165
166
if __name__ == '__main__':
167
    main()
168