File: standalone.py

package info (click to toggle)
viewcvs 0.9.2%2Bcvs.1.0.dev.2004.07.28-4.1etch1
  • links: PTS
  • area: main
  • in suites: etch
  • size: 1,452 kB
  • ctags: 1,355
  • sloc: python: 10,100; cpp: 840; ansic: 763; yacc: 526; sh: 163; makefile: 115
file content (648 lines) | stat: -rwxr-xr-x 26,649 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
#!/usr/bin/env python
# $Id: standalone.py,v 1.34 2004/07/17 01:50:20 jhenstridge Exp $
# vim:sw=4:ts=4:et:nowrap
# [Emacs: -*- python -*-]
#
# Copyright (C) 1999-2002 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
#   This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany
#   ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302
#   gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# Note: this module is designed to deploy instantly and run under any
# version of Python from 1.5 and up.  That's why some 2.0 features 
# (like string methods) are conspicuously avoided.

# XXX Security issues?

"""Run "standalone.py -p <port>" to start an HTTP server on a given port 
on the local machine to generate ViewCVS web pages.
"""

__author__ = "Peter Funk <pf@artcom-gmbh.de>"
__date__ = "11 November 2001"
__version__ = "$Revision: 1.34 $"
__credits__ = """Guido van Rossum, for an excellent programming language.
Greg Stein, for writing ViewCVS in the first place.
Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py.
"""

# INSTALL-TIME CONFIGURATION
#
# This value will be set during the installation process. During
# development, it will remain None.
#

LIBRARY_DIR = None

import sys
import os
import stat
import string
import urllib
import rfc822
import socket
import select
import BaseHTTPServer

if LIBRARY_DIR:
    sys.path.insert(0, LIBRARY_DIR)
else:
    sys.path[:0] = ['lib']

import sapi
import viewcvs
import apache_icons
import compat; compat.for_standalone()


# calculate the mtime of the apache_icons module, for use when serving icons
mtime = os.stat(apache_icons.__file__)[stat.ST_MTIME]
icons_last_modified = rfc822.formatdate(mtime)
del mtime

if viewcvs.CONF_PATHNAME is None:
  viewcvs.g_install_dir = ''

class Options:
    port = 7467 # default TCP/IP port used for the server
    start_gui = 0 # No GUI unless requested.
    repositories = {} # use default repositories specified in config
    if sys.platform == 'mac':
        host = '127.0.0.1' 
    else:
        host = 'localhost'

# --- web browser interface: ----------------------------------------------

class StandaloneServer(sapi.CgiServer):
    def __init__(self, handler):
        sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32")
        self.handler = handler

    def header(self, content_type='text/html', status=None):
        if not self.headerSent:
            self.headerSent = 1
            if status is None:
              statusCode = 200
              statusText = 'OK'           
            else:              
              p = string.find(status, ' ')
              if p < 0:
                statusCode = int(status)
                statusText = ''
              else:
                statusCode = int(status[:p])
                statusText = status[p+1:]
            self.handler.send_response(statusCode, statusText)
            self.handler.send_header("Content-type", content_type)
            for (name, value) in self.headers:
                self.handler.send_header(name, value)
            self.handler.end_headers()
            

