root/config.py

Revision 616:980beee26513, 13.1 kB (checked in by Stefan Schwarzer <sschwarzer@…>, 4 weeks ago)
Removed support for option `--dir-levels`. This option doesn't make sense for the new JavaScript-based interface.
Line 
1# encoding: UTF-8
2# Copyright (C) 2009-2010, Stefan Schwarzer
3# see the file LICENSE for the license of this software
4
5"""
6Configure the application.
7
8Configuration is done from the command line and/or from
9environment variables. Of these, currently only the variable
10`WSB_IGNORE` is supported.
11
12This module also contains the manpage-style documentation for end
13users of Websourcebrowser.
14"""
15
16import fnmatch
17import sys
18
19
20MAN_PAGE = """\
21NAME
22
23  Websourcebrowser - conveniently browse source code with a webbrowser
24
25SYNOPSIS
26
27  wsbrowser [options]
28
29  The name of the executable is wsbrowser on Posix and wsbrowser.py on
30  Windows.
31
32DESCRIPTION
33
34  Websourcebrowser starts an internal webserver and listens for HTTP
35  requests for directories and files in a project's source code
36  directory. The web interface allows to browse the source code with a
37  left frame containing the directory tree and a right frame a
38  selected file. The directory frame is always accessible.
39
40  If Pygments (http://pygments.org) is installed, files may be
41  displayed with syntax highlighting.
42
43  To stop Websourcebrowser, type Ctrl-C (on Posix) or Ctrl-Break (on
44  Windows).
45
46WEB INTERFACE
47
48  After invoking Websourcebrowser, it outputs the URL you should put
49  in the address line of your webbrowser. After you've done this, you
50  get a display with two frames: The left displays the directory
51  specified by the --root option (or the default), the right shows a
52  placeholder. If you click a link in the left frame denoting a file,
53  it's shown in the right frame. If the file is a binary file for
54  which Websourcebrowser doesn't know to render it, a hexdump is shown
55  instead. At the moment, this happens even for popular formats as
56  PDF, and that will change.
57
58  The directory display on the left has several framed links which you
59  can imagine as buttons. "Reset view" resets the display, this is as
60  if you just entered the root URL which Websourcebrowser prints in
61  the terminal upon start. The button "Homepage" opens a new window
62  with the page identified with the command line option
63  --project-homepage. Below the two already described buttons you can
64  control the number of expanded directory levels. By definition, when
65  the level is 1, just a linear list of the directories and files in
66  the project root directory is shown. A level of 2 means that the
67  contained directories and files are also displayed. This is easier
68  to check out than to describe. Note that you won't see a difference
69  after a click if the project directory structure is not that deep.
70  By the way, clicking on a directory link will show a list of its
71  contents if they're not already displayed. If, for your taste, too
72  many levels are shown, you can reduce them with the buttons at the
73  top of the frame.
74
75OPTIONS
76
77  -h, --help
78        Show this help text and exit.
79
80  -r ROOT, --root=ROOT
81        Set the document root directory (default: current directory).
82        For example, if --root=/my/greatproject is specified, the URL
83        path / will correspond to the directory /my/greatproject.
84        Directories or files "above" the root directory aren't
85        accessible (probably, see section BUGS).
86
87  -t PROJECT_TITLE, --project-title=PROJECT_TITLE
88        Set the project title to display as the title of the frameset
89        (default: capitalized base name of the root directory, so the
90        above root option sets a project title "Greatproject").
91
92  -i PATTERN, --ignore-pattern=PATTERN
93        Ignore directories and files matching this pattern (default:
94        nothing is ignored). To specify more than one pattern, repeat
95        this option.
96
97        For each pattern, the corresponding pattern plus "/*" is also
98        used, so you don't have to specify two patterns to ignore a
99        directory and everything under it.
100
101        Example: To ignore all Mercurial and Subversion bookkeeping
102        files, use --ignore-pattern="/*.svn" --ignore-pattern="/*.hg".
103
104  -l, --line-numbers
105        Prepend lines of text files with line numbers (default: no
106        line numbers).
107
108        This works only if Pygments is installed, otherwise this
109        option is ignored.
110
111  --http-host=HTTP_HOST
112        Use the specified host name or IP for the local interface to
113        listen on (default: localhost).
114
115        With the defaults for --http-host and --http-port, the address
116        line in the browser is http://localhost:8000/ .
117
118  --http-port=HTTP_PORT
119        Listen on this HTTP port (default: 8000).
120
121        With the defaults for --http-host and --http-port, the address
122        line in the browser is http://localhost:8000/ .
123
124  -c CLIENT, --allowed-client=CLIENT
125        Allow remote access from this host name or IP address
126        (default: allow only access from localhost). The localhost
127        address is always included. For multiple host names or IP
128        addresses use the option several times. For example:
129
130        wsbrowser -c my.host.com -c 199.243.19.27
131
132        As a special case, specifying ALL as the options value accepts
133        requests from all addresses and so effectively makes
134        Websourcebrowser a web server on the public internet, unless
135        prevented by a router or firewall.
136
137  --logging
138        Activate logging of HTTP accesses to standard output (default:
139        no logging).
140
141ENVIRONMENT
142
143  WSB_IGNORE
144        Set wildcard patterns to ignore. If multiple patterns are
145        given, they must be separated by whitespace.
146
147        A typical example for WSB_IGNORE might be
148        *.pyc  *.pyo  */.svn  *.svn/*  */.hg  */.hg/*  *.swp
149
150        If both this environment variable and one or more
151        --ignore-pattern options are used, the patterns from the
152        command line are added to those from the environment variable.
153
154BUGS
155
156  Probably there are some bugs as Websourcebrowser is still alpha
157  software. Currently, I don't recommend to use Websourcebrowser for
158  use on the public internet because it may contain significant
159  vulnerabilities. (I indeed paid attention to security though I would
160  welcome an experienced fellow to spot security problems.)
161
162  Since the software is still in the alpha stage, it may still change
163  significantly. This may include incompatible variations like
164  removing command line options or changing their semantics.
165
166AUTHOR
167
168  The author of Websourcebrowser is
169  Stefan Schwarzer <sschwarzer@sschwarzer.net>.
170
171SEE ALSO
172
173  The pydoc module in the Python distribution similarly acts as a
174  webserver if it's invoked as a program with the option -p and a port
175  number.
176"""
177
178import optparse
179import os
180import socket
181import sys
182
183# Websourcebrowser modules
184import coding
185import tools
186
187#
188# constants used in several places
189#
190# Websourcebrowser version
191VERSION = "0.4 pre-alpha"
192# special value to denote allowance of all clients
193ALL_CLIENTS = "all_clients_allowed"
194
195# The following paths are also used in the JavaScript code. So
196#  if you change the constants here, you'll possibly also have
197#  to change the JavaScript code.
198
199# "subdirectory" for the project
200#  this is hard-coded in `browser.Websourcebrowser`!
201PROJECT_DIR = u"project"
202# "subdirectory" for static files
203STATIC_DIR = u"static"
204
205#
206# defaults, can be changed via command line
207#
208
209# project root directory
210root = os.getcwd()
211if not isinstance(root, unicode):
212    #XXX default and fallback encoding in `coding` module _may_
213    #  _both_ be wrong, that is, the file actually may have
214    #  another encoding
215    root = coding.decode(root)
216
217# project title, included in HTML `title` and `h1` tags; if `None`,
218#  use the basename of the root directory (see above), i. e. if the
219#  root directory is "/some/project/path", the title becomes "Path"
220project_title = None
221
222# list of glob patterns for files/directories to ignore; for each
223#  pattern "x", the pattern "x/*" is added implicitly, so you don't
224#  need to do it
225ignore_patterns = []
226
227# if true, include line numbers in listings; works only if Pygments
228#  is installed and used
229line_numbers = False
230
231# the local HTTP interface
232http_host = 'localhost'
233
234# the HTTP port to listen on
235http_port = 8000
236
237# clients which are allowed to access this server
238allowed_clients = ['localhost']
239
240# if true, log each GET request on stdout
241logging = False
242
243
244def ignore_path(path):
245    """Return True if the path should be ignored according to formerly
246    set ignore patterns.
247
248    In addition to the values given on the command line and in the
249    `WSB_IGNORE` environment variable, the test uses a pattern "x/*"
250    for each pattern "x".
251    """
252    additional_ignore_patterns = [p + "/*" for p in ignore_patterns]
253    for pattern in ignore_patterns + additional_ignore_patterns:
254        if fnmatch.fnmatch(path, pattern):
255            return True
256    return False
257
258def set_from_args(args=None):
259    """(Re)set the configuration values in the module from the
260    provided list of arguments `args`, similar to what
261    `sys.argv[1:]` usually contains. If `args` is not set or None,
262    use the defaults as set in this file.
263
264    >>> import config
265    >>> config.set_from_args(["-t", u"Cool project", '--ignore-pattern=*/.svn',
266    ...                       '--ignore-pattern=*/.svn/*', '--http-port=8080',
267    ...                       '--line-numbers'])
268    >>> config.project_title
269    u'Cool project'
270    >>> config.ignore_patterns
271    ['*/.svn', '*/.svn/*']
272    >>> config.http_port   # the port value is an integer
273    8080
274    >>> config.line_numbers, config.logging
275    (True, False)
276    """
277    if args is None:
278        args = sys.argv[1:]
279    # set up command line parser
280    parser = optparse.OptionParser(add_help_option=False)
281    parser.add_option("-h", "--help", action='store_true')
282    parser.add_option("-r", "--root", help="document root [current directory]")
283    parser.add_option("-t", "--project-title",
284                      help="project title to use in HTML title "
285                           "[last part of root dir]")
286    parser.add_option("-i", "--ignore-pattern", metavar="PATTERN",
287                      dest='ignore_patterns', action='append',
288                      help='ignore dirs/files matching this pattern '
289                           '(e. g. "*/.svn/*") [nothing ignored]')
290    parser.add_option("-l", "--line-numbers", action='store_true',
291                      help="prepend text lines with line numbers [no]")
292    parser.add_option("--http-host", help="local HTTP interface [localhost]")
293    parser.add_option("--http-port", type='int',
294                      help="HTTP port to listen on [%default]")
295    parser.add_option("-c", "--allowed-client", metavar="CLIENT",
296                      dest='allowed_clients', action='append',
297                      help="allow a remote client to connect [localhost]; "
298                           "use ALL to run as a public server")
299    parser.add_option("--logging", action='store_true',
300                      help="log HTTP accesses to stdout [no]")
301    parser.set_defaults(**globals())
302    (options, args) = parser.parse_args(args)
303    if options.help:
304        print MAN_PAGE
305        sys.exit()
306    # from here on, `args` is the list of only the positional arguments!
307    if args:
308        parser.error("program doesn't take arguments")
309    if not isinstance(options.root, unicode):
310        #XXX default and fallback encoding in `coding` module _may_
311        #  _both_ be wrong, in other words, the file may have yet
312        #  another encoding
313        options.root = coding.decode(options.root)
314    # interpret tilde markup for a user's home directory, then normalize
315    #  path by collapsing ".." sequences etc.
316    options.root = tools.normalize_path(os.path.expanduser(options.root))
317    if options.project_title is None:
318        options.project_title = os.path.basename(options.root).capitalize()
319    try:
320        options.project_title = coding.decode(options.project_title)
321    except coding.Error:
322        pass
323    # normalize allowed client addresses to IPs
324    allowed_clients = set()
325    dummy_port = 80
326    global invalid_clients
327    invalid_clients = []
328    for client in options.allowed_clients:
329        if client == "ALL":
330            allowed_clients = ALL_CLIENTS
331            break
332        try:
333            for addr_info in socket.getaddrinfo(client, dummy_port):
334                # `addr_info[4]` is a host/port tuple
335                allowed_clients.add(addr_info[4][0])
336        except socket.gaierror:
337            invalid_clients.append(client)
338    options.allowed_clients = allowed_clients
339    # copy changed configuration back to module namespace; do not use
340    #  `globals().update()` because that may copy too much
341    option_names = ['root', 'project_title', 'ignore_patterns',
342                    'line_numbers', 'http_host', 'http_port',
343                    'allowed_clients', 'logging']
344    for name in option_names:
345        globals()[name] = getattr(options, name)
346
347def set_from_environment():
348    """Inspect the environment to set some configuration parameters.
349
350    Currently, only the ignore patterns are considered, via the
351    environment variable `WSB_IGNORE`. Its value is a whitespace-
352    separated string of patterns, for example "*.pyc  *.pyo  */.svn
353    *.svn/*  */.hg  */.hg/*  *.swp".
354    """
355    global ignore_patterns
356    environ = os.environ
357    patterns = environ.get('WSB_IGNORE', "")
358    for pattern in patterns.split():
359        ignore_patterns.append(pattern)
Note: See TracBrowser for help on using the browser.