#!/usr/bin/env python # -*-python-*- # # Copyright (C) 1999-2006 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 ViewVC # distribution or at http://viewvc.org/license-1.html. # # For more information, visit http://viewvc.org/ # # ----------------------------------------------------------------------- """Run "standalone.py -p " to start an HTTP server on a given port on the local machine to generate ViewVC web pages. """ __author__ = "Peter Funk " __date__ = "11 November 2001" __version__ = "$Revision: 1288 $" __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 # # These values will be set during the installation process. During # development, they will remain None. # LIBRARY_DIR = None CONF_PATHNAME = None import sys import os import os.path 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.insert(0, os.path.abspath(os.path.join(sys.argv[0], "../../lib"))) import sapi import viewvc import compat; compat.for_standalone() 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' script_alias = 'viewvc' # --- 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 ViewVC_Handler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): """Serve a GET request.""" if not self.path or self.path == "/": self.redirect() elif self.is_viewvc(): try: self.run_viewvc() except IOError: # ignore IOError: [Errno 32] Broken pipe pass else: self.send_error(404) def do_POST(self): """Serve a POST request.""" if self.is_viewvc(): self.run_viewvc() else: self.send_error(501, "Can only POST to %s" % (options.script_alias)) def is_viewvc(self): """Check whether self.path is, or is a child of, the ScriptAlias""" if self.path == '/' + options.script_alias: return 1 if self.path[:len(options.script_alias)+2] == \ '/' + options.script_alias + '/': return 1 if self.path[:len(options.script_alias)+2] == \ '/' + options.script_alias + '?': return 1 return 0 def redirect(self): """redirect the browser to the viewvc URL""" new_url = self.server.url + options.script_alias + '/' self.send_response(301, "Moved (redirection follows)") self.send_header("Content-type", "text/html") self.send_header("Location", new_url) self.end_headers() self.wfile.write("""

Redirection to ViewVC

Wait a second. You will be automatically redirected to ViewVC. If this doesn't work, please click on the link above. """ % tuple([new_url]*2)) def run_viewvc(self): """This is a quick and dirty cut'n'rape from Python's standard library module CGIHTTPServer.""" scriptname = '/' + options.script_alias assert string.find(self.path, scriptname) == 0 viewvc_url = self.server.url[:-1] + scriptname rest = self.path[len(scriptname):] 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 env['HTTP_HOST'] = self.server.address[0] 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.dup2(self.wfile.fileno(), 1) sys.stdin = self.rfile viewvc.main(StandaloneServer(self), cfg) finally: sys.argv = save_argv sys.stdin = save_stdin sys.stdout.flush() if sys.platform != "win32": os.dup2(save_realstdout, 1) os.close(save_realstdout) sys.stdout = save_stdout sys.stderr = save_stderr except SystemExit, status: self.log_error("ViewVC exit status %s", str(status)) else: self.log_error("ViewVC exited ok") class ViewVC_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) ViewVC_Server.handler = ViewVC_Handler try: # XXX Move this code out of this function. # Early loading of configuration here. Used to # allow tinkering with some configuration settings: handle_config() if options.repositories: cfg.general.default_root = "Development" cfg.general.cvs_roots.update(options.repositories) elif cfg.general.cvs_roots.has_key("Development") and \ not os.path.isdir(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 docroot location cfg.options.docroot = None # if cvsnt isn't found, fall back to rcs if (cfg.conf_path is None and cfg.general.cvsnt_exe_path): import popen cvsnt_works = 0 try: fp = popen.popen(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: cfg.cvsnt_exe_path = None ViewVC_Server(host, port, callback).serve_until_quit() except (KeyboardInterrupt, select.error): pass print 'server stopped' def handle_config(): global cfg cfg = viewvc.load_config(CONF_PATHNAME) # --- 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('ViewVC 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: handle_config() if not LIBRARY_DIR: 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(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(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(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(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(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(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('', 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(cfg.templates.directory) self.dirtemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.dirtemplate_svar) self.dirtemplate_entry.bind('', 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(cfg.templates.log) self.logtemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.logtemplate_svar) self.logtemplate_entry.bind('', 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(cfg.templates.query) self.querytemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.querytemplate_svar) self.querytemplate_entry.bind('', 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): cfg.options.use_cvsgraph = self.cvsgraph_ivar.get() def toggle_use_enscript(self, event=None): cfg.options.use_enscript = self.enscript_ivar.get() def toggle_use_localtime(self, event=None): cfg.options.use_localtime = self.use_localtime_ivar.get() def toggle_subdirmod(self, event=None): cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get() def toggle_useresearch(self, event=None): cfg.options.use_re_search = self.useresearch_ivar.get() def set_use_pagesize(self, event=None): cfg.options.use_pagesize = self.use_pagesize_ivar.get() def set_templates_log(self, event=None): cfg.templates.log = self.logtemplate_svar.get() def set_templates_directory(self, event=None): cfg.templates.directory = self.dirtemplate_svar.get() def set_templates_query(self, event=None): 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='ViewVC 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:s:', ['gui', 'port=', 'repository=', 'script-alias=']) 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 elif opt in ('-s', '--script-alias'): options.script_alias = \ string.join(filter(None, string.split(val, '/')), '/') if options.start_gui: gui(options.host, options.port) return elif options.port: def ready(server): print 'server ready at %s%s' % (server.url, options.script_alias) serve(options.host, options.port, ready) return raise BadUsage except (getopt.error, BadUsage): cmd = os.path.basename(sys.argv[0]) port = options.port host = options.host script_alias = options.script_alias print """ViewVC standalone - a simple standalone HTTP-Server Usage: %(cmd)s [OPTIONS] Available Options: -h , --host=: Start the HTTP server listening on . You need to provide the hostname if you want to access the standalone server from a remote machine. [default: %(host)s] -p , --port=: Start an HTTP server on the given port. [default: %(port)d] -r , --repository=: Specify a path for a CVS repository. Repository definitions are typically read from the viewvc.conf file, if available. This option may be used more than once. -s , --script-alias=: Specify the ScriptAlias, the artificial path location that at which ViewVC appears to be located. For example, if your ScriptAlias is "cgi-bin/viewvc", then ViewVC will appear to be accessible at the URL "http://%(host)s:%(port)s/cgi-bin/viewvc". [default: %(script_alias)s] -g, --gui: Pop up a graphical interface for serving and testing ViewVC. NOTE: this requires a valid X11 display connection. """ % locals() if __name__ == '__main__': options = Options() cli(sys.argv)