def serve(host, port, callback=None):
    """start a HTTP server on the given port.  call 'callback' when the
    server is ready to serve"""

    class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
         
        def do_GET(self):
            """Serve a GET request."""
            if not self.path or self.path == "/":
                self.redirect()
            elif self.is_viewcvs():
                try:
                    self.run_viewcvs()
                except IOError:
                    # ignore IOError: [Errno 32] Broken pipe
                    pass
            elif self.path[:7] == "/icons/":
                # XXX icon type should not be hardcoded to GIF:
                self.send_response(200)
                self.send_header("Content-type", "image/gif")
                self.send_header("Last-Modified", icons_last_modified)
                self.end_headers()
                apache_icons.serve_icon(self.path, self.wfile)
            else:
                self.send_error(404)

        def do_POST(self):
            """Serve a POST request."""
            if self.is_viewcvs():
                self.run_viewcvs()
            else:
                self.send_error(501, "Can only POST to viewcvs")

        def is_viewcvs(self):
            """Check whether self.path matches the hardcoded ScriptAlias
            /viewcvs"""
            if self.path[:8] == "/viewcvs":
                return 1
            return 0

        def redirect(self):
            """redirect the browser to the viewcvs URL"""
            self.send_response(301, "moved (redirection follows)")
            self.send_header("Content-type", "text/html")
            self.send_header("Location", self.server.url + 'viewcvs/')
            self.end_headers()
            self.wfile.write("""<html>
<head>
<meta http-equiv="refresh" content="1; URL=%s">
</head>
<body>
<h1>Redirection to <a href="%s">ViewCVS</a></h1>
Wait a second.   You will be automatically redirected to <b>ViewCVS</b>.
If this doesn't work, please click on the link above.
</body>
</html>
""" % tuple([self.server.url + "viewcvs/"]*2))

        def run_viewcvs(self):
            """This is a quick and dirty cut'n'rape from Pythons 
            standard library module CGIHTTPServer."""
            scriptname = "/viewcvs"
            assert self.path[:8] == scriptname
            viewcvs_url, rest = self.server.url[:-1]+scriptname, self.path[8:]
            i = string.rfind(rest, '?')
            if i >= 0:
                rest, query = rest[:i], rest[i+1:]
            else:
                query = ''
            # sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
            env = os.environ
            # Since we're going to modify the env in the parent, provide empty
            # values to override previously set values
            for k in env.keys():
                if k[:5] == 'HTTP_':
                    del env[k]
            for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
                      'HTTP_USER_AGENT', 'HTTP_COOKIE'):
                if env.has_key(k): 
                    env[k] = ""
            # XXX Much of the following could be prepared ahead of time!
            env['SERVER_SOFTWARE'] = self.version_string()
            env['SERVER_NAME'] = self.server.server_name
            env['GATEWAY_INTERFACE'] = 'CGI/1.1'
            env['SERVER_PROTOCOL'] = self.protocol_version
            env['SERVER_PORT'] = str(self.server.server_port)
            env['REQUEST_METHOD'] = self.command
            uqrest = urllib.unquote(rest)
            env['PATH_INFO'] = uqrest
            env['SCRIPT_NAME'] = scriptname
            if query:
                env['QUERY_STRING'] = query
            host = self.address_string()
            if host != self.client_address[0]:
                env['REMOTE_HOST'] = host
            env['REMOTE_ADDR'] = self.client_address[0]
            # AUTH_TYPE
            # REMOTE_USER
            # REMOTE_IDENT
            if self.headers.typeheader is None:
                env['CONTENT_TYPE'] = self.headers.type
            else:
                env['CONTENT_TYPE'] = self.headers.typeheader
            length = self.headers.getheader('content-length')
            if length:
                env['CONTENT_LENGTH'] = length
            accept = []
            for line in self.headers.getallmatchingheaders('accept'):
                if line[:1] in string.whitespace:
                    accept.append(string.strip(line))
                else:
                    accept = accept + string.split(line[7:], ',')
            env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
            ua = self.headers.getheader('user-agent')
            if ua:
                env['HTTP_USER_AGENT'] = ua
            modified = self.headers.getheader('if-modified-since')
            if modified:
                env['HTTP_IF_MODIFIED_SINCE'] = modified
            etag = self.headers.getheader('if-none-match')
            if etag:
                env['HTTP_IF_NONE_MATCH'] = etag
            # XXX Other HTTP_* headers
            decoded_query = string.replace(query, '+', ' ')

            # Preserve state, because we execute script in current process:
            save_argv = sys.argv
            save_stdin = sys.stdin
            save_stdout = sys.stdout
            save_stderr = sys.stderr
            # For external tools like enscript we also need to redirect
            # the real stdout file descriptor. (On windows, reassigning the
            # sys.stdout variable is sufficient because pipe_cmds makes it
            # the standard output for child processes.)
            if sys.platform != "win32": save_realstdout = os.dup(1) 
            try:
                try:
                    sys.stdout = self.wfile
                    if sys.platform != "win32":
                      os.close(1) 
                      assert os.dup(self.wfile.fileno()) == 1
                    sys.stdin = self.rfile
                    viewcvs.main(StandaloneServer(self))
                finally:
                    sys.argv = save_argv
                    sys.stdin = save_stdin
                    sys.stdout.flush()
                    if sys.platform != "win32":
                      os.close(1)
                      assert os.dup(save_realstdout) == 1
                      os.close(save_realstdout)
                    sys.stdout = save_stdout
                    sys.stderr = save_stderr
            except SystemExit, status:
                self.log_error("ViewCVS exit status %s", str(status))
            else:
                self.log_error("ViewCVS exited ok")

    class ViewCVS_Server(BaseHTTPServer.HTTPServer):
        def __init__(self, host, port, callback):
            self.address = (host, port)
            self.url = 'http://%s:%d/' % (host, port)
            self.callback = callback
            BaseHTTPServer.HTTPServer.__init__(self, self.address,
                                               self.handler)

        def serve_until_quit(self):
            self.quit = 0
            while not self.quit:
                rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
                if rd:
                    self.handle_request()

        def server_activate(self):
            BaseHTTPServer.HTTPServer.server_activate(self)
            if self.callback:
                self.callback(self)

        def server_bind(self):
            # set SO_REUSEADDR (if available on this platform)
            if hasattr(socket, 'SOL_SOCKET') \
               and hasattr(socket, 'SO_REUSEADDR'):
                self.socket.setsockopt(socket.SOL_SOCKET,
                                       socket.SO_REUSEADDR, 1)
            BaseHTTPServer.HTTPServer.server_bind(self)

    ViewCVS_Server.handler = ViewCVS_Handler

    try:
        # XXX Move this code out of this function.
        # Early loading of configuration here.  Used to
        # allow tinkering with some configuration settings:
        viewcvs.handle_config()
        if options.repositories:
            viewcvs.cfg.general.default_root = "Development"
            viewcvs.cfg.general.cvs_roots.update(options.repositories)
        elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \
             not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]):
            sys.stderr.write("*** No repository found. Please use the -r option.\n")
            sys.stderr.write("   Use --help for more info.\n")
            raise KeyboardInterrupt # Hack!
        os.close(0) # To avoid problems with shell job control

        # always use default icon and docroot locations
        viewcvs.cfg.options.icons = "/icons"
        viewcvs.cfg.options.docroot = None

        # if cvsnt isn't found, fall back to rcs
        if (viewcvs.cfg.conf_path is None 
            and viewcvs.cfg.general.cvsnt_exe_path):
          import popen
          cvsnt_works = 0
          try:
            fp = popen.popen(viewcvs.cfg.general.cvsnt_exe_path, 
                             ['--version'], 'rt')
            try:
              while 1:
                line = fp.readline()
                if not line: break
                if string.find(line, "Concurrent Versions System (CVSNT)")>=0:
                  cvsnt_works = 1
                  while fp.read(4096):
                    pass
                  break
            finally:
              fp.close()
          except:
            pass
          if not cvsnt_works:
            viewcvs.cfg.cvsnt_exe_path = None

        ViewCVS_Server(host, port, callback).serve_until_quit()
    except (KeyboardInterrupt, select.error):
        pass
    print 'server stopped'

