📄 viewcvs.py
字号:
# -*-python-*-## Copyright (C) 1999-2001 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:# Greg Stein, PO Box 760, Palo Alto, CA, 94302# gstein@lyra.org, http://viewcvs.sourceforge.net/## -----------------------------------------------------------------------## viewcvs: View CVS repositories via a web browser## -----------------------------------------------------------------------## This software is based on "cvsweb" by Henner Zeller (which is, in turn,# derived from software by Bill Fenner, with additional modifications by# Henrik Nordstrom and Ken Coar). The cvsweb distribution can be found# on Zeller's site:# http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/## -----------------------------------------------------------------------#__version__ = '0.9.2'########################################################################### INSTALL-TIME CONFIGURATION## These values will be set during the installation process. During# development, they will remain None.#CONF_PATHNAME = None########################################################################## this comes from our library; measure the startup timeimport debugdebug.t_start('startup')debug.t_start('imports')# standard modules that we know are in the path or builtinimport sysimport osimport cgiimport stringimport urllibimport mimetypesimport timeimport reimport statimport struct# these modules come from our library (the stub has set up the path)import compatimport configimport popenimport eztimport acceptdebug.t_end('imports')#########################################################################checkout_magic_path = '*checkout*'# According to RFC 1738 the '~' character is unsafe in URLs.# But for compatibility with URLs bookmarked with older releases of ViewCVS:oldstyle_checkout_magic_path = '~checkout~'docroot_magic_path = '*docroot*'viewcvs_mime_type = 'text/vnd.viewcvs-markup'# put here the variables we need in order to hold our state - they will be# added (with their current value) to any link/query string you construct_sticky_vars = ( 'cvsroot', 'hideattic', 'sortby', 'logsort', 'diff_format', 'only_with_tag', 'search', )# regex used to move from a file to a directory_re_up_path = re.compile('(Attic/)?[^/]+$')_EOF_FILE = 'end of file entries' # no more entries for this RCS file_EOF_LOG = 'end of log' # hit the true EOF on the pipe_EOF_ERROR = 'error message found' # rlog issued an error_FILE_HAD_ERROR = 'could not read file'_UNREADABLE_MARKER = '//UNREADABLE-MARKER//'# for reading/writing between a couple descriptorsCHUNK_SIZE = 8192# if your rlog doesn't use 77 '=' characters, then this must changeLOG_END_MARKER = '=' * 77 + '\n'ENTRY_END_MARKER = '-' * 28 + '\n'# global configuration:cfg = None # see belowif CONF_PATHNAME: # installed g_install_dir = os.path.dirname(CONF_PATHNAME)else: # development directories g_install_dir = os.pardir # typically, ".."class Request: def __init__(self): where = os.environ.get('PATH_INFO', '') # clean it up. this removes duplicate '/' characters and any that may # exist at the front or end of the path. parts = filter(None, string.split(where, '/')) self.has_checkout_magic = 0 self.has_docroot_magic = 0 # does it have a magic prefix? if parts: if parts[0] in (checkout_magic_path, oldstyle_checkout_magic_path): self.has_checkout_magic = 1 del parts[0] elif parts[0] == docroot_magic_path: self.has_docroot_magic = 1 del parts[0] # put it back together where = string.join(parts, '/') script_name = os.environ['SCRIPT_NAME'] ### clean this up? if where: url = script_name + '/' + urllib.quote(where) else: url = script_name self.where = where self.script_name = script_name self.url = url if parts: self.module = parts[0] else: self.module = None self.browser = os.environ.get('HTTP_USER_AGENT', 'unknown') # in lynx, it it very annoying to have two links # per file, so disable the link at the icon # in this case: self.no_file_links = string.find(self.browser, 'Lynx') != -1 # newer browsers accept gzip content encoding # and state this in a header # (netscape did always but didn't state it) # It has been reported that these # braindamaged MS-Internet Explorers claim that they # accept gzip .. but don't in fact and # display garbage then :-/ self.may_compress = ( ( string.find(os.environ.get('HTTP_ACCEPT_ENCODING', ''), 'gzip') != -1 or string.find(self.browser, 'Mozilla/3') != -1) and string.find(self.browser, 'MSIE') == -1 ) # parse the query params into a dictionary (and use defaults) query_dict = default_settings.copy() for name, values in cgi.parse().items(): query_dict[name] = values[0] # set up query strings, prefixed by question marks and ampersands query = sticky_query(query_dict) if query: self.qmark_query = '?' + query self.amp_query = '&' + query else: self.qmark_query = '' self.amp_query = '' self.query_dict = query_dict # set up the CVS repository to use self.cvsrep = query_dict.get('cvsroot', cfg.general.default_root) try: self.cvsroot = cfg.general.cvs_roots[self.cvsrep] except KeyError: if query_dict.has_key('cvsroot'): error("Repository cvsroot %s not configured in viewcvs.conf" % self.cvsrep, "404 Repository not found") else: error("The settings of 'cvs_roots' and " "default_root=%s are misconfigured in the viewcvs.conf file." % self.cvsrep) self.full_name = self.cvsroot + '/' + where # process the Accept-Language: header hal = os.environ.get('HTTP_ACCEPT_LANGUAGE') self.lang_selector = accept.language(hal) self.language = self.lang_selector.select_from(cfg.general.languages) # load the key/value files, given the selected language self.kv = cfg.load_kv_files(self.language) def setup_mime_type_info(self): if cfg.general.mime_types_file: mimetypes.init([cfg.general.mime_types_file]) self.mime_type, self.encoding = mimetypes.guess_type(self.where) if not self.mime_type: self.mime_type = 'text/plain' self.default_viewable = cfg.options.allow_markup and \ is_viewable(self.mime_type)class LogHeader: "Hold state from the header portion of an 'rlog' output." def __init__(self, filename, head=None, branch=None, taginfo=None): self.filename = filename self.head = head self.branch = branch self.taginfo = taginfoclass LogEntry: "Hold state for each revision entry in an 'rlog' output." def __init__(self, rev, date, author, state, changed, log): self.rev = rev self.date = date self.author = author self.state = state self.changed = changed self.log = logdef redirect(location): print 'Status: 301 Moved' print 'Location:', location print print 'This document is located <a href="%s">here</a>.' % location sys.exit(0)def error(msg, status='500 Internal Server Error'): print 'Status:', status print print msg sys.exit(0)def generate_page(request, tname, data): # allow per-language template selection if request: tname = string.replace(tname, '%lang%', request.language) else: tname = string.replace(tname, '%lang%', 'en') debug.t_start('ezt-parse') template = ezt.Template(os.path.join(g_install_dir, tname)) debug.t_end('ezt-parse') template.generate(sys.stdout, data)_header_sent = 0def http_header(content_type='text/html'): global _header_sent if _header_sent: return print 'Content-Type:', content_type print _header_sent = 1def html_footer(request): ### would be nice to have a "standard" set of data available to all ### templates. should move that to the request ob, probably data = { 'cfg' : cfg, 'vsn' : __version__, } if request: data['kv'] = request.kv # generate the footer generate_page(request, cfg.templates.footer, data)def sticky_query(dict): sticky_dict = { } for varname in _sticky_vars: value = dict.get(varname) if value is not None and value != default_settings.get(varname, ''): sticky_dict[varname] = value return compat.urlencode(sticky_dict)def toggle_query(query_dict, which, value=None): dict = query_dict.copy() if value is None: dict[which] = not dict[which] else: dict[which] = value query = sticky_query(dict) if query: return '?' + query return ''def clickable_path(request, path, leaf_is_link, leaf_is_file, drop_leaf): s = '<a href="%s/%s#dirlist">[%s]</a>' % \ (request.script_name, request.qmark_query, request.cvsrep) parts = filter(None, string.split(path, '/')) if drop_leaf: del parts[-1] leaf_is_link = 1 leaf_is_file = 0 where = '' for i in range(len(parts)): where = where + '/' + parts[i] is_leaf = i == len(parts) - 1 if not is_leaf or leaf_is_link: if is_leaf and leaf_is_file: slash = '' else: slash = '/' ### should we be encoding/quoting the URL stuff? (probably...) s = s + ' / <a href="%s%s%s%s#dirlist">%s</a>' % \ (request.script_name, where, slash, request.qmark_query, parts[i]) else: s = s + ' / ' + parts[i] return sdef prep_tags(query_dict, file_url, tags): links = [ ] for tag in tags: href = file_url + toggle_query(query_dict, 'only_with_tag', tag) links.append(_item(name=tag, href=href)) return linksdef is_viewable(mime_type): return mime_type[:5] == 'text/' or mime_type[:6] == 'image/'_re_rewrite_url = re.compile('((http|ftp)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*(#([-a-zA-Z0-9%.~:_]+)?)?)')_re_rewrite_email = re.compile('([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})')def htmlify(html): html = cgi.escape(html) html = re.sub(_re_rewrite_url, r'<a href="\1">\1</a>', html) html = re.sub(_re_rewrite_email, r'<a href="mailto:\1">\1</a>', html) return htmldef format_log(log): s = htmlify(log[:cfg.options.short_log_len]) if len(log) > cfg.options.short_log_len: s = s + '...' return sdef download_url(request, url, revision, mime_type): if cfg.options.checkout_magic and mime_type != viewcvs_mime_type: url = '%s/%s/%s/%s' % \ (request.script_name, checkout_magic_path, os.path.dirname(request.where), url) url = url + '?rev=' + revision + request.amp_query if mime_type: return url + '&content-type=' + mime_type return url_time_desc = { 1 : 'second', 60 : 'minute', 3600 : 'hour', 86400 : 'day', 604800 : 'week', 2628000 : 'month', 31536000 : 'year', }def get_time_text(request, interval, num): "Get some time text, possibly internationalized." ### some languages have even harder pluralization rules. we'll have to ### deal with those on demand if num == 0: return '' text = _time_desc[interval] if num == 1: attr = text + '_singular' fmt = '%d ' + text else: attr = text + '_plural' fmt = '%d ' + text + 's' try: fmt = getattr(request.kv.i18n.time, attr) except AttributeError: pass return fmt % numdef little_time(request): try: return request.kv.i18n.time.little_time except AttributeError: return 'very little time'def html_time(request, secs, extended=0): secs = long(time.time()) - secs if secs < 2: return little_time(request) breaks = _time_desc.keys() breaks.sort() i = 0 while i < len(breaks): if secs < 2 * breaks[i]: break i = i + 1 value = breaks[i - 1] s = get_time_text(request, value, secs / value) if extended and i > 1: secs = secs % value value = breaks[i - 2] ext = get_time_text(request, value, secs / value) if ext: ### this is not i18n compatible. pass on it for now s = s + ', ' + ext return sdef nav_header_data(request, path, filename, rev): return { 'nav_path' : clickable_path(request, path, 1, 0, 0), 'path' : path, 'filename' : filename,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -