| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | """ |
|---|
| 6 | Configure the application. |
|---|
| 7 | |
|---|
| 8 | Configuration is done from the command line and/or from |
|---|
| 9 | environment variables. Of these, currently only the variable |
|---|
| 10 | `WSB_IGNORE` is supported. |
|---|
| 11 | |
|---|
| 12 | This module also contains the manpage-style documentation for end |
|---|
| 13 | users of Websourcebrowser. |
|---|
| 14 | """ |
|---|
| 15 | |
|---|
| 16 | import fnmatch |
|---|
| 17 | import sys |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | MAN_PAGE = """\ |
|---|
| 21 | NAME |
|---|
| 22 | |
|---|
| 23 | Websourcebrowser - conveniently browse source code with a webbrowser |
|---|
| 24 | |
|---|
| 25 | SYNOPSIS |
|---|
| 26 | |
|---|
| 27 | wsbrowser [options] |
|---|
| 28 | |
|---|
| 29 | The name of the executable is wsbrowser on Posix and wsbrowser.py on |
|---|
| 30 | Windows. |
|---|
| 31 | |
|---|
| 32 | DESCRIPTION |
|---|
| 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 | |
|---|
| 46 | WEB 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 | |
|---|
| 75 | OPTIONS |
|---|
| 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 | |
|---|
| 141 | ENVIRONMENT |
|---|
| 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 | |
|---|
| 154 | BUGS |
|---|
| 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 | |
|---|
| 166 | AUTHOR |
|---|
| 167 | |
|---|
| 168 | The author of Websourcebrowser is |
|---|
| 169 | Stefan Schwarzer <sschwarzer@sschwarzer.net>. |
|---|
| 170 | |
|---|
| 171 | SEE 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 | |
|---|
| 178 | import optparse |
|---|
| 179 | import os |
|---|
| 180 | import socket |
|---|
| 181 | import sys |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | import coding |
|---|
| 185 | import tools |
|---|
| 186 | |
|---|
| 187 | |
|---|
| 188 | |
|---|
| 189 | |
|---|
| 190 | |
|---|
| 191 | VERSION = "0.4 pre-alpha" |
|---|
| 192 | |
|---|
| 193 | ALL_CLIENTS = "all_clients_allowed" |
|---|
| 194 | |
|---|
| 195 | |
|---|
| 196 | |
|---|
| 197 | |
|---|
| 198 | |
|---|
| 199 | |
|---|
| 200 | |
|---|
| 201 | PROJECT_DIR = u"project" |
|---|
| 202 | |
|---|
| 203 | STATIC_DIR = u"static" |
|---|
| 204 | |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | |
|---|
| 208 | |
|---|
| 209 | |
|---|
| 210 | root = os.getcwd() |
|---|
| 211 | if not isinstance(root, unicode): |
|---|
| 212 | |
|---|
| 213 | |
|---|
| 214 | |
|---|
| 215 | root = coding.decode(root) |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | |
|---|
| 219 | |
|---|
| 220 | project_title = None |
|---|
| 221 | |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | ignore_patterns = [] |
|---|
| 226 | |
|---|
| 227 | |
|---|
| 228 | |
|---|
| 229 | line_numbers = False |
|---|
| 230 | |
|---|
| 231 | |
|---|
| 232 | http_host = 'localhost' |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | http_port = 8000 |
|---|
| 236 | |
|---|
| 237 | |
|---|
| 238 | allowed_clients = ['localhost'] |
|---|
| 239 | |
|---|
| 240 | |
|---|
| 241 | logging = False |
|---|
| 242 | |
|---|
| 243 | |
|---|
| 244 | def 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 | |
|---|
| 258 | def 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 | |
|---|
| 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 | |
|---|
| 307 | if args: |
|---|
| 308 | parser.error("program doesn't take arguments") |
|---|
| 309 | if not isinstance(options.root, unicode): |
|---|
| 310 | |
|---|
| 311 | |
|---|
| 312 | |
|---|
| 313 | options.root = coding.decode(options.root) |
|---|
| 314 | |
|---|
| 315 | |
|---|
| 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 | |
|---|
| 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 | |
|---|
| 335 | allowed_clients.add(addr_info[4][0]) |
|---|
| 336 | except socket.gaierror: |
|---|
| 337 | invalid_clients.append(client) |
|---|
| 338 | options.allowed_clients = allowed_clients |
|---|
| 339 | |
|---|
| 340 | |
|---|
| 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 | |
|---|
| 347 | def 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) |
|---|