# --- graphical interface: --------------------------------------------------

def nogui(missing_module):
    sys.stderr.write(
        "Sorry! Your Python was compiled without the %s module"%missing_module+
        " enabled.\nI'm unable to run the GUI part.  Please omit the '-g'\n"+
        "and '--gui' options or install another Python interpreter.\n")
    raise SystemExit, 1

def gui(host, port):
    """Graphical interface (starts web server and pops up a control window)."""
    class GUI:
        def __init__(self, window, host, port):
            self.window = window
            self.server = None
            self.scanner = None

            try:
                import Tkinter
            except ImportError:
                nogui("Tkinter")

            self.server_frm = Tkinter.Frame(window)
            self.title_lbl = Tkinter.Label(self.server_frm,
                text='Starting server...\n ')
            self.open_btn = Tkinter.Button(self.server_frm,
                text='open browser', command=self.open, state='disabled')
            self.quit_btn = Tkinter.Button(self.server_frm,
                text='quit serving', command=self.quit, state='disabled')


            self.window.title('ViewCVS standalone')
            self.window.protocol('WM_DELETE_WINDOW', self.quit)
            self.title_lbl.pack(side='top', fill='x')
            self.open_btn.pack(side='left', fill='x', expand=1)
            self.quit_btn.pack(side='right', fill='x', expand=1)

            # Early loading of configuration here.  Used to
            # allow tinkering with configuration settings through the gui:
            viewcvs.handle_config()
            if not LIBRARY_DIR:
                viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"

            self.options_frm = Tkinter.Frame(window)

            # cvsgraph toggle:
            self.cvsgraph_ivar = Tkinter.IntVar()
            self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph)
            self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
                text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
                command=self.toggle_use_cvsgraph)
            self.cvsgraph_toggle.pack(side='top', anchor='w')

            # enscript toggle:
            self.enscript_ivar = Tkinter.IntVar()
            self.enscript_ivar.set(viewcvs.cfg.options.use_enscript)
            self.enscript_toggle = Tkinter.Checkbutton(self.options_frm,
                text="enable enscript (needs binary)", var=self.enscript_ivar,
                command=self.toggle_use_enscript)
            self.enscript_toggle.pack(side='top', anchor='w')

            # show_subdir_lastmod toggle:
            self.subdirmod_ivar = Tkinter.IntVar()
            self.subdirmod_ivar.set(viewcvs.cfg.options.show_subdir_lastmod)
            self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
                text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
                command=self.toggle_subdirmod)
            self.subdirmod_toggle.pack(side='top', anchor='w')

            # use_re_search toggle:
            self.useresearch_ivar = Tkinter.IntVar()
            self.useresearch_ivar.set(viewcvs.cfg.options.use_re_search)
            self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
                text="allow regular expr search", var=self.useresearch_ivar,
                command=self.toggle_useresearch)
            self.useresearch_toggle.pack(side='top', anchor='w')

            # use_localtime toggle:
            self.use_localtime_ivar = Tkinter.IntVar()
            self.use_localtime_ivar.set(viewcvs.cfg.options.use_localtime)
            self.use_localtime_toggle = Tkinter.Checkbutton(self.options_frm,
                text="use localtime (instead of UTC)", 
                var=self.use_localtime_ivar,
                command=self.toggle_use_localtime)
            self.use_localtime_toggle.pack(side='top', anchor='w')

            # use_pagesize integer var:
            self.usepagesize_lbl = Tkinter.Label(self.options_frm,
                text='Paging (number of items per page, 0 disables):')
            self.usepagesize_lbl.pack(side='top', anchor='w')
            self.use_pagesize_ivar = Tkinter.IntVar()
            self.use_pagesize_ivar.set(viewcvs.cfg.options.use_pagesize)
            self.use_pagesize_entry = Tkinter.Entry(self.options_frm,
                width=10, textvariable=self.use_pagesize_ivar)
            self.use_pagesize_entry.bind('<Return>', self.set_use_pagesize)
            self.use_pagesize_entry.pack(side='top', anchor='w')

            # directory view template:
            self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
                text='Chooose HTML Template for the Directory pages:')
            self.dirtemplate_lbl.pack(side='top', anchor='w')
            self.dirtemplate_svar = Tkinter.StringVar()
            self.dirtemplate_svar.set(viewcvs.cfg.templates.directory)
            self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
                width = 40, textvariable=self.dirtemplate_svar)
            self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
            self.dirtemplate_entry.pack(side='top', anchor='w')
            self.templates_dir = Tkinter.Radiobutton(self.options_frm,
                text="directory.ezt", value="templates/directory.ezt", 
                var=self.dirtemplate_svar, command=self.set_templates_directory)
            self.templates_dir.pack(side='top', anchor='w')
            self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
                text="dir_alternate.ezt", value="templates/dir_alternate.ezt", 
                var=self.dirtemplate_svar, command=self.set_templates_directory)
            self.templates_dir_alt.pack(side='top', anchor='w')

            # log view template:
            self.logtemplate_lbl = Tkinter.Label(self.options_frm,
                text='Chooose HTML Template for the Log pages:')
            self.logtemplate_lbl.pack(side='top', anchor='w')
            self.logtemplate_svar = Tkinter.StringVar()
            self.logtemplate_svar.set(viewcvs.cfg.templates.log)
            self.logtemplate_entry = Tkinter.Entry(self.options_frm,
                width = 40, textvariable=self.logtemplate_svar)
            self.logtemplate_entry.bind('<Return>', self.set_templates_log)
            self.logtemplate_entry.pack(side='top', anchor='w')
            self.templates_log = Tkinter.Radiobutton(self.options_frm,
                text="log.ezt", value="templates/log.ezt", 
                var=self.logtemplate_svar, command=self.set_templates_log)
            self.templates_log.pack(side='top', anchor='w')
            self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
                text="log_table.ezt", value="templates/log_table.ezt", 
                var=self.logtemplate_svar, command=self.set_templates_log)
            self.templates_log_table.pack(side='top', anchor='w')

            # query view template:
            self.querytemplate_lbl = Tkinter.Label(self.options_frm,
                text='Template for the database query page:')
            self.querytemplate_lbl.pack(side='top', anchor='w')
            self.querytemplate_svar = Tkinter.StringVar()
            self.querytemplate_svar.set(viewcvs.cfg.templates.query)
            self.querytemplate_entry = Tkinter.Entry(self.options_frm,
                width = 40, textvariable=self.querytemplate_svar)
            self.querytemplate_entry.bind('<Return>', self.set_templates_query)
            self.querytemplate_entry.pack(side='top', anchor='w')
            self.templates_query = Tkinter.Radiobutton(self.options_frm,
                text="query.ezt", value="templates/query.ezt", 
                var=self.querytemplate_svar, command=self.set_templates_query)
            self.templates_query.pack(side='top', anchor='w')

            # pack and set window manager hints:
            self.server_frm.pack(side='top', fill='x')
            self.options_frm.pack(side='top', fill='x')

            self.window.update()
            self.minwidth = self.window.winfo_width()
            self.minheight = self.window.winfo_height()
            self.expanded = 0
            self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
            self.window.wm_minsize(self.minwidth, self.minheight)

            try:
                import threading
            except ImportError:
                nogui("thread")
            threading.Thread(target=serve, 
                             args=(host, port, self.ready)).start()

        def toggle_use_cvsgraph(self, event=None):
            viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()

        def toggle_use_enscript(self, event=None):
            viewcvs.cfg.options.use_enscript = self.enscript_ivar.get()

        def toggle_use_localtime(self, event=None):
            viewcvs.cfg.options.use_localtime = self.use_localtime_ivar.get()

        def toggle_subdirmod(self, event=None):
            viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()

        def toggle_useresearch(self, event=None):
            viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get()

        def set_use_pagesize(self, event=None):
            viewcvs.cfg.options.use_pagesize = self.use_pagesize_ivar.get()

        def set_templates_log(self, event=None):
            viewcvs.cfg.templates.log = self.logtemplate_svar.get()

        def set_templates_directory(self, event=None):
            viewcvs.cfg.templates.directory = self.dirtemplate_svar.get()

        def set_templates_query(self, event=None):
            viewcvs.cfg.templates.query = self.querytemplate_svar.get()

        def ready(self, server):
            """used as callback parameter to the serve() function"""
            self.server = server
            self.title_lbl.config(
                text='ViewCVS standalone server at\n' + server.url)
            self.open_btn.config(state='normal')
            self.quit_btn.config(state='normal')

        def open(self, event=None, url=None):
            """opens a browser window on the local machine"""
            url = url or self.server.url
            try:
                import webbrowser
                webbrowser.open(url)
            except ImportError: # pre-webbrowser.py compatibility
                if sys.platform == 'win32':
                    os.system('start "%s"' % url)
                elif sys.platform == 'mac':
                    try:
                        import ic
                        ic.launchurl(url)
                    except ImportError: pass
                else:
                    rc = os.system('netscape -remote "openURL(%s)" &' % url)
                    if rc: os.system('netscape "%s" &' % url)

        def quit(self, event=None):
            if self.server:
                self.server.quit = 1
            self.window.quit()

    import Tkinter
    try:
        gui = GUI(Tkinter.Tk(), host, port)
        Tkinter.mainloop()
    except KeyboardInterrupt:
        pass

