Passed
Pull Request — develop (#458)
by
unknown
02:49
created

doorstop.server.main   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 112
dl 0
loc 175
rs 10
c 0
b 0
f 0
wmc 20

10 Functions

Rating   Name   Duplication   Size   Complexity  
A get_documents() 0 9 2
A post_numbers() 0 11 2
A get_item() 0 9 2
A main() 0 28 1
A get_attrs() 0 11 2
A get_document() 0 9 2
A index() 0 6 1
A get_items() 0 10 2
A get_attr() 0 16 4
A run() 0 18 2
1
#!/usr/bin/env python
2
3
"""REST server to display content and reserve item numbers."""
4
5
import os
6
from collections import defaultdict
7
import webbrowser
8
import argparse
9
import logging
10
11
import bottle
12
from bottle import get, post, request
13
14
from doorstop import common, build, publisher
15
from doorstop.common import HelpFormatter
16
from doorstop.server import utilities
17
from doorstop import settings
18
19
log = common.logger(__name__)
20
21
app = utilities.StripPathMiddleware(bottle.app())
22
tree = None  # set in `run`, read in the route functions
23
numbers = defaultdict(int)  # cache of next document numbers
24
25
26
def main(args=None):
27
    """Process command-line arguments and run the program."""
28
    from doorstop import SERVER, VERSION
0 ignored issues
show
introduced by
Import outside toplevel (doorstop)
Loading history...
29
30
    # Shared options
31
    debug = argparse.ArgumentParser(add_help=False)
32
    debug.add_argument('-V', '--version', action='version', version=VERSION)
33
    debug.add_argument('--debug', action='store_true', help=argparse.SUPPRESS)
34
    debug.add_argument('--launch', action='store_true', help=argparse.SUPPRESS)
35
    shared = {'formatter_class': HelpFormatter, 'parents': [debug]}
36
37
    # Build main parser
38
    parser = argparse.ArgumentParser(prog=SERVER, description=__doc__,
39
                                     **shared)
40
    parser.add_argument('-j', '--project', metavar="PATH",
41
                        help="path to the root of the project")
42
    parser.add_argument('-P', '--port', metavar='NUM', type=int,
43
                        help="use a custom port for the server")
44
45
    # Parse arguments
46
    args = parser.parse_args(args=args)
47
48
    # Configure logging
49
    logging.basicConfig(format=settings.VERBOSE_LOGGING_FORMAT,
50
                        level=settings.VERBOSE_LOGGING_LEVEL)
51
52
    # Run the program
53
    run(args, os.getcwd(), parser.error)
54
55
56
def run(args, cwd, _):
57
    """Start the server.
58
59
    :param args: Namespace of CLI arguments (from this module or the CLI)
60
    :param cwd: current working directory
61
    :param error: function to call for CLI errors
62
63
    """
64
    global tree  # pylint: disable=W0603,C0103
65
    tree = build(cwd=cwd, root=args.project)
66
    tree.load()
67
    host = 'localhost'
68
    port = args.port or settings.SERVER_PORT
69
    if args.launch:
70
        url = utilities.build_url(host=host, port=port)
71
        webbrowser.open(url)
72
    bottle.run(app=app, host=host, port=port,
73
               debug=args.debug, reloader=args.debug)
74
75
76
@get('/')
77
def index():
78
    """Read the tree."""
79
    yield '<pre><code>'
80
    yield tree.draw()
81
    yield '</pre></code>'
82
83
84
@get('/documents')
85
def get_documents():
86
    """Read the tree's documents."""
87
    prefixes = [str(document.prefix) for document in tree]
88
    if utilities.json_response(request):
89
        data = {'prefixes': prefixes}
90
        return data
91
    else:
92
        return '<br>'.join(prefixes)
93
94
95
@get('/documents/<prefix>')
96
def get_document(prefix):
97
    """Read a tree's document."""
98
    document = tree.find_document(prefix)
99
    if utilities.json_response(request):
100
        data = {str(item.uid): item.data for item in document}
101
        return data
102
    else:
103
        return publisher.publish_lines(document, ext='.html')
104
105
106
@get('/documents/<prefix>/items')
107
def get_items(prefix):
108
    """Read a document's items."""
109
    document = tree.find_document(prefix)
110
    uids = [str(item.uid) for item in document]
111
    if utilities.json_response(request):
112
        data = {'uids': uids}
113
        return data
114
    else:
115
        return '<br>'.join(uids)
116
117
118
@get('/documents/<prefix>/items/<uid>')
119
def get_item(prefix, uid):
120
    """Read a document's item."""
121
    document = tree.find_document(prefix)
122
    item = document.find_item(uid)
123
    if utilities.json_response(request):
124
        return {'data': item.data}
125
    else:
126
        return publisher.publish_lines(item, ext='.html')
127
128
129
@get('/documents/<prefix>/items/<uid>/attrs')
130
def get_attrs(prefix, uid):
131
    """Read an item's attributes."""
132
    document = tree.find_document(prefix)
133
    item = document.find_item(uid)
134
    attrs = sorted(item.data.keys())
135
    if utilities.json_response(request):
136
        data = {'attrs': attrs}
137
        return data
138
    else:
139
        return '<br>'.join(attrs)
140
141
142
@get('/documents/<prefix>/items/<uid>/attrs/<name>')
143
def get_attr(prefix, uid, name):
144
    """Read an item's attribute value."""
145
    document = tree.find_document(prefix)
146
    item = document.find_item(uid)
147
    value = item.data.get(name, None)
148
    if utilities.json_response(request):
149
        data = {'value': value}
150
        return data
151
    else:
152
        if isinstance(value, str):
153
            return value
154
        try:
155
            return '<br>'.join(str(e) for e in value)
156
        except TypeError:
157
            return str(value)
158
159
160
@post('/documents/<prefix>/numbers')
161
def post_numbers(prefix):
162
    """Create the next number in a document."""
163
    document = tree.find_document(prefix)
164
    number = max(document.next_number, numbers[prefix])
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable numbers does not seem to be defined.
Loading history...
165
    numbers[prefix] = number + 1
166
    if utilities.json_response(request):
167
        data = {'next': number}
168
        return data
169
    else:
170
        return str(number)
171
172
173
if __name__ == '__main__':  # pragma: no cover (manual test)
174
    main()
175