#!/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 <port>" to start an HTTP server on a given port
on the local machine to generate ViewVC web pages.
"""
__author__ = "Peter Funk <pf@artcom-gmbh.de>"
__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("""<html>
<head>
<meta http-equiv="refresh" content="1; URL=%s">
</head>
<body>
<h1>Redirection to <a href="%s">ViewVC</a></h1>
Wait a second. You will be automatically redirected to <b>ViewVC</b>.
If this doesn't work, please click on the link above.
</body>
</html>
""" % 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('<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(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(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(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):
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>, --host=<host>:
Start the HTTP server listening on <host>. You need to provide
the hostname if you want to access the standalone server from a
remote machine. [default: %(host)s]
-p <port>, --port=<port>:
Start an HTTP server on the given port. [default: %(port)d]
-r <path>, --repository=<path>:
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 <path>, --script-alias=<path>:
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)
syntax highlighted by Code2HTML, v. 0.9.1