# --- command-line interface: ----------------------------------------------

def cli(argv):
    """Command-line interface (looks at argv to decide what to do)."""
    import getopt
    class BadUsage(Exception): pass

    try:
        opts, args = getopt.getopt(argv[1:], 'gp:r:h:', 
            ['gui', 'port=', 'repository='])
        for opt, val in opts:
            if opt in ('-g', '--gui'):
                options.start_gui = 1
            elif opt in ('-r', '--repository'):
                if options.repositories: # option may be used more than once:
                    num = len(options.repositories.keys())+1
                    symbolic_name = "Repository"+str(num)
                    options.repositories[symbolic_name] = val
                else:
                    options.repositories["Development"] = val
            elif opt in ('-p', '--port'):
                try:
                    options.port = int(val)
                except ValueError:
                    raise BadUsage
            elif opt in ('-h', '--host'):
                options.host = val
        if options.start_gui:
            gui(options.host, options.port)
            return
        elif options.port:
            def ready(server):
                print 'server ready at %s' % server.url
            serve(options.host, options.port, ready)
            return
        raise BadUsage
    except (getopt.error, BadUsage):
        cmd = sys.argv[0]
        port = options.port
        host = options.host
        print """ViewCVS standalone - a simple standalone HTTP-Server

Usage: %(cmd)s [ <options> ]

Available Options:

-h <host> or --host=<host>
    Start the HTTP server listening on <host>.
    Defaults to %(host)s.  You need to provide the
    hostname, if you want to access the standalone server
    from remote.

-p <port> or --port=<port>
    Start an HTTP server on the given port.
    Default port is %(port)d.

-r <path> or --repository=<path>
    Specify path for a CVS repository.  May be used more than once.
    If you don't have a CVS repository at /home/cvsroot you will need to
    use this option or you have to install first and edit viewcvs.conf.

-g or --gui
    Pop up a graphical interface for serving and testing ViewCVS.
    Note: This requires you start %(cmd)s with a valid X11 display
    connection on Unix/Linux systems.

""" % locals()

if __name__ == '__main__':
    options = Options()
    cli(sys.argv)