mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fd44add349 | ||
![]() |
44528c0b7e | ||
![]() |
11f594b7ed | ||
![]() |
8a9719a5f8 | ||
![]() |
7ca272ab88 | ||
![]() |
d13e98ef34 | ||
![]() |
ba99093be8 | ||
![]() |
9171ad07a8 | ||
![]() |
cd03714ecd | ||
![]() |
df09de394d | ||
![]() |
f46fed363e | ||
![]() |
b41bd99aa3 | ||
![]() |
c9710b9ba2 | ||
![]() |
5ca27eee4c | ||
![]() |
caf33e7862 | ||
![]() |
1470ccd12f | ||
![]() |
b476f4a882 | ||
![]() |
eacb04ee26 | ||
![]() |
2910bc4a22 | ||
![]() |
ae049f281b | ||
![]() |
7e3baffea4 | ||
![]() |
c40b88fb8b | ||
![]() |
c7fc7d8885 | ||
![]() |
68336ffcdf | ||
![]() |
70db664ec6 | ||
![]() |
e88fcaebce | ||
![]() |
e2c82d9341 | ||
![]() |
3844161f7d | ||
![]() |
9da5df779a | ||
![]() |
f9d697db0c | ||
![]() |
8432cb1c1d | ||
![]() |
c8718c00cd | ||
![]() |
d71c208540 | ||
![]() |
830c6ba76c | ||
![]() |
e9a4e3bbe2 | ||
![]() |
62f37986d5 | ||
![]() |
a2b579b5ac | ||
![]() |
46d8987848 | ||
![]() |
73209134c9 | ||
![]() |
8e9831d6ec | ||
![]() |
83f6a7c7d5 | ||
![]() |
acbf138a6e | ||
![]() |
e872681988 | ||
![]() |
27430791cb | ||
![]() |
e72b133665 | ||
![]() |
3ea9aec854 | ||
![]() |
a807605e31 | ||
![]() |
d3ed0a9cb5 | ||
![]() |
77f1d6f2e4 | ||
![]() |
d59be9459d | ||
![]() |
1a8c6ac15b |
46
CHANGES
46
CHANGES
@@ -1,3 +1,49 @@
|
||||
Version 0.9.4 (released 17-Aug-2005)
|
||||
|
||||
* security fix: omit forbidden/hidden modules from query results.
|
||||
|
||||
Version 0.9.3 (released 17-May-2005)
|
||||
|
||||
* security fix: disallow bad "content-type" input [CAN-2004-1062]
|
||||
* security fix: disallow bad "sortby" and "cvsroot" input [CAN-2002-0771]
|
||||
* security fix: omit forbidden/hidden modules from tarballs [CAN-2004-0915]
|
||||
|
||||
Version 0.9.2 (released 15-Jan-2001)
|
||||
|
||||
* fix redirects to Attic for diffs
|
||||
* fix diffs that have no changes (causing an infinite loop)
|
||||
|
||||
Version 0.9.1 (released 26-Dec-2001)
|
||||
|
||||
* fix a problem with some syntax in ndiff.py which isn't compatible
|
||||
with Python 1.5.2 (causing problems at install time)
|
||||
* remove a debug statement left in the code which continues to
|
||||
append lines to /tmp/log
|
||||
|
||||
Version 0.9 (released 23-Dec-2001)
|
||||
|
||||
* create templates for the rest of the pages: markup pages, graphs,
|
||||
annotation, and diff.
|
||||
* add multiple language support and dynamic selection based on the
|
||||
Accept-Language request header
|
||||
* add support for key/value files to provide a way for user-defined
|
||||
variables within templates
|
||||
* add optional regex searching for file contents
|
||||
* add new templates for the navigation header and the footer
|
||||
* EZT changes:
|
||||
- add formatting into print directives
|
||||
- add parameters to [include] directives
|
||||
- relax what can go in double quotes
|
||||
- [include] directives are now relative to the current template
|
||||
- throw an exception for unclosed blocks
|
||||
* changes to standalone.py: add flag for regex search
|
||||
* add more help pages
|
||||
* change installer to optionally show diffs
|
||||
* fix to log.ezt and log_table.ezt to select "Side by Side" properly
|
||||
* create dir_alternate.ezt for the flipped rev/name links
|
||||
* various UI tweaks for the directory pages
|
||||
|
||||
|
||||
Version 0.8 (released 10-Dec-2001)
|
||||
|
||||
* add EZT templating mechanism for generating output pages
|
||||
|
@@ -136,6 +136,75 @@ forbidden =
|
||||
# forbidden = !xml, x*, !*
|
||||
#
|
||||
|
||||
#
|
||||
# This option provides a mechanism for custom key/value pairs to be
|
||||
# available to templates. These are stored in key/value files (KV files).
|
||||
#
|
||||
# Pathnames to the KV files are listed here, specified as absolute paths
|
||||
# or relative to this configuration file. The kV files follow the same
|
||||
# format as this configuration file. It may have multiple, user-defined
|
||||
# sections, and user-defined options in those sections. These are all
|
||||
# placed into a structure available to the templates as:
|
||||
#
|
||||
# kv.SECTION.OPTION
|
||||
#
|
||||
# Note that an option name can be dotted. For example:
|
||||
#
|
||||
# [my_images]
|
||||
# logos.small = /images/small-logo.png
|
||||
# logos.big = /images/big-logo.png
|
||||
#
|
||||
# Templates can use these with a directive like: [kv.my_images.logos.small]
|
||||
#
|
||||
# Note that sections across multiple files will be merged. If two files
|
||||
# have a [my_images] section, then the options will be merged together.
|
||||
# If two files have the same option name in a section, then one will
|
||||
# overwrite the other (it is unspecified regarding which "wins").
|
||||
#
|
||||
# To further categorize the KV files, and how the values are provided to
|
||||
# the templates, a KV file name may be annotated with an additional level
|
||||
# of dotted naming. For example:
|
||||
#
|
||||
# kv_files = [asf]kv/images.conf
|
||||
#
|
||||
# Assuming the same section as above, the template would refer to an image
|
||||
# using [kv.asf.my_images.logos.small]
|
||||
#
|
||||
# Lastly, it is possible to use %lang% in the filenames to specify a
|
||||
# substitution of the selected language-tag.
|
||||
#
|
||||
kv_files =
|
||||
|
||||
# example:
|
||||
# kv_files = kv/file1.conf, kv/file2.conf, [i18n]kv/%lang%_data.conf
|
||||
#
|
||||
|
||||
#
|
||||
# The languages available to ViewCVS. There are several i18n mechanisms
|
||||
# available:
|
||||
#
|
||||
# 1) using key/value extension system and reading KV files based on
|
||||
# the selected language
|
||||
# 2) GNU gettext to substitute text in the templates
|
||||
# 3) using different templates, based on the selected language
|
||||
#
|
||||
# ### NOTE: at the moment, the GNU gettext style is not implemented
|
||||
#
|
||||
# This option is a comma-separated list of language-tag values. The first
|
||||
# language-tag listed is the default language, and will be used if an
|
||||
# Accept-Language header is not present in the request, or none of the
|
||||
# user's requested languages are available. If there are ties on the
|
||||
# selection of a language, then the first to appear in the list is chosen.
|
||||
#
|
||||
languages = en-us
|
||||
|
||||
# other examples:
|
||||
#
|
||||
# languages = en-us, de
|
||||
# languages = en-us, en-gb, de
|
||||
# languages = de, fr, en-us
|
||||
#
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[templates]
|
||||
|
||||
@@ -143,13 +212,31 @@ forbidden =
|
||||
# The templates are specified relative to the configuration file. Absolute
|
||||
# paths may be used, if you want to keep these elsewhere.
|
||||
#
|
||||
# If %lang% occurs in the pathname, then the selected language will be
|
||||
# substituted.
|
||||
#
|
||||
# Note: the selected language is defined by the "languages" item in the
|
||||
# [general] section, and based on the request's Accept-Language
|
||||
# header.
|
||||
#
|
||||
|
||||
query = templates/query.ezt
|
||||
footer = templates/footer.ezt
|
||||
diff = templates/diff.ezt
|
||||
graph = templates/graph.ezt
|
||||
annotate = templates/annotate.ezt
|
||||
markup = templates/markup.ezt
|
||||
|
||||
directory = templates/directory.ezt
|
||||
# For an alternate form, where the first column displays a revision number
|
||||
# and brings you to the log view (and the filename displays the HEAD), then
|
||||
# you may use this template:
|
||||
# directory = templates/dir_alternate.ezt
|
||||
|
||||
log = templates/log.ezt
|
||||
# There is also a new style table based alternative template available.
|
||||
# You might want to try it out:
|
||||
# For a log view where the revisions are displayed in a table, you may
|
||||
# want to try this template:
|
||||
# log = templates/log_table.ezt
|
||||
query = templates/query.ezt
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[cvsdb]
|
||||
@@ -162,59 +249,6 @@ query = templates/query.ezt
|
||||
#readonly_passwd =
|
||||
#row_limit = 1000
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[images]
|
||||
#
|
||||
# All images are defined with three values: URL, WIDTH, HEIGHT
|
||||
#
|
||||
|
||||
#
|
||||
# these icons represent a back-pointer, a directory (folder), and a file.
|
||||
# they are normally available in a standard Apache distribution, along
|
||||
# with larger versions if these are too small for you.
|
||||
#
|
||||
back_icon = /icons/small/back.gif, 16, 16
|
||||
dir_icon = /icons/small/dir.gif, 16, 16
|
||||
file_icon = /icons/small/text.gif, 16, 16
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[colors]
|
||||
|
||||
# background color of log entry in markup
|
||||
markup_log = #ffffff
|
||||
|
||||
# The following six colors are used together:
|
||||
# color of change-section headings in a diff (default turquoise)
|
||||
diff_heading = #99cccc
|
||||
|
||||
# color of "empty" lines (default light gray)
|
||||
diff_empty = #cccccc
|
||||
|
||||
# removed lines (default light red)
|
||||
diff_remove = #ffaaaa
|
||||
|
||||
# changed lines (default light yellow)
|
||||
diff_change = #ffff77
|
||||
|
||||
# added lines (default light green)
|
||||
diff_add = #aaffaa
|
||||
|
||||
# empty lines in a change block (if one part smaller is than the other;
|
||||
# default is a greyish yellow: the color should match the hue of 'diff_change')
|
||||
diff_dark_change = #eeee77
|
||||
|
||||
# navigation header (in diff screen, file view, annotation, etc)
|
||||
nav_header = #9999ee
|
||||
|
||||
# color of text on most pages
|
||||
text = #000000
|
||||
|
||||
# color of standard background
|
||||
background = #ffffff
|
||||
|
||||
# color of alternate background (diffs, file view, annotations, etc)
|
||||
alt_background = #eeeeee
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[options]
|
||||
### DOC
|
||||
@@ -227,9 +261,9 @@ alt_background = #eeeeee
|
||||
# log Sort by log message
|
||||
sort_by = file
|
||||
|
||||
# hide_attic: Hide or show files in Attic
|
||||
# 1 Hide files in Attic
|
||||
# 0 Show files in Attic
|
||||
# hide_attic: Hide or show the contents of the Attic subdirectory
|
||||
# 1 Hide dead files inside Attic subdir
|
||||
# 0 Show the files which are inside the Attic subdir
|
||||
hide_attic = 1
|
||||
|
||||
# log_sort: Sort order for CVS logs
|
||||
@@ -251,14 +285,6 @@ diff_format = h
|
||||
# 0 Show CVSROOT directory
|
||||
hide_cvsroot = 1
|
||||
|
||||
# hide_non_readable: Don't show entries which cannot be read
|
||||
# 1 Hide non-readable entries
|
||||
# 0 Show non-readble entries
|
||||
hide_non_readable = 1
|
||||
|
||||
# Show author of last change
|
||||
show_author = 1
|
||||
|
||||
# set to 1 to make lines break at spaces,
|
||||
# set to 0 to make no-break lines,
|
||||
# set to a positive integer to make the lines cut at that length
|
||||
@@ -291,14 +317,6 @@ allow_markup = 1
|
||||
# [make sure to have gzip in the path]
|
||||
allow_compress = 1
|
||||
|
||||
# Make use of javascript functions to skip the need for submitting a form.
|
||||
# For example, this way you can select one of your CVS roots without
|
||||
# pressing 'Go' (... if you have more than one CVSROOT defined)
|
||||
use_java_script = 1
|
||||
|
||||
# open Download-Links in another window
|
||||
open_extern_window = 1
|
||||
|
||||
# If you have files which automatically refers to other files
|
||||
# (such as HTML) then this allows you to browse the checked
|
||||
# out files as if outside CVS.
|
||||
@@ -314,15 +332,6 @@ checkout_magic = 1
|
||||
# Enable this if you like the feature, but don't rely on correct results.
|
||||
show_subdir_lastmod = 0
|
||||
|
||||
# The next flag defines the meaning of clicking on either a filename or
|
||||
# the revision number of that file in the directory view. if 0 then
|
||||
# the traditional behavior applies: clicking on the name takes you to the
|
||||
# CVS log page, where clicking on the revision number displays that revision.
|
||||
# If the flag is set to 1 then both columns will be swapped and the meaning
|
||||
# of clicking is also exchanged. This should be more intuitive to new users.
|
||||
# The classic setting is default:
|
||||
flip_links_in_dirview = 0
|
||||
|
||||
# show a portion of the most recent log entry in directory listings
|
||||
show_logs = 1
|
||||
|
||||
@@ -392,6 +401,31 @@ cvsgraph_path =
|
||||
#
|
||||
cvsgraph_conf = <VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf
|
||||
|
||||
#
|
||||
# Set to enable regular expression search of all files in a directory
|
||||
#
|
||||
# WARNING:
|
||||
#
|
||||
# Enabling this option can consume HUGE amounts of server time. A
|
||||
# "checkout" must be performed on *each* file in a directory, and
|
||||
# the result needs to be searched for a match against the regular
|
||||
# expression.
|
||||
#
|
||||
#
|
||||
# SECURITY WARNING: Denial Of Service
|
||||
#
|
||||
# Since a user can enter the regular expression, it is possible for
|
||||
# them to enter an expression with many alternatives and a lot of
|
||||
# backtracking. Executing that search over thousands of lines over
|
||||
# dozens of files can easily tie up a server for a long period of
|
||||
# time.
|
||||
#
|
||||
# This option should only be used on sites with trusted users. It is
|
||||
# highly inadvisable to use this on a public site.
|
||||
#
|
||||
use_re_search = 0
|
||||
# use_re_search = 1
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[vhosts]
|
||||
### DOC
|
||||
|
190
lib/accept.py
Normal file
190
lib/accept.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# -*-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/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# accept.py: parse/handle the various Accept headers from the client
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def language(hdr):
|
||||
"Parse an Accept-Language header."
|
||||
|
||||
# parse the header, storing results in a _LanguageSelector object
|
||||
return _parse(hdr, _LanguageSelector())
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
_re_token = re.compile(r'\s*([^\s;,"]+|"[^"]*")+\s*')
|
||||
_re_param = re.compile(r';\s*([^;,"]+|"[^"]*")+\s*')
|
||||
_re_split_param = re.compile(r'([^\s=])\s*=\s*(.*)')
|
||||
|
||||
def _parse(hdr, result):
|
||||
# quick exit for empty or not-supplied header
|
||||
if not hdr:
|
||||
return result
|
||||
|
||||
pos = 0
|
||||
while pos < len(hdr):
|
||||
name = _re_token.match(hdr, pos)
|
||||
if not name:
|
||||
raise AcceptParseError()
|
||||
a = result.item_class(string.lower(name.group(1)))
|
||||
pos = name.end()
|
||||
while 1:
|
||||
# are we looking at a parameter?
|
||||
match = _re_param.match(hdr, pos)
|
||||
if not match:
|
||||
break
|
||||
param = match.group(1)
|
||||
pos = match.end()
|
||||
|
||||
# split up the pieces of the parameter
|
||||
match = _re_split_param.match(param)
|
||||
if not match:
|
||||
# the "=" was probably missing
|
||||
continue
|
||||
|
||||
pname = string.lower(match.group(1))
|
||||
if pname == 'q' or pname == 'qs':
|
||||
try:
|
||||
a.quality = float(match.group(2))
|
||||
except ValueError:
|
||||
# bad float literal
|
||||
pass
|
||||
elif pname == 'level':
|
||||
try:
|
||||
a.level = float(match.group(2))
|
||||
except ValueError:
|
||||
# bad float literal
|
||||
pass
|
||||
elif pname == 'charset':
|
||||
a.charset = string.lower(match.group(2))
|
||||
|
||||
result.append(a)
|
||||
if hdr[pos:pos+1] == ',':
|
||||
pos = pos + 1
|
||||
|
||||
return result
|
||||
|
||||
class _AcceptItem:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.quality = 1.0
|
||||
self.level = 0.0
|
||||
self.charset = ''
|
||||
|
||||
def __str__(self):
|
||||
s = self.name
|
||||
if self.quality != 1.0:
|
||||
s = '%s;q=%.3f' % (s, self.quality)
|
||||
if self.level != 0.0:
|
||||
s = '%s;level=%.3f' % (s, self.level)
|
||||
if self.charset:
|
||||
s = '%s;charset=%s' % (s, self.charset)
|
||||
return s
|
||||
|
||||
class _LanguageRange(_AcceptItem):
|
||||
def matches(self, tag):
|
||||
"Match the tag against self. Returns the qvalue, or None if non-matching."
|
||||
if tag == self.name:
|
||||
return self.quality
|
||||
|
||||
# are we a prefix of the available language-tag
|
||||
name = self.name + '-'
|
||||
if tag[:len(name)] == name:
|
||||
return self.quality
|
||||
return None
|
||||
|
||||
class _LanguageSelector:
|
||||
item_class = _LanguageRange
|
||||
|
||||
def __init__(self):
|
||||
self.requested = [ ]
|
||||
|
||||
def select_from(self, avail):
|
||||
"""Select one of the available choices based on the request.
|
||||
|
||||
Note: if there isn't a match, then the first available choice is
|
||||
considered the default.
|
||||
|
||||
avail is a list of language-tag strings of available languages
|
||||
"""
|
||||
|
||||
# tuples of (qvalue, language-tag)
|
||||
matches = [ ]
|
||||
|
||||
# try matching all pairs of desired vs available, recording the
|
||||
# resulting qvalues. we also need to record the longest language-range
|
||||
# that matches since the most specific range "wins"
|
||||
for tag in avail:
|
||||
longest = 0
|
||||
final = 0.0
|
||||
|
||||
# check this tag against the requests from the user
|
||||
for want in self.requested:
|
||||
qvalue = want.matches(tag)
|
||||
#print 'have %s. want %s. qvalue=%s' % (tag, want.name, qvalue)
|
||||
if qvalue is not None and len(want.name) > longest:
|
||||
# we have a match and it is longer than any we may have had
|
||||
final = qvalue
|
||||
longest = len(want.name)
|
||||
|
||||
# a non-zero qvalue is a potential match
|
||||
if final:
|
||||
matches.append((final, tag))
|
||||
|
||||
# if we have any matches, then look at the highest qvalue
|
||||
if matches:
|
||||
matches.sort()
|
||||
qvalue, tag = matches[-1]
|
||||
|
||||
if len(matches) >= 2 and matches[-2][0] == qvalue:
|
||||
#print "non-deterministic choice", avail
|
||||
pass
|
||||
|
||||
# if the qvalue is non-zero, then we have a valid match
|
||||
if qvalue:
|
||||
return tag
|
||||
# the qvalue is zero (non-match). drop thru to return the default
|
||||
|
||||
# return the default language tag
|
||||
return avail[0]
|
||||
|
||||
def append(self, item):
|
||||
self.requested.append(item)
|
||||
|
||||
class AcceptParseError(Exception):
|
||||
pass
|
||||
|
||||
def _test():
|
||||
s = language('en')
|
||||
assert s.select_from(['en']) == 'en'
|
||||
assert s.select_from(['en', 'de']) == 'en'
|
||||
assert s.select_from(['de', 'en']) == 'en'
|
||||
|
||||
s = language('fr, de;q=0.9, en-gb;q=0.7, en;q=0.6, en-gb-foo;q=0.8')
|
||||
assert s.select_from(['en']) == 'en'
|
||||
assert s.select_from(['en-gb-foo']) == 'en-gb-foo'
|
||||
assert s.select_from(['de', 'fr']) == 'fr'
|
||||
assert s.select_from(['de', 'en-gb']) == 'de'
|
||||
assert s.select_from(['en-gb', 'en-gb-foo']) == 'en-gb-foo'
|
||||
assert s.select_from(['en-bar']) == 'en-bar'
|
||||
assert s.select_from(['en-gb-bar', 'en-gb-foo']) == 'en-gb-foo'
|
||||
|
||||
# non-deterministic. en-gb;q=0.7 matches both avail tags.
|
||||
#assert s.select_from(['en-gb-bar', 'en-gb']) == 'en-gb'
|
@@ -41,8 +41,9 @@ import fnmatch
|
||||
#########################################################################
|
||||
|
||||
class Config:
|
||||
_sections = ('general', 'images', 'options', 'colors', 'cvsdb', 'templates')
|
||||
_force_multi_value = ('cvs_roots', 'forbidden', 'disable_enscript_lang')
|
||||
_sections = ('general', 'options', 'cvsdb', 'templates')
|
||||
_force_multi_value = ('cvs_roots', 'forbidden', 'disable_enscript_lang',
|
||||
'languages', 'kv_files')
|
||||
|
||||
def __init__(self):
|
||||
for section in self._sections:
|
||||
@@ -51,6 +52,8 @@ class Config:
|
||||
def load_config(self, fname, vhost=None):
|
||||
this_dir = os.path.dirname(sys.argv[0])
|
||||
pathname = os.path.join(this_dir, fname)
|
||||
self.base = os.path.dirname(pathname)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read(pathname)
|
||||
|
||||
@@ -61,12 +64,41 @@ class Config:
|
||||
if vhost and parser.has_section('vhosts'):
|
||||
self._process_vhost(parser, vhost)
|
||||
|
||||
def load_kv_files(self, language):
|
||||
kv = _sub_config()
|
||||
|
||||
for fname in self.general.kv_files:
|
||||
if fname[0] == '[':
|
||||
idx = string.index(fname, ']')
|
||||
parts = string.split(fname[1:idx], '.')
|
||||
fname = string.strip(fname[idx+1:])
|
||||
else:
|
||||
parts = [ ]
|
||||
fname = string.replace(fname, '%lang%', language)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read(os.path.join(self.base, fname))
|
||||
for section in parser.sections():
|
||||
for option in parser.options(section):
|
||||
full_name = parts + [section]
|
||||
ob = kv
|
||||
for name in full_name:
|
||||
try:
|
||||
ob = getattr(ob, name)
|
||||
except AttributeError:
|
||||
c = _sub_config()
|
||||
setattr(ob, name, c)
|
||||
ob = c
|
||||
setattr(ob, option, parser.get(section, option))
|
||||
|
||||
return kv
|
||||
|
||||
def _process_section(self, parser, section, subcfg_name):
|
||||
sc = getattr(self, subcfg_name)
|
||||
|
||||
for opt in parser.options(section):
|
||||
value = parser.get(section, opt)
|
||||
if opt in self._force_multi_value or subcfg_name == 'images':
|
||||
if opt in self._force_multi_value:
|
||||
value = map(string.strip, filter(None, string.split(value, ',')))
|
||||
else:
|
||||
try:
|
||||
@@ -120,10 +152,17 @@ class Config:
|
||||
self.general.address = '<a href="mailto:user@insert.your.domain.here">No CVS admin address has been configured</a>'
|
||||
self.general.main_title = 'CVS Repository'
|
||||
self.general.forbidden = ()
|
||||
self.general.kv_files = [ ]
|
||||
self.general.languages = ['en-us']
|
||||
|
||||
self.templates.directory = 'templates/directory.ezt'
|
||||
self.templates.log = 'templates/log.ezt'
|
||||
self.templates.query = 'templates/query.ezt'
|
||||
self.templates.footer = 'templates/footer.ezt'
|
||||
self.templates.diff = 'templates/diff.ezt'
|
||||
self.templates.graph = 'templates/graph.ezt'
|
||||
self.templates.annotate = 'templates/annotate.ezt'
|
||||
self.templates.markup = 'templates/markup.ezt'
|
||||
|
||||
self.cvsdb.enabled = 0
|
||||
self.cvsdb.host = ''
|
||||
@@ -134,33 +173,11 @@ class Config:
|
||||
self.cvsdb.readonly_passwd = ''
|
||||
self.cvsdb.row_limit = 1000
|
||||
|
||||
self.images.back_icon = "/icons/small/back.gif", 16, 16
|
||||
self.images.dir_icon = "/icons/small/dir.gif", 16, 16
|
||||
self.images.file_icon = "/icons/small/text.gif", 16, 16
|
||||
|
||||
self.colors.markup_log = "#ffffff"
|
||||
|
||||
self.colors.diff_heading = "#99cccc"
|
||||
self.colors.diff_empty = "#cccccc"
|
||||
# trafic light methaphor:
|
||||
self.colors.diff_remove = "#ffaaaa" # red
|
||||
self.colors.diff_change = "#ffff77" # yellow/orange
|
||||
self.colors.diff_add = "#aaffaa" # green
|
||||
self.colors.diff_dark_change = "#eeee77" # meets hue of diff_change
|
||||
|
||||
self.colors.nav_header = "#9999ee"
|
||||
|
||||
self.colors.text = "#000000"
|
||||
self.colors.background = "#ffffff"
|
||||
self.colors.alt_background = "#eeeeee"
|
||||
|
||||
self.options.sort_by = 'file'
|
||||
self.options.hide_attic = 1
|
||||
self.options.log_sort = 'date'
|
||||
self.options.diff_format = 'h'
|
||||
self.options.hide_cvsroot = 1
|
||||
self.options.hide_non_readable = 1
|
||||
self.options.show_author = 1
|
||||
self.options.hr_breakable = 1
|
||||
self.options.hr_funout = 1
|
||||
self.options.hr_ignore_white = 1
|
||||
@@ -168,11 +185,8 @@ class Config:
|
||||
self.options.allow_annotate = 1
|
||||
self.options.allow_markup = 1
|
||||
self.options.allow_compress = 1
|
||||
self.options.use_java_script = 1
|
||||
self.options.open_extern_window = 1
|
||||
self.options.checkout_magic = 1
|
||||
self.options.show_subdir_lastmod = 0
|
||||
self.options.flip_links_in_dirview = 0
|
||||
self.options.show_logs = 1
|
||||
self.options.show_log_in_markup = 1
|
||||
self.options.py2html_path = '.'
|
||||
@@ -186,6 +200,7 @@ class Config:
|
||||
self.options.use_cvsgraph = 0
|
||||
self.options.cvsgraph_path = ''
|
||||
self.options.cvsgraph_conf = "<VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf"
|
||||
self.options.use_re_search = 0
|
||||
|
||||
def is_forbidden(self, module):
|
||||
if not module:
|
||||
@@ -201,13 +216,7 @@ class Config:
|
||||
return default
|
||||
|
||||
class _sub_config:
|
||||
def get_image(self, which):
|
||||
text = '[%s]' % string.upper(which)
|
||||
path, width, height = getattr(self, which)
|
||||
if path:
|
||||
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
|
||||
(path, text, width, height)
|
||||
return text
|
||||
pass
|
||||
|
||||
if not hasattr(sys, 'hexversion'):
|
||||
# Python 1.5 or 1.5.1. fix the syntax for ConfigParser options.
|
||||
|
11
lib/debug.py
11
lib/debug.py
@@ -12,19 +12,22 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# Note: a t_start/t_end pair consumes about 0.00005 seconds on a P3/700.
|
||||
# the lambda form (when debugging is disabled) should be even faster.
|
||||
#
|
||||
|
||||
if 0:
|
||||
|
||||
import time
|
||||
|
||||
_stack = [ ]
|
||||
_timers = { }
|
||||
_times = { }
|
||||
|
||||
def t_start():
|
||||
_stack.append(time.time())
|
||||
def t_start(which):
|
||||
_timers[which] = time.time()
|
||||
|
||||
def t_end(which):
|
||||
t = time.time() - _stack.pop()
|
||||
t = time.time() - _timers[which]
|
||||
if _times.has_key(which):
|
||||
_times[which] = _times[which] + t
|
||||
else:
|
||||
|
786
lib/difflib.py
Executable file
786
lib/difflib.py
Executable file
@@ -0,0 +1,786 @@
|
||||
#! /usr/bin/env python
|
||||
# Backported to Python 1.5.2 for the ViewCVS project by pf@artcom-gmbh.de
|
||||
# 24-Dec-2001, original version "stolen" from Python-2.1.1
|
||||
"""
|
||||
Module difflib -- helpers for computing deltas between objects.
|
||||
|
||||
Function get_close_matches(word, possibilities, n=3, cutoff=0.6):
|
||||
|
||||
Use SequenceMatcher to return list of the best "good enough" matches.
|
||||
|
||||
word is a sequence for which close matches are desired (typically a
|
||||
string).
|
||||
|
||||
possibilities is a list of sequences against which to match word
|
||||
(typically a list of strings).
|
||||
|
||||
Optional arg n (default 3) is the maximum number of close matches to
|
||||
return. n must be > 0.
|
||||
|
||||
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
|
||||
that don't score at least that similar to word are ignored.
|
||||
|
||||
The best (no more than n) matches among the possibilities are returned
|
||||
in a list, sorted by similarity score, most similar first.
|
||||
|
||||
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
|
||||
['apple', 'ape']
|
||||
>>> import keyword
|
||||
>>> get_close_matches("wheel", keyword.kwlist)
|
||||
['while']
|
||||
>>> get_close_matches("apple", keyword.kwlist)
|
||||
[]
|
||||
>>> get_close_matches("accept", keyword.kwlist)
|
||||
['except']
|
||||
|
||||
Class SequenceMatcher
|
||||
|
||||
SequenceMatcher is a flexible class for comparing pairs of sequences of any
|
||||
type, so long as the sequence elements are hashable. The basic algorithm
|
||||
predates, and is a little fancier than, an algorithm published in the late
|
||||
1980's by Ratcliff and Obershelp under the hyperbolic name "gestalt pattern
|
||||
matching". The basic idea is to find the longest contiguous matching
|
||||
subsequence that contains no "junk" elements (R-O doesn't address junk).
|
||||
The same idea is then applied recursively to the pieces of the sequences to
|
||||
the left and to the right of the matching subsequence. This does not yield
|
||||
minimal edit sequences, but does tend to yield matches that "look right"
|
||||
to people.
|
||||
|
||||
Example, comparing two strings, and considering blanks to be "junk":
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x == " ",
|
||||
... "private Thread currentThread;",
|
||||
... "private volatile Thread currentThread;")
|
||||
>>>
|
||||
|
||||
.ratio() returns a float in [0, 1], measuring the "similarity" of the
|
||||
sequences. As a rule of thumb, a .ratio() value over 0.6 means the
|
||||
sequences are close matches:
|
||||
|
||||
>>> print round(s.ratio(), 3)
|
||||
0.866
|
||||
>>>
|
||||
|
||||
If you're only interested in where the sequences match,
|
||||
.get_matching_blocks() is handy:
|
||||
|
||||
>>> for block in s.get_matching_blocks():
|
||||
... print "a[%d] and b[%d] match for %d elements" % block
|
||||
a[0] and b[0] match for 8 elements
|
||||
a[8] and b[17] match for 6 elements
|
||||
a[14] and b[23] match for 15 elements
|
||||
a[29] and b[38] match for 0 elements
|
||||
|
||||
Note that the last tuple returned by .get_matching_blocks() is always a
|
||||
dummy, (len(a), len(b), 0), and this is the only case in which the last
|
||||
tuple element (number of elements matched) is 0.
|
||||
|
||||
If you want to know how to change the first sequence into the second, use
|
||||
.get_opcodes():
|
||||
|
||||
>>> for opcode in s.get_opcodes():
|
||||
... print "%6s a[%d:%d] b[%d:%d]" % opcode
|
||||
equal a[0:8] b[0:8]
|
||||
insert a[8:8] b[8:17]
|
||||
equal a[8:14] b[17:23]
|
||||
equal a[14:29] b[23:38]
|
||||
|
||||
See Tools/scripts/ndiff.py for a fancy human-friendly file differencer,
|
||||
which uses SequenceMatcher both to view files as sequences of lines, and
|
||||
lines as sequences of characters.
|
||||
|
||||
See also function get_close_matches() in this module, which shows how
|
||||
simple code building on SequenceMatcher can be used to do useful work.
|
||||
|
||||
Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||
case. SequenceMatcher is quadratic time for the worst case and has
|
||||
expected-case behavior dependent in a complicated way on how many
|
||||
elements the sequences have in common; best case time is linear.
|
||||
|
||||
SequenceMatcher methods:
|
||||
|
||||
__init__(isjunk=None, a='', b='')
|
||||
Construct a SequenceMatcher.
|
||||
|
||||
Optional arg isjunk is None (the default), or a one-argument function
|
||||
that takes a sequence element and returns true iff the element is junk.
|
||||
None is equivalent to passing "lambda x: 0", i.e. no elements are
|
||||
considered to be junk. For example, pass
|
||||
lambda x: x in " \\t"
|
||||
if you're comparing lines as sequences of characters, and don't want to
|
||||
synch up on blanks or hard tabs.
|
||||
|
||||
Optional arg a is the first of two sequences to be compared. By
|
||||
default, an empty string. The elements of a must be hashable.
|
||||
|
||||
Optional arg b is the second of two sequences to be compared. By
|
||||
default, an empty string. The elements of b must be hashable.
|
||||
|
||||
set_seqs(a, b)
|
||||
Set the two sequences to be compared.
|
||||
|
||||
>>> s = SequenceMatcher()
|
||||
>>> s.set_seqs("abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
|
||||
set_seq1(a)
|
||||
Set the first sequence to be compared.
|
||||
|
||||
The second sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq1("bcde")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against many
|
||||
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
|
||||
each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq2().
|
||||
|
||||
set_seq2(b)
|
||||
Set the second sequence to be compared.
|
||||
|
||||
The first sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq2("abcd")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against many
|
||||
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
|
||||
each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq1().
|
||||
|
||||
find_longest_match(alo, ahi, blo, bhi)
|
||||
Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
|
||||
If isjunk is not defined:
|
||||
|
||||
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
alo <= i <= i+k <= ahi
|
||||
blo <= j <= j+k <= bhi
|
||||
and for all (i',j',k') meeting those conditions,
|
||||
k >= k'
|
||||
i <= i'
|
||||
and if i == i', j <= j'
|
||||
|
||||
In other words, of all maximal matching blocks, return one that starts
|
||||
earliest in a, and of all those maximal matching blocks that start
|
||||
earliest in a, return the one that starts earliest in b.
|
||||
|
||||
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(0, 4, 5)
|
||||
|
||||
If isjunk is defined, first the longest matching block is determined as
|
||||
above, but with the additional restriction that no junk element appears
|
||||
in the block. Then that block is extended as far as possible by
|
||||
matching (only) junk elements on both sides. So the resulting block
|
||||
never matches on junk except as identical junk happens to be adjacent
|
||||
to an "interesting" match.
|
||||
|
||||
Here's the same example as before, but considering blanks to be junk.
|
||||
That prevents " abcd" from matching the " abcd" at the tail end of the
|
||||
second sequence directly. Instead only the "abcd" can match, and
|
||||
matches the leftmost "abcd" in the second sequence:
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(1, 0, 4)
|
||||
|
||||
If no blocks match, return (alo, blo, 0).
|
||||
|
||||
>>> s = SequenceMatcher(None, "ab", "c")
|
||||
>>> s.find_longest_match(0, 2, 0, 1)
|
||||
(0, 0, 0)
|
||||
|
||||
get_matching_blocks()
|
||||
Return list of triples describing matching subsequences.
|
||||
|
||||
Each triple is of the form (i, j, n), and means that
|
||||
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in i
|
||||
and in j.
|
||||
|
||||
The last triple is a dummy, (len(a), len(b), 0), and is the only triple
|
||||
with n==0.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abxcd", "abcd")
|
||||
>>> s.get_matching_blocks()
|
||||
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
|
||||
|
||||
get_opcodes()
|
||||
Return list of 5-tuples describing how to turn a into b.
|
||||
|
||||
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple has
|
||||
i1 == j1 == 0, and remaining tuples have i1 == the i2 from the tuple
|
||||
preceding it, and likewise for j1 == the previous j2.
|
||||
|
||||
The tags are strings, with these meanings:
|
||||
|
||||
'replace': a[i1:i2] should be replaced by b[j1:j2]
|
||||
'delete': a[i1:i2] should be deleted.
|
||||
Note that j1==j2 in this case.
|
||||
'insert': b[j1:j2] should be inserted at a[i1:i1].
|
||||
Note that i1==i2 in this case.
|
||||
'equal': a[i1:i2] == b[j1:j2]
|
||||
|
||||
>>> a = "qabxcd"
|
||||
>>> b = "abycdf"
|
||||
>>> s = SequenceMatcher(None, a, b)
|
||||
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
|
||||
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
|
||||
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
|
||||
delete a[0:1] (q) b[0:0] ()
|
||||
equal a[1:3] (ab) b[0:2] (ab)
|
||||
replace a[3:4] (x) b[2:3] (y)
|
||||
equal a[4:6] (cd) b[3:5] (cd)
|
||||
insert a[6:6] () b[5:6] (f)
|
||||
|
||||
ratio()
|
||||
Return a measure of the sequences' similarity (float in [0,1]).
|
||||
|
||||
Where T is the total number of elements in both sequences, and M is the
|
||||
number of matches, this is 2,0*M / T. Note that this is 1 if the
|
||||
sequences are identical, and 0 if they have nothing in common.
|
||||
|
||||
.ratio() is expensive to compute if you haven't already computed
|
||||
.get_matching_blocks() or .get_opcodes(), in which case you may want to
|
||||
try .quick_ratio() or .real_quick_ratio() first to get an upper bound.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.quick_ratio()
|
||||
0.75
|
||||
>>> s.real_quick_ratio()
|
||||
1.0
|
||||
|
||||
quick_ratio()
|
||||
Return an upper bound on .ratio() relatively quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute.
|
||||
|
||||
real_quick_ratio():
|
||||
Return an upper bound on ratio() very quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute than either .ratio() or .quick_ratio().
|
||||
"""
|
||||
|
||||
TRACE = 0
|
||||
|
||||
class SequenceMatcher:
|
||||
def __init__(self, isjunk=None, a='', b=''):
|
||||
"""Construct a SequenceMatcher.
|
||||
|
||||
Optional arg isjunk is None (the default), or a one-argument
|
||||
function that takes a sequence element and returns true iff the
|
||||
element is junk. None is equivalent to passing "lambda x: 0", i.e.
|
||||
no elements are considered to be junk. For example, pass
|
||||
lambda x: x in " \\t"
|
||||
if you're comparing lines as sequences of characters, and don't
|
||||
want to synch up on blanks or hard tabs.
|
||||
|
||||
Optional arg a is the first of two sequences to be compared. By
|
||||
default, an empty string. The elements of a must be hashable. See
|
||||
also .set_seqs() and .set_seq1().
|
||||
|
||||
Optional arg b is the second of two sequences to be compared. By
|
||||
default, an empty string. The elements of b must be hashable. See
|
||||
also .set_seqs() and .set_seq2().
|
||||
"""
|
||||
|
||||
# Members:
|
||||
# a
|
||||
# first sequence
|
||||
# b
|
||||
# second sequence; differences are computed as "what do
|
||||
# we need to do to 'a' to change it into 'b'?"
|
||||
# b2j
|
||||
# for x in b, b2j[x] is a list of the indices (into b)
|
||||
# at which x appears; junk elements do not appear
|
||||
# b2jhas
|
||||
# b2j.has_key
|
||||
# fullbcount
|
||||
# for x in b, fullbcount[x] == the number of times x
|
||||
# appears in b; only materialized if really needed (used
|
||||
# only for computing quick_ratio())
|
||||
# matching_blocks
|
||||
# a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
|
||||
# ascending & non-overlapping in i and in j; terminated by
|
||||
# a dummy (len(a), len(b), 0) sentinel
|
||||
# opcodes
|
||||
# a list of (tag, i1, i2, j1, j2) tuples, where tag is
|
||||
# one of
|
||||
# 'replace' a[i1:i2] should be replaced by b[j1:j2]
|
||||
# 'delete' a[i1:i2] should be deleted
|
||||
# 'insert' b[j1:j2] should be inserted
|
||||
# 'equal' a[i1:i2] == b[j1:j2]
|
||||
# isjunk
|
||||
# a user-supplied function taking a sequence element and
|
||||
# returning true iff the element is "junk" -- this has
|
||||
# subtle but helpful effects on the algorithm, which I'll
|
||||
# get around to writing up someday <0.9 wink>.
|
||||
# DON'T USE! Only __chain_b uses this. Use isbjunk.
|
||||
# isbjunk
|
||||
# for x in b, isbjunk(x) == isjunk(x) but much faster;
|
||||
# it's really the has_key method of a hidden dict.
|
||||
# DOES NOT WORK for x in a!
|
||||
|
||||
self.isjunk = isjunk
|
||||
self.a = self.b = None
|
||||
self.set_seqs(a, b)
|
||||
|
||||
def set_seqs(self, a, b):
|
||||
"""Set the two sequences to be compared.
|
||||
|
||||
>>> s = SequenceMatcher()
|
||||
>>> s.set_seqs("abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
"""
|
||||
|
||||
self.set_seq1(a)
|
||||
self.set_seq2(b)
|
||||
|
||||
def set_seq1(self, a):
|
||||
"""Set the first sequence to be compared.
|
||||
|
||||
The second sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq1("bcde")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against
|
||||
many sequences, use .set_seq2(S) once and call .set_seq1(x)
|
||||
repeatedly for each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq2().
|
||||
"""
|
||||
|
||||
if a is self.a:
|
||||
return
|
||||
self.a = a
|
||||
self.matching_blocks = self.opcodes = None
|
||||
|
||||
def set_seq2(self, b):
|
||||
"""Set the second sequence to be compared.
|
||||
|
||||
The first sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq2("abcd")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against
|
||||
many sequences, use .set_seq2(S) once and call .set_seq1(x)
|
||||
repeatedly for each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq1().
|
||||
"""
|
||||
|
||||
if b is self.b:
|
||||
return
|
||||
self.b = b
|
||||
self.matching_blocks = self.opcodes = None
|
||||
self.fullbcount = None
|
||||
self.__chain_b()
|
||||
|
||||
# For each element x in b, set b2j[x] to a list of the indices in
|
||||
# b where x appears; the indices are in increasing order; note that
|
||||
# the number of times x appears in b is len(b2j[x]) ...
|
||||
# when self.isjunk is defined, junk elements don't show up in this
|
||||
# map at all, which stops the central find_longest_match method
|
||||
# from starting any matching block at a junk element ...
|
||||
# also creates the fast isbjunk function ...
|
||||
# note that this is only called when b changes; so for cross-product
|
||||
# kinds of matches, it's best to call set_seq2 once, then set_seq1
|
||||
# repeatedly
|
||||
|
||||
def __chain_b(self):
|
||||
# Because isjunk is a user-defined (not C) function, and we test
|
||||
# for junk a LOT, it's important to minimize the number of calls.
|
||||
# Before the tricks described here, __chain_b was by far the most
|
||||
# time-consuming routine in the whole module! If anyone sees
|
||||
# Jim Roskind, thank him again for profile.py -- I never would
|
||||
# have guessed that.
|
||||
# The first trick is to build b2j ignoring the possibility
|
||||
# of junk. I.e., we don't call isjunk at all yet. Throwing
|
||||
# out the junk later is much cheaper than building b2j "right"
|
||||
# from the start.
|
||||
b = self.b
|
||||
self.b2j = b2j = {}
|
||||
self.b2jhas = b2jhas = b2j.has_key
|
||||
for i in xrange(len(b)):
|
||||
elt = b[i]
|
||||
if b2jhas(elt):
|
||||
b2j[elt].append(i)
|
||||
else:
|
||||
b2j[elt] = [i]
|
||||
|
||||
# Now b2j.keys() contains elements uniquely, and especially when
|
||||
# the sequence is a string, that's usually a good deal smaller
|
||||
# than len(string). The difference is the number of isjunk calls
|
||||
# saved.
|
||||
isjunk, junkdict = self.isjunk, {}
|
||||
if isjunk:
|
||||
for elt in b2j.keys():
|
||||
if isjunk(elt):
|
||||
junkdict[elt] = 1 # value irrelevant; it's a set
|
||||
del b2j[elt]
|
||||
|
||||
# Now for x in b, isjunk(x) == junkdict.has_key(x), but the
|
||||
# latter is much faster. Note too that while there may be a
|
||||
# lot of junk in the sequence, the number of *unique* junk
|
||||
# elements is probably small. So the memory burden of keeping
|
||||
# this dict alive is likely trivial compared to the size of b2j.
|
||||
self.isbjunk = junkdict.has_key
|
||||
|
||||
def find_longest_match(self, alo, ahi, blo, bhi):
|
||||
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
|
||||
If isjunk is not defined:
|
||||
|
||||
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
alo <= i <= i+k <= ahi
|
||||
blo <= j <= j+k <= bhi
|
||||
and for all (i',j',k') meeting those conditions,
|
||||
k >= k'
|
||||
i <= i'
|
||||
and if i == i', j <= j'
|
||||
|
||||
In other words, of all maximal matching blocks, return one that
|
||||
starts earliest in a, and of all those maximal matching blocks that
|
||||
start earliest in a, return the one that starts earliest in b.
|
||||
|
||||
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(0, 4, 5)
|
||||
|
||||
If isjunk is defined, first the longest matching block is
|
||||
determined as above, but with the additional restriction that no
|
||||
junk element appears in the block. Then that block is extended as
|
||||
far as possible by matching (only) junk elements on both sides. So
|
||||
the resulting block never matches on junk except as identical junk
|
||||
happens to be adjacent to an "interesting" match.
|
||||
|
||||
Here's the same example as before, but considering blanks to be
|
||||
junk. That prevents " abcd" from matching the " abcd" at the tail
|
||||
end of the second sequence directly. Instead only the "abcd" can
|
||||
match, and matches the leftmost "abcd" in the second sequence:
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(1, 0, 4)
|
||||
|
||||
If no blocks match, return (alo, blo, 0).
|
||||
|
||||
>>> s = SequenceMatcher(None, "ab", "c")
|
||||
>>> s.find_longest_match(0, 2, 0, 1)
|
||||
(0, 0, 0)
|
||||
"""
|
||||
|
||||
# CAUTION: stripping common prefix or suffix would be incorrect.
|
||||
# E.g.,
|
||||
# ab
|
||||
# acab
|
||||
# Longest matching block is "ab", but if common prefix is
|
||||
# stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||
# strip, so ends up claiming that ab is changed to acab by
|
||||
# inserting "ca" in the middle. That's minimal but unintuitive:
|
||||
# "it's obvious" that someone inserted "ac" at the front.
|
||||
# Windiff ends up at the same place as diff, but by pairing up
|
||||
# the unique 'b's and then matching the first two 'a's.
|
||||
|
||||
a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
|
||||
besti, bestj, bestsize = alo, blo, 0
|
||||
# find longest junk-free match
|
||||
# during an iteration of the loop, j2len[j] = length of longest
|
||||
# junk-free match ending with a[i-1] and b[j]
|
||||
j2len = {}
|
||||
nothing = []
|
||||
for i in xrange(alo, ahi):
|
||||
# look at all instances of a[i] in b; note that because
|
||||
# b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||
j2lenget = j2len.get
|
||||
newj2len = {}
|
||||
for j in b2j.get(a[i], nothing):
|
||||
# a[i] matches b[j]
|
||||
if j < blo:
|
||||
continue
|
||||
if j >= bhi:
|
||||
break
|
||||
k = newj2len[j] = j2lenget(j-1, 0) + 1
|
||||
if k > bestsize:
|
||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||
j2len = newj2len
|
||||
|
||||
# Now that we have a wholly interesting match (albeit possibly
|
||||
# empty!), we may as well suck up the matching junk on each
|
||||
# side of it too. Can't think of a good reason not to, and it
|
||||
# saves post-processing the (possibly considerable) expense of
|
||||
# figuring out what to do with it. In the case of an empty
|
||||
# interesting match, this is clearly the right thing to do,
|
||||
# because no other kind of match is possible in the regions.
|
||||
while besti > alo and bestj > blo and \
|
||||
isbjunk(b[bestj-1]) and \
|
||||
a[besti-1] == b[bestj-1]:
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
while besti+bestsize < ahi and bestj+bestsize < bhi and \
|
||||
isbjunk(b[bestj+bestsize]) and \
|
||||
a[besti+bestsize] == b[bestj+bestsize]:
|
||||
bestsize = bestsize + 1
|
||||
|
||||
if TRACE:
|
||||
print "get_matching_blocks", alo, ahi, blo, bhi
|
||||
print " returns", besti, bestj, bestsize
|
||||
return besti, bestj, bestsize
|
||||
|
||||
def get_matching_blocks(self):
|
||||
"""Return list of triples describing matching subsequences.
|
||||
|
||||
Each triple is of the form (i, j, n), and means that
|
||||
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||
i and in j.
|
||||
|
||||
The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||
triple with n==0.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abxcd", "abcd")
|
||||
>>> s.get_matching_blocks()
|
||||
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
|
||||
"""
|
||||
|
||||
if self.matching_blocks is not None:
|
||||
return self.matching_blocks
|
||||
self.matching_blocks = []
|
||||
la, lb = len(self.a), len(self.b)
|
||||
self.__helper(0, la, 0, lb, self.matching_blocks)
|
||||
self.matching_blocks.append( (la, lb, 0) )
|
||||
if TRACE:
|
||||
print '*** matching blocks', self.matching_blocks
|
||||
return self.matching_blocks
|
||||
|
||||
# builds list of matching blocks covering a[alo:ahi] and
|
||||
# b[blo:bhi], appending them in increasing order to answer
|
||||
|
||||
def __helper(self, alo, ahi, blo, bhi, answer):
|
||||
i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
|
||||
# a[alo:i] vs b[blo:j] unknown
|
||||
# a[i:i+k] same as b[j:j+k]
|
||||
# a[i+k:ahi] vs b[j+k:bhi] unknown
|
||||
if k:
|
||||
if alo < i and blo < j:
|
||||
self.__helper(alo, i, blo, j, answer)
|
||||
answer.append(x)
|
||||
if i+k < ahi and j+k < bhi:
|
||||
self.__helper(i+k, ahi, j+k, bhi, answer)
|
||||
|
||||
def get_opcodes(self):
|
||||
"""Return list of 5-tuples describing how to turn a into b.
|
||||
|
||||
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||
has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||
tuple preceding it, and likewise for j1 == the previous j2.
|
||||
|
||||
The tags are strings, with these meanings:
|
||||
|
||||
'replace': a[i1:i2] should be replaced by b[j1:j2]
|
||||
'delete': a[i1:i2] should be deleted.
|
||||
Note that j1==j2 in this case.
|
||||
'insert': b[j1:j2] should be inserted at a[i1:i1].
|
||||
Note that i1==i2 in this case.
|
||||
'equal': a[i1:i2] == b[j1:j2]
|
||||
|
||||
>>> a = "qabxcd"
|
||||
>>> b = "abycdf"
|
||||
>>> s = SequenceMatcher(None, a, b)
|
||||
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
|
||||
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
|
||||
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
|
||||
delete a[0:1] (q) b[0:0] ()
|
||||
equal a[1:3] (ab) b[0:2] (ab)
|
||||
replace a[3:4] (x) b[2:3] (y)
|
||||
equal a[4:6] (cd) b[3:5] (cd)
|
||||
insert a[6:6] () b[5:6] (f)
|
||||
"""
|
||||
|
||||
if self.opcodes is not None:
|
||||
return self.opcodes
|
||||
i = j = 0
|
||||
self.opcodes = answer = []
|
||||
for ai, bj, size in self.get_matching_blocks():
|
||||
# invariant: we've pumped out correct diffs to change
|
||||
# a[:i] into b[:j], and the next matching block is
|
||||
# a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||
# out a diff to change a[i:ai] into b[j:bj], pump out
|
||||
# the matching block, and move (i,j) beyond the match
|
||||
tag = ''
|
||||
if i < ai and j < bj:
|
||||
tag = 'replace'
|
||||
elif i < ai:
|
||||
tag = 'delete'
|
||||
elif j < bj:
|
||||
tag = 'insert'
|
||||
if tag:
|
||||
answer.append( (tag, i, ai, j, bj) )
|
||||
i, j = ai+size, bj+size
|
||||
# the list of matching blocks is terminated by a
|
||||
# sentinel with size 0
|
||||
if size:
|
||||
answer.append( ('equal', ai, i, bj, j) )
|
||||
return answer
|
||||
|
||||
def ratio(self):
|
||||
"""Return a measure of the sequences' similarity (float in [0,1]).
|
||||
|
||||
Where T is the total number of elements in both sequences, and
|
||||
M is the number of matches, this is 2,0*M / T.
|
||||
Note that this is 1 if the sequences are identical, and 0 if
|
||||
they have nothing in common.
|
||||
|
||||
.ratio() is expensive to compute if you haven't already computed
|
||||
.get_matching_blocks() or .get_opcodes(), in which case you may
|
||||
want to try .quick_ratio() or .real_quick_ratio() first to get an
|
||||
upper bound.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.quick_ratio()
|
||||
0.75
|
||||
>>> s.real_quick_ratio()
|
||||
1.0
|
||||
"""
|
||||
|
||||
matches = reduce(lambda sum, triple: sum + triple[-1],
|
||||
self.get_matching_blocks(), 0)
|
||||
return 2.0 * matches / (len(self.a) + len(self.b))
|
||||
|
||||
def quick_ratio(self):
|
||||
"""Return an upper bound on ratio() relatively quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute.
|
||||
"""
|
||||
|
||||
# viewing a and b as multisets, set matches to the cardinality
|
||||
# of their intersection; this counts the number of matches
|
||||
# without regard to order, so is clearly an upper bound
|
||||
if self.fullbcount is None:
|
||||
self.fullbcount = fullbcount = {}
|
||||
for elt in self.b:
|
||||
fullbcount[elt] = fullbcount.get(elt, 0) + 1
|
||||
fullbcount = self.fullbcount
|
||||
# avail[x] is the number of times x appears in 'b' less the
|
||||
# number of times we've seen it in 'a' so far ... kinda
|
||||
avail = {}
|
||||
availhas, matches = avail.has_key, 0
|
||||
for elt in self.a:
|
||||
if availhas(elt):
|
||||
numb = avail[elt]
|
||||
else:
|
||||
numb = fullbcount.get(elt, 0)
|
||||
avail[elt] = numb - 1
|
||||
if numb > 0:
|
||||
matches = matches + 1
|
||||
return 2.0 * matches / (len(self.a) + len(self.b))
|
||||
|
||||
def real_quick_ratio(self):
|
||||
"""Return an upper bound on ratio() very quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute than either .ratio() or .quick_ratio().
|
||||
"""
|
||||
|
||||
la, lb = len(self.a), len(self.b)
|
||||
# can't have more matches than the number of elements in the
|
||||
# shorter sequence
|
||||
return 2.0 * min(la, lb) / (la + lb)
|
||||
|
||||
def get_close_matches(word, possibilities, n=3, cutoff=0.6):
|
||||
"""Use SequenceMatcher to return list of the best "good enough" matches.
|
||||
|
||||
word is a sequence for which close matches are desired (typically a
|
||||
string).
|
||||
|
||||
possibilities is a list of sequences against which to match word
|
||||
(typically a list of strings).
|
||||
|
||||
Optional arg n (default 3) is the maximum number of close matches to
|
||||
return. n must be > 0.
|
||||
|
||||
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
|
||||
that don't score at least that similar to word are ignored.
|
||||
|
||||
The best (no more than n) matches among the possibilities are returned
|
||||
in a list, sorted by similarity score, most similar first.
|
||||
|
||||
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
|
||||
['apple', 'ape']
|
||||
>>> import keyword
|
||||
>>> get_close_matches("wheel", keyword.kwlist)
|
||||
['while']
|
||||
>>> get_close_matches("apple", keyword.kwlist)
|
||||
[]
|
||||
>>> get_close_matches("accept", keyword.kwlist)
|
||||
['except']
|
||||
"""
|
||||
|
||||
if not n > 0:
|
||||
raise ValueError("n must be > 0: " + `n`)
|
||||
if not 0.0 <= cutoff <= 1.0:
|
||||
raise ValueError("cutoff must be in [0.0, 1.0]: " + `cutoff`)
|
||||
result = []
|
||||
s = SequenceMatcher()
|
||||
s.set_seq2(word)
|
||||
for x in possibilities:
|
||||
s.set_seq1(x)
|
||||
if s.real_quick_ratio() >= cutoff and \
|
||||
s.quick_ratio() >= cutoff and \
|
||||
s.ratio() >= cutoff:
|
||||
result.append((s.ratio(), x))
|
||||
# Sort by score.
|
||||
result.sort()
|
||||
# Retain only the best n.
|
||||
result = result[-n:]
|
||||
# Move best-scorer to head of list.
|
||||
result.reverse()
|
||||
# Strip scores.
|
||||
# Python 2.x list comprehensions: return [x for score, x in result]
|
||||
return_result = []
|
||||
for score, x in result:
|
||||
return_result.append(x)
|
||||
return return_result
|
||||
|
||||
def _test():
|
||||
import doctest, difflib
|
||||
return doctest.testmod(difflib)
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
153
lib/ezt.py
153
lib/ezt.py
@@ -147,11 +147,15 @@ Directives
|
||||
import string
|
||||
import re
|
||||
from types import StringType, IntType, FloatType
|
||||
import os
|
||||
|
||||
#
|
||||
# This regular expression matches three alternatives:
|
||||
# expr: DIRECTIVE | BRACKET | COMMENT
|
||||
# DIRECTIVE: '[' ('-' | '.' | ' ' | '"' | '/' | alphanum)+ ']
|
||||
# DIRECTIVE: '[' ITEM (whitespace ITEM)* ']
|
||||
# ITEM: STRING | NAME
|
||||
# STRING: '"' (not-slash-or-dquote | '\' anychar)* '"'
|
||||
# NAME: (alphanum | '_' | '-' | '.')+
|
||||
# BRACKET: '[[]'
|
||||
# COMMENT: '[#' not-rbracket* ']'
|
||||
#
|
||||
@@ -160,7 +164,10 @@ from types import StringType, IntType, FloatType
|
||||
# the COMMENT matches are not placed into a group, they are considered a
|
||||
# "splitting" value and simply dropped.
|
||||
#
|
||||
_re_parse = re.compile('(\[[-\w."/ ]+\])|(\[\[\])|\[#[^\]]*\]')
|
||||
_item = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)'
|
||||
_re_parse = re.compile(r'\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % (_item, _item))
|
||||
|
||||
_re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+')
|
||||
|
||||
# block commands and their argument counts
|
||||
_block_cmd_specs = { 'if-any':1, 'if-index':2, 'for':1, 'is':2 }
|
||||
@@ -172,6 +179,12 @@ _block_cmds = _block_cmd_specs.keys()
|
||||
_re_newline = re.compile('[ \t\r\f\v]*\n\\s*')
|
||||
_re_whitespace = re.compile(r'\s\s+')
|
||||
|
||||
# this regex is used to substitute arguments into a value. we split the value,
|
||||
# replace the relevant pieces, and then put it all back together. splitting
|
||||
# will produce a list of: TEXT ( splitter TEXT )*. splitter will be '%' or
|
||||
# an integer.
|
||||
_re_subst = re.compile('%(%|[0-9]+)')
|
||||
|
||||
class Template:
|
||||
|
||||
def __init__(self, fname=None):
|
||||
@@ -198,10 +211,11 @@ class Template:
|
||||
ctx.for_index = { }
|
||||
self._execute(self.program, fp, ctx)
|
||||
|
||||
def _parse_file(self, fname, for_names=None):
|
||||
return self._parse(open(fname, "rt").read(), for_names)
|
||||
def _parse_file(self, fname, for_names=None, file_args=()):
|
||||
return self._parse(open(fname, "rt").read(), for_names, file_args,
|
||||
os.path.dirname(fname))
|
||||
|
||||
def _parse(self, text, for_names=None):
|
||||
def _parse(self, text, for_names=None, file_args=(), base=None):
|
||||
"""text -> string object containing the HTML template.
|
||||
|
||||
This is a private helper function doing the real work for method parse.
|
||||
@@ -232,7 +246,7 @@ class Template:
|
||||
program.append('[')
|
||||
elif piece:
|
||||
# DIRECTIVE is present.
|
||||
args = string.split(piece[1:-1])
|
||||
args = _re_args.findall(piece)
|
||||
cmd = args[0]
|
||||
if cmd == 'else':
|
||||
if len(args) > 1:
|
||||
@@ -256,34 +270,46 @@ class Template:
|
||||
if len(args) > _block_cmd_specs[cmd] + 1:
|
||||
raise ArgCountSyntaxError()
|
||||
### this assumes arg1 is always a ref
|
||||
args[1] = _prepare_ref(args[1], for_names)
|
||||
args[1] = _prepare_ref(args[1], for_names, file_args)
|
||||
|
||||
# handle arg2 for the 'is' command
|
||||
if cmd == 'is':
|
||||
if args[2][0] == '"':
|
||||
# strip the quotes
|
||||
args[2] = args[2][1:-1]
|
||||
else:
|
||||
args[2] = _prepare_ref(args[2], for_names)
|
||||
args[2] = _prepare_ref(args[2], for_names, file_args)
|
||||
elif cmd == 'for':
|
||||
for_names.append(args[1][0])
|
||||
|
||||
# remember the cmd, current pos, args, and a section placeholder
|
||||
stack.append([cmd, len(program), args[1:], None])
|
||||
elif cmd == 'include':
|
||||
if len(args) != 2:
|
||||
raise ArgCountSyntaxError()
|
||||
if args[1][0] in ('"', "'"):
|
||||
if args[1][0] == '"':
|
||||
include_filename = args[1][1:-1]
|
||||
program.extend(self._parse_file(include_filename, for_names))
|
||||
if base:
|
||||
include_filename = os.path.join(base, include_filename)
|
||||
f_args = [ ]
|
||||
for arg in args[2:]:
|
||||
f_args.append(_prepare_ref(arg, for_names, file_args))
|
||||
program.extend(self._parse_file(include_filename, for_names,
|
||||
f_args))
|
||||
else:
|
||||
program.append((self._cmd_include, _prepare_ref(args[1], for_names)))
|
||||
if len(args) != 2:
|
||||
raise ArgCountSyntaxError()
|
||||
program.append((self._cmd_include,
|
||||
(_prepare_ref(args[1], for_names, file_args),
|
||||
base)))
|
||||
else:
|
||||
# implied PRINT command
|
||||
if len(args) > 1:
|
||||
raise ArgCountSyntaxError()
|
||||
program.append((self._cmd_print, _prepare_ref(args[0], for_names)))
|
||||
f_args = [ ]
|
||||
for arg in args:
|
||||
f_args.append(_prepare_ref(arg, for_names, file_args))
|
||||
program.append((self._cmd_format, (f_args[0], f_args[1:])))
|
||||
else:
|
||||
program.append((self._cmd_print,
|
||||
_prepare_ref(args[0], for_names, file_args)))
|
||||
|
||||
if stack:
|
||||
### would be nice to say which blocks...
|
||||
raise UnclosedBlocksError()
|
||||
return program
|
||||
|
||||
def _execute(self, program, fp, ctx):
|
||||
@@ -298,11 +324,38 @@ class Template:
|
||||
step[0](step[1], fp, ctx)
|
||||
|
||||
def _cmd_print(self, valref, fp, ctx):
|
||||
### type check the value
|
||||
fp.write(_get_value(valref, ctx))
|
||||
value = _get_value(valref, ctx)
|
||||
|
||||
def _cmd_include(self, valref, fp, ctx):
|
||||
self._execute(self._parse_file(_get_value(valref, ctx)), fp, ctx)
|
||||
# if the value has a 'read' attribute, then it is a stream: copy it
|
||||
if hasattr(value, 'read'):
|
||||
while 1:
|
||||
chunk = value.read(16384)
|
||||
if not chunk:
|
||||
break
|
||||
fp.write(chunk)
|
||||
else:
|
||||
fp.write(value)
|
||||
|
||||
def _cmd_format(self, (valref, args), fp, ctx):
|
||||
fmt = _get_value(valref, ctx)
|
||||
parts = _re_subst.split(fmt)
|
||||
for i in range(len(parts)):
|
||||
piece = parts[i]
|
||||
if i%2 == 1 and piece != '%':
|
||||
idx = int(piece)
|
||||
if idx < len(args):
|
||||
piece = _get_value(args[idx], ctx)
|
||||
else:
|
||||
piece = '<undef>'
|
||||
fp.write(piece)
|
||||
|
||||
def _cmd_include(self, (valref, base), fp, ctx):
|
||||
fname = _get_value(valref, ctx)
|
||||
if base:
|
||||
fname = os.path.join(base, fname)
|
||||
### note: we don't have the set of for_names to pass into this parse.
|
||||
### I don't think there is anything to do but document it.
|
||||
self._execute(self._parse_file(fname), fp, ctx)
|
||||
|
||||
def _cmd_if_any(self, args, fp, ctx):
|
||||
"If the value is a non-empty string or non-empty list, then T else F."
|
||||
@@ -326,10 +379,9 @@ class Template:
|
||||
self._do_if(value, t_section, f_section, fp, ctx)
|
||||
|
||||
def _cmd_is(self, args, fp, ctx):
|
||||
((valref, value), t_section, f_section) = args
|
||||
if not isinstance(value, StringType):
|
||||
value = _get_value(value, ctx)
|
||||
value = string.lower(_get_value(valref, ctx)) == string.lower(value)
|
||||
((left_ref, right_ref), t_section, f_section) = args
|
||||
value = _get_value(right_ref, ctx)
|
||||
value = string.lower(_get_value(left_ref, ctx)) == string.lower(value)
|
||||
self._do_if(value, t_section, f_section, fp, ctx)
|
||||
|
||||
def _do_if(self, value, t_section, f_section, fp, ctx):
|
||||
@@ -349,20 +401,40 @@ class Template:
|
||||
if isinstance(list, StringType):
|
||||
raise NeedSequenceError()
|
||||
refname = valref[0]
|
||||
ctx.for_index[refname] = [ list, 0 ]
|
||||
for i in range(len(list)):
|
||||
ctx.for_index[refname][1] = i
|
||||
ctx.for_index[refname] = idx = [ list, 0 ]
|
||||
for item in list:
|
||||
self._execute(section, fp, ctx)
|
||||
idx[1] = idx[1] + 1
|
||||
del ctx.for_index[refname]
|
||||
|
||||
def boolean(value):
|
||||
"Return a value suitable for [if-any bool_var] usage in a template."
|
||||
if value:
|
||||
return 'yes'
|
||||
return None
|
||||
|
||||
def _prepare_ref(refname, for_names):
|
||||
|
||||
def _prepare_ref(refname, for_names, file_args):
|
||||
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
|
||||
for_names -> a list of active for sequences.
|
||||
|
||||
Returns a `value reference', a 3-Tupel made out of (refname, start, rest),
|
||||
for fast access later.
|
||||
"""
|
||||
# is the reference a string constant?
|
||||
if refname[0] == '"':
|
||||
return None, refname[1:-1], None
|
||||
|
||||
# if this is an include-argument, then just return the prepared ref
|
||||
if refname[:3] == 'arg':
|
||||
try:
|
||||
idx = int(refname[3:])
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if idx < len(file_args):
|
||||
return file_args[idx]
|
||||
|
||||
parts = string.split(refname, '.')
|
||||
start = parts[0]
|
||||
rest = parts[1:]
|
||||
@@ -384,6 +456,9 @@ def _get_value((refname, start, rest), ctx):
|
||||
for blocks take precedence over data dictionary members with the
|
||||
same name.
|
||||
"""
|
||||
if rest is None:
|
||||
# it was a string constant
|
||||
return start
|
||||
if ctx.for_index.has_key(start):
|
||||
list, idx = ctx.for_index[start]
|
||||
ob = list[idx]
|
||||
@@ -420,7 +495,23 @@ class UnknownReference(Exception):
|
||||
class NeedSequenceError(Exception):
|
||||
pass
|
||||
|
||||
class UnclosedBlocksError(Exception):
|
||||
pass
|
||||
|
||||
# --- standard test environment ---
|
||||
def test_parse():
|
||||
assert _re_parse.split('[a]') == ['', '[a]', None, '']
|
||||
assert _re_parse.split('[a] [b]') == \
|
||||
['', '[a]', None, ' ', '[b]', None, '']
|
||||
assert _re_parse.split('[a c] [b]') == \
|
||||
['', '[a c]', None, ' ', '[b]', None, '']
|
||||
assert _re_parse.split('x [a] y [b] z') == \
|
||||
['x ', '[a]', None, ' y ', '[b]', None, ' z']
|
||||
assert _re_parse.split('[a "b" c "d"]') == \
|
||||
['', '[a "b" c "d"]', None, '']
|
||||
assert _re_parse.split(r'["a \"b[foo]" c.d f]') == \
|
||||
['', '["a \\"b[foo]" c.d f]', None, '']
|
||||
|
||||
def _test(argv):
|
||||
import doctest, ezt
|
||||
verbose = "-v" in argv
|
||||
|
347
lib/ndiff.py
Normal file
347
lib/ndiff.py
Normal file
@@ -0,0 +1,347 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Module ndiff version 1.6.0
|
||||
# Released to the public domain 08-Dec-2000,
|
||||
# by Tim Peters (tim.one@home.com).
|
||||
|
||||
# Backported to Python 1.5.2 for ViewCVS by pf@artcom-gmbh.de, 24-Dec-2001
|
||||
|
||||
# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
|
||||
|
||||
"""ndiff [-q] file1 file2
|
||||
or
|
||||
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
|
||||
|
||||
Print a human-friendly file difference report to stdout. Both inter-
|
||||
and intra-line differences are noted. In the second form, recreate file1
|
||||
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
|
||||
|
||||
In the first form, if -q ("quiet") is not specified, the first two lines
|
||||
of output are
|
||||
|
||||
-: file1
|
||||
+: file2
|
||||
|
||||
Each remaining line begins with a two-letter code:
|
||||
|
||||
"- " line unique to file1
|
||||
"+ " line unique to file2
|
||||
" " line common to both files
|
||||
"? " line not present in either input file
|
||||
|
||||
Lines beginning with "? " attempt to guide the eye to intraline
|
||||
differences, and were not present in either input file. These lines can be
|
||||
confusing if the source files contain tab characters.
|
||||
|
||||
The first file can be recovered by retaining only lines that begin with
|
||||
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
|
||||
|
||||
The second file can be recovered similarly, but by retaining only " " and
|
||||
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
|
||||
recovered by piping the output through
|
||||
|
||||
sed -n '/^[+ ] /s/^..//p'
|
||||
|
||||
See module comments for details and programmatic interface.
|
||||
"""
|
||||
|
||||
__version__ = 1, 6, 1
|
||||
|
||||
# SequenceMatcher tries to compute a "human-friendly diff" between
|
||||
# two sequences (chiefly picturing a file as a sequence of lines,
|
||||
# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm)
|
||||
# diff, the fundamental notion is the longest *contiguous* & junk-free
|
||||
# matching subsequence. That's what catches peoples' eyes. The
|
||||
# Windows(tm) windiff has another interesting notion, pairing up elements
|
||||
# that appear uniquely in each sequence. That, and the method here,
|
||||
# appear to yield more intuitive difference reports than does diff. This
|
||||
# method appears to be the least vulnerable to synching up on blocks
|
||||
# of "junk lines", though (like blank lines in ordinary text files,
|
||||
# or maybe "<P>" lines in HTML files). That may be because this is
|
||||
# the only method of the 3 that has a *concept* of "junk" <wink>.
|
||||
#
|
||||
# Note that ndiff makes no claim to produce a *minimal* diff. To the
|
||||
# contrary, minimal diffs are often counter-intuitive, because they
|
||||
# synch up anywhere possible, sometimes accidental matches 100 pages
|
||||
# apart. Restricting synch points to contiguous matches preserves some
|
||||
# notion of locality, at the occasional cost of producing a longer diff.
|
||||
#
|
||||
# With respect to junk, an earlier version of ndiff simply refused to
|
||||
# *start* a match with a junk element. The result was cases like this:
|
||||
# before: private Thread currentThread;
|
||||
# after: private volatile Thread currentThread;
|
||||
# If you consider whitespace to be junk, the longest contiguous match
|
||||
# not starting with junk is "e Thread currentThread". So ndiff reported
|
||||
# that "e volatil" was inserted between the 't' and the 'e' in "private".
|
||||
# While an accurate view, to people that's absurd. The current version
|
||||
# looks for matching blocks that are entirely junk-free, then extends the
|
||||
# longest one of those as far as possible but only with matching junk.
|
||||
# So now "currentThread" is matched, then extended to suck up the
|
||||
# preceding blank; then "private" is matched, and extended to suck up the
|
||||
# following blank; then "Thread" is matched; and finally ndiff reports
|
||||
# that "volatile " was inserted before "Thread". The only quibble
|
||||
# remaining is that perhaps it was really the case that " volatile"
|
||||
# was inserted after "private". I can live with that <wink>.
|
||||
#
|
||||
# NOTE on junk: the module-level names
|
||||
# IS_LINE_JUNK
|
||||
# IS_CHARACTER_JUNK
|
||||
# can be set to any functions you like. The first one should accept
|
||||
# a single string argument, and return true iff the string is junk.
|
||||
# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a
|
||||
# line without visible characters, except for at most one splat).
|
||||
# The second should accept a string of length 1 etc. The default is
|
||||
# whether the character is a blank or tab (note: bad idea to include
|
||||
# newline in this!).
|
||||
#
|
||||
# After setting those, you can call fcompare(f1name, f2name) with the
|
||||
# names of the files you want to compare. The difference report
|
||||
# is sent to stdout. Or you can call main(args), passing what would
|
||||
# have been in sys.argv[1:] had the cmd-line form been used.
|
||||
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
import string
|
||||
TRACE = 0
|
||||
|
||||
# define what "junk" means
|
||||
import re
|
||||
|
||||
def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
|
||||
return pat(line) is not None
|
||||
|
||||
def IS_CHARACTER_JUNK(ch, ws=" \t"):
|
||||
return ch in ws
|
||||
|
||||
del re
|
||||
|
||||
# meant for dumping lines
|
||||
def dump(tag, x, lo, hi):
|
||||
for i in xrange(lo, hi):
|
||||
print tag, x[i],
|
||||
|
||||
def plain_replace(a, alo, ahi, b, blo, bhi):
|
||||
assert alo < ahi and blo < bhi
|
||||
# dump the shorter block first -- reduces the burden on short-term
|
||||
# memory if the blocks are of very different sizes
|
||||
if bhi - blo < ahi - alo:
|
||||
dump('+', b, blo, bhi)
|
||||
dump('-', a, alo, ahi)
|
||||
else:
|
||||
dump('-', a, alo, ahi)
|
||||
dump('+', b, blo, bhi)
|
||||
|
||||
# When replacing one block of lines with another, this guy searches
|
||||
# the blocks for *similar* lines; the best-matching pair (if any) is
|
||||
# used as a synch point, and intraline difference marking is done on
|
||||
# the similar pair. Lots of work, but often worth it.
|
||||
|
||||
def fancy_replace(a, alo, ahi, b, blo, bhi):
|
||||
if TRACE:
|
||||
print '*** fancy_replace', alo, ahi, blo, bhi
|
||||
dump('>', a, alo, ahi)
|
||||
dump('<', b, blo, bhi)
|
||||
|
||||
# don't synch up unless the lines have a similarity score of at
|
||||
# least cutoff; best_ratio tracks the best score seen so far
|
||||
best_ratio, cutoff = 0.74, 0.75
|
||||
cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
|
||||
eqi, eqj = None, None # 1st indices of equal lines (if any)
|
||||
|
||||
# search for the pair that matches best without being identical
|
||||
# (identical lines must be junk lines, & we don't want to synch up
|
||||
# on junk -- unless we have to)
|
||||
for j in xrange(blo, bhi):
|
||||
bj = b[j]
|
||||
cruncher.set_seq2(bj)
|
||||
for i in xrange(alo, ahi):
|
||||
ai = a[i]
|
||||
if ai == bj:
|
||||
if eqi is None:
|
||||
eqi, eqj = i, j
|
||||
continue
|
||||
cruncher.set_seq1(ai)
|
||||
# computing similarity is expensive, so use the quick
|
||||
# upper bounds first -- have seen this speed up messy
|
||||
# compares by a factor of 3.
|
||||
# note that ratio() is only expensive to compute the first
|
||||
# time it's called on a sequence pair; the expensive part
|
||||
# of the computation is cached by cruncher
|
||||
if cruncher.real_quick_ratio() > best_ratio and \
|
||||
cruncher.quick_ratio() > best_ratio and \
|
||||
cruncher.ratio() > best_ratio:
|
||||
best_ratio, best_i, best_j = cruncher.ratio(), i, j
|
||||
if best_ratio < cutoff:
|
||||
# no non-identical "pretty close" pair
|
||||
if eqi is None:
|
||||
# no identical pair either -- treat it as a straight replace
|
||||
plain_replace(a, alo, ahi, b, blo, bhi)
|
||||
return
|
||||
# no close pair, but an identical pair -- synch up on that
|
||||
best_i, best_j, best_ratio = eqi, eqj, 1.0
|
||||
else:
|
||||
# there's a close pair, so forget the identical pair (if any)
|
||||
eqi = None
|
||||
|
||||
# a[best_i] very similar to b[best_j]; eqi is None iff they're not
|
||||
# identical
|
||||
if TRACE:
|
||||
print '*** best_ratio', best_ratio, best_i, best_j
|
||||
dump('>', a, best_i, best_i+1)
|
||||
dump('<', b, best_j, best_j+1)
|
||||
|
||||
# pump out diffs from before the synch point
|
||||
fancy_helper(a, alo, best_i, b, blo, best_j)
|
||||
|
||||
# do intraline marking on the synch pair
|
||||
aelt, belt = a[best_i], b[best_j]
|
||||
if eqi is None:
|
||||
# pump out a '-', '?', '+', '?' quad for the synched lines
|
||||
atags = btags = ""
|
||||
cruncher.set_seqs(aelt, belt)
|
||||
for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
|
||||
la, lb = ai2 - ai1, bj2 - bj1
|
||||
if tag == 'replace':
|
||||
atags = atags + '^' * la
|
||||
btags = btags + '^' * lb
|
||||
elif tag == 'delete':
|
||||
atags = atags + '-' * la
|
||||
elif tag == 'insert':
|
||||
btags = btags + '+' * lb
|
||||
elif tag == 'equal':
|
||||
atags = atags + ' ' * la
|
||||
btags = btags + ' ' * lb
|
||||
else:
|
||||
raise ValueError, 'unknown tag ' + `tag`
|
||||
printq(aelt, belt, atags, btags)
|
||||
else:
|
||||
# the synch pair is identical
|
||||
print ' ', aelt,
|
||||
|
||||
# pump out diffs from after the synch point
|
||||
fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
|
||||
|
||||
def fancy_helper(a, alo, ahi, b, blo, bhi):
|
||||
if alo < ahi:
|
||||
if blo < bhi:
|
||||
fancy_replace(a, alo, ahi, b, blo, bhi)
|
||||
else:
|
||||
dump('-', a, alo, ahi)
|
||||
elif blo < bhi:
|
||||
dump('+', b, blo, bhi)
|
||||
|
||||
# Crap to deal with leading tabs in "?" output. Can hurt, but will
|
||||
# probably help most of the time.
|
||||
|
||||
def printq(aline, bline, atags, btags):
|
||||
common = min(count_leading(aline, "\t"),
|
||||
count_leading(bline, "\t"))
|
||||
common = min(common, count_leading(atags[:common], " "))
|
||||
print "-", aline,
|
||||
if count_leading(atags, " ") < len(atags):
|
||||
print "?", "\t" * common + atags[common:]
|
||||
print "+", bline,
|
||||
if count_leading(btags, " ") < len(btags):
|
||||
print "?", "\t" * common + btags[common:]
|
||||
|
||||
def count_leading(line, ch):
|
||||
i, n = 0, len(line)
|
||||
while i < n and line[i] == ch:
|
||||
i = i+1
|
||||
return i
|
||||
|
||||
def fail(msg):
|
||||
import sys
|
||||
out = sys.stderr.write
|
||||
out(msg + "\n\n")
|
||||
out(__doc__)
|
||||
return 0
|
||||
|
||||
# open a file & return the file object; gripe and return 0 if it
|
||||
# couldn't be opened
|
||||
def fopen(fname):
|
||||
try:
|
||||
return open(fname, 'r')
|
||||
except IOError, detail:
|
||||
return fail("couldn't open " + fname + ": " + str(detail))
|
||||
|
||||
# open two files & spray the diff to stdout; return false iff a problem
|
||||
def fcompare(f1name, f2name):
|
||||
f1 = fopen(f1name)
|
||||
f2 = fopen(f2name)
|
||||
if not f1 or not f2:
|
||||
return 0
|
||||
|
||||
a = f1.readlines(); f1.close()
|
||||
b = f2.readlines(); f2.close()
|
||||
|
||||
cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
|
||||
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
|
||||
if tag == 'replace':
|
||||
fancy_replace(a, alo, ahi, b, blo, bhi)
|
||||
elif tag == 'delete':
|
||||
dump('-', a, alo, ahi)
|
||||
elif tag == 'insert':
|
||||
dump('+', b, blo, bhi)
|
||||
elif tag == 'equal':
|
||||
dump(' ', a, alo, ahi)
|
||||
else:
|
||||
raise ValueError, 'unknown tag ' + `tag`
|
||||
|
||||
return 1
|
||||
|
||||
# crack args (sys.argv[1:] is normal) & compare;
|
||||
# return false iff a problem
|
||||
|
||||
def main(args):
|
||||
import getopt
|
||||
try:
|
||||
opts, args = getopt.getopt(args, "qr:")
|
||||
except getopt.error, detail:
|
||||
return fail(str(detail))
|
||||
noisy = 1
|
||||
qseen = rseen = 0
|
||||
for opt, val in opts:
|
||||
if opt == "-q":
|
||||
qseen = 1
|
||||
noisy = 0
|
||||
elif opt == "-r":
|
||||
rseen = 1
|
||||
whichfile = val
|
||||
if qseen and rseen:
|
||||
return fail("can't specify both -q and -r")
|
||||
if rseen:
|
||||
if args:
|
||||
return fail("no args allowed with -r option")
|
||||
if whichfile in "12":
|
||||
restore(whichfile)
|
||||
return 1
|
||||
return fail("-r value must be 1 or 2")
|
||||
if len(args) != 2:
|
||||
return fail("need 2 filename args")
|
||||
f1name, f2name = args
|
||||
if noisy:
|
||||
print '-:', f1name
|
||||
print '+:', f2name
|
||||
return fcompare(f1name, f2name)
|
||||
|
||||
def restore(which):
|
||||
import sys
|
||||
tag = {"1": "- ", "2": "+ "}[which]
|
||||
prefixes = (" ", tag)
|
||||
for line in sys.stdin.readlines():
|
||||
if line[:2] in prefixes:
|
||||
print line[2:],
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if "-profile" in args:
|
||||
import profile, pstats
|
||||
args.remove("-profile")
|
||||
statf = "ndiff.pro"
|
||||
profile.run("main(args)", statf)
|
||||
stats = pstats.Stats(statf)
|
||||
stats.strip_dirs().sort_stats('time').print_stats()
|
||||
else:
|
||||
main(args)
|
@@ -295,6 +295,12 @@ def build_commit(desc, files):
|
||||
ob.desc = ' '
|
||||
|
||||
for commit in files:
|
||||
dir_parts = filter(None, string.split(commit.GetDirectory(), '/'))
|
||||
if dir_parts \
|
||||
and ((dir_parts[0] == 'CVSROOT' and cfg.options.hide_cvsroot) \
|
||||
or cfg.is_forbidden(dir_parts[0])):
|
||||
continue
|
||||
|
||||
ctime = commit.GetTime()
|
||||
if not ctime:
|
||||
ctime = " ";
|
||||
|
935
lib/viewcvs.py
935
lib/viewcvs.py
File diff suppressed because it is too large
Load Diff
@@ -172,8 +172,11 @@ If this doesn't work, please click on the link above.
|
||||
decoded_query = string.replace(query, '+', ' ')
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
# FIXME: I'm not sure about this: Sometimes it hurts, sometimes
|
||||
# it is required. Please enlight me
|
||||
if 1:
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
|
||||
# Preserve state, because we execute script in current process:
|
||||
save_argv = sys.argv
|
||||
@@ -314,13 +317,13 @@ def gui(port):
|
||||
command=self.toggle_subdirmod)
|
||||
self.subdirmod_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# flip_links_in_dirview toggle:
|
||||
self.fliplinks_ivar = Tkinter.IntVar()
|
||||
self.fliplinks_ivar.set(viewcvs.cfg.options.flip_links_in_dirview)
|
||||
self.fliplinks_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="flip file/rev columns (dir view)", var=self.fliplinks_ivar,
|
||||
command=self.toggle_fliplinks)
|
||||
self.fliplinks_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')
|
||||
|
||||
# directory view template:
|
||||
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
@@ -336,6 +339,10 @@ def gui(port):
|
||||
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,
|
||||
@@ -394,8 +401,8 @@ def gui(port):
|
||||
def toggle_subdirmod(self, event=None):
|
||||
viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()
|
||||
|
||||
def toggle_fliplinks(self, event=None):
|
||||
viewcvs.cfg.options.flip_links_in_dirview = self.fliplinks_ivar.get()
|
||||
def toggle_useresearch(self, event=None):
|
||||
viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get()
|
||||
|
||||
def set_templates_log(self, event=None):
|
||||
viewcvs.cfg.templates.log = self.logtemplate_svar.get()
|
||||
|
3
templates/annotate.ezt
Normal file
3
templates/annotate.ezt
Normal file
@@ -0,0 +1,3 @@
|
||||
[include "header.ezt" "annotate"]
|
||||
|
||||
<hr noshade>
|
88
templates/diff.ezt
Normal file
88
templates/diff.ezt
Normal file
@@ -0,0 +1,88 @@
|
||||
[include "header.ezt" "diff"]
|
||||
|
||||
<h3 align=center>Diff for /[where] between version [rev1] and [rev2]</h3>
|
||||
<table border=0 cellspacing=0 cellpadding=0 width="100%">
|
||||
<tr bgcolor=white>
|
||||
<th width="50%" valign=top>
|
||||
version [rev1][date1]
|
||||
[if-any tag1]<br>Tag: [tag1][end]
|
||||
</th>
|
||||
<th width="50%" valign=top>
|
||||
version [rev2][date2]
|
||||
[if-any tag2]<br>Tag: [tag2][end]
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
[for changes]
|
||||
[is changes.type "header"]
|
||||
<tr bgcolor="#99cccc"><td width="50%">
|
||||
<table width="100%" border=1 cellpadding=5><tr>
|
||||
<td><b>Line [changes.line1]</b> <font
|
||||
size="-1">[changes.extra]</font></td>
|
||||
</tr></table></td><td width="50%">
|
||||
<table width="100%" border=1 cellpadding=5><tr>
|
||||
<td><b>Line [changes.line2]</b> <font
|
||||
size="-1">[changes.extra]</font></td>
|
||||
</tr></table>
|
||||
</td></tr>
|
||||
[else]
|
||||
[is changes.type "add"]
|
||||
<tr>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
<td bgcolor="#aaffaa">[changes.right]</td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "remove"]
|
||||
<tr>
|
||||
<td bgcolor="#ffaaaa">[changes.left]</td>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "change"]
|
||||
<tr>
|
||||
[if-any changes.have_left]<td bgcolor="#ffff77">[changes.left]</td>
|
||||
[else]<td bgcolor="#eeee77"> </td>[end]
|
||||
[if-any changes.have_right]<td bgcolor="#ffff77">[changes.right]</td>
|
||||
[else]<td bgcolor="#eeee77"> </td>[end]
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "no-changes"]
|
||||
<tr><td colspan=2> </td></tr>
|
||||
<tr bgcolor="#cccccc"><td colspan=2 align=center>
|
||||
<br><b>- No changes -</b><br>
|
||||
</td></tr>
|
||||
[else][# a line of context]
|
||||
<tr><td>[changes.left]</td><td>[changes.right]</td></tr>
|
||||
[end][end][end][end][end]
|
||||
[end]
|
||||
|
||||
</table><br><hr noshade width="100%">
|
||||
<table border=0 cellpadding=10><tr><td>
|
||||
|
||||
<table border=1><tr><td>Legend:<br>
|
||||
<table border=0 cellspacing=0 cellpadding=1>
|
||||
<tr>
|
||||
<td align=center bgcolor="#ffaaaa">Removed from v.[rev1]</td>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
</tr>
|
||||
<tr bgcolor="#ffff77"><td align=center colspan=2>changed lines</td></tr>
|
||||
<tr>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
<td align=center bgcolor="#aaffaa">Added in v.[rev2]</td>
|
||||
</tr>
|
||||
</table></td></tr></table></td>
|
||||
|
||||
<td><form method="GET" action="[request.url]">
|
||||
[hidden_values]
|
||||
<select name="diff_format" onchange="submit()">
|
||||
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
<input type=submit value="Show">
|
||||
</form></td></tr>
|
||||
</table>
|
||||
|
||||
[include "footer.ezt"]
|
307
templates/dir_alternate.ezt
Normal file
307
templates/dir_alternate.ezt
Normal file
@@ -0,0 +1,307 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[if-any where][else]
|
||||
<!-- you may insert repository access instructions here -->
|
||||
|
||||
[if-any roots]
|
||||
<h3>Project Root</h3>
|
||||
<form method=GET action="./">
|
||||
<select name=cvsroot onchange="submit()">
|
||||
[for roots]
|
||||
[is roots current_root]
|
||||
<option selected>[roots]</option>
|
||||
[else]
|
||||
<option>[roots]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value="Go">
|
||||
</form>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<p><a name="dirlist"></a></p>
|
||||
|
||||
[if-any where]
|
||||
<table>
|
||||
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
|
||||
[if-any view_tag]
|
||||
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
|
||||
[end]
|
||||
[if-any search_re]
|
||||
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
|
||||
[end]
|
||||
[is num_files "0"]
|
||||
[else]
|
||||
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
|
||||
[end]
|
||||
</table>
|
||||
[end]
|
||||
|
||||
<hr noshade>
|
||||
|
||||
[# if you want a colored border around the table of directory
|
||||
information, then add this additional table tag:
|
||||
|
||||
<table border=0 cellpadding=0 width="100%"><tr>
|
||||
<td bgcolor="#999999">
|
||||
]
|
||||
|
||||
|
||||
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||
<tr>
|
||||
[if-any have_logs]
|
||||
[is sortby "rev"]
|
||||
<th align=left bgcolor="#88ff88">Rev.</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
|
||||
[end]
|
||||
[is sortby "file"]
|
||||
<th align=left bgcolor="#88ff88"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
>File</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
><a href="./[sortby_file_href]#dirlist">File</a></th>
|
||||
[end]
|
||||
[is sortby "date"]
|
||||
<th align=left bgcolor="#88ff88">Age</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_date_href]#dirlist">Age</a></th>
|
||||
[end]
|
||||
[is sortby "author"]
|
||||
<th align=left bgcolor="#88ff88">Author</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_author_href]#dirlist">Author</a></th>
|
||||
[end]
|
||||
[is sortby "log"]
|
||||
<th align=left bgcolor="#88ff88">Last log entry</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
|
||||
[end]
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc">File</th>
|
||||
[end]
|
||||
</tr>
|
||||
|
||||
[for rows]
|
||||
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
|
||||
[is rows.type "unreadable"]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[end]
|
||||
<td><a name="[rows.anchor]">[rows.name]</a>
|
||||
[if-any have_logs]
|
||||
</td>
|
||||
<td colspan=[is cfg.options.use_cvsgraph "1"]4[else]3[end]>
|
||||
<i>this entry is unreadable</i>
|
||||
</td>
|
||||
[else]
|
||||
- <i>this entry is unreadable</i>
|
||||
[end]
|
||||
</td>
|
||||
[else]
|
||||
[is rows.type "dir"]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[end]
|
||||
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
|
||||
<a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.name "Attic/"]
|
||||
<a href="./[show_attic_href]#dirlist">[[]show contents]</a>
|
||||
[end]
|
||||
</td>
|
||||
[is rows.cvs "error"]
|
||||
[# for an error to occur, we must have some logs. always use colspan]
|
||||
<td colspan=3>
|
||||
<i>Last modification unavailable - could not read CVS information</i>
|
||||
</td>
|
||||
[else]
|
||||
[if-any have_logs]
|
||||
[is rows.cvs "none"]
|
||||
<td> </td> [# age ]
|
||||
<td> </td> [# author ]
|
||||
<td> </td> [# log ]
|
||||
[else]
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log_file]/[rows.log_rev]<br>
|
||||
<font size="-1">[rows.log]</font></td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[else]
|
||||
[is rows.cvs "error"]
|
||||
<td> </td>
|
||||
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
|
||||
<a name="[rows.anchor]">[rows.name]</a>
|
||||
</td>
|
||||
<td colspan=3><i>CVS information is unreadable</i></td>
|
||||
[else]
|
||||
<td> <a href="[rows.href]"><b>[rows.rev]</b></a></td>
|
||||
<td width="1%"><a name="[rows.anchor]" href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
[# to display the revision in a separate window, you could use:
|
||||
|
||||
<a name="{rows.anchor}" href="{rows.rev_href}" target="cvs_checkout"
|
||||
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||
width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.state "dead"]
|
||||
[# don't let this phrase/link be wrapped ]
|
||||
[if-any view_tag](not exist)[else](in the Attic)[end] <a href="./[hide_attic_href]#dirlist">[[]hide]</a>
|
||||
[end]
|
||||
</td>
|
||||
[if-any rows.graph_href]
|
||||
<td width="1%"><a href="[rows.graph_href]"><img
|
||||
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
|
||||
alt="(graph)" width=16 height=16 border=0>
|
||||
</a></td>
|
||||
[end]
|
||||
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log]</td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
</table>
|
||||
|
||||
[# if you are using a table border (see above), then add:
|
||||
|
||||
</td></tr></table>
|
||||
]
|
||||
|
||||
[if-any no_match]
|
||||
<p><b>NOTE:</b> There are [num_files] files, but none match the
|
||||
current selection criteria.
|
||||
[end]
|
||||
|
||||
[if-any unreadable]
|
||||
<hr size=1 noshade>
|
||||
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
|
||||
repository should be readable by the web server process. Please
|
||||
report this condition to the administrator of this CVS repository.
|
||||
[end]
|
||||
|
||||
[if-any selection_form]
|
||||
<hr size=1 noshade>
|
||||
|
||||
[# this table holds the selectors on the left, and reset on the right ]
|
||||
<table><tr><td>
|
||||
|
||||
<form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
|
||||
<table>
|
||||
[if-any has_tags]
|
||||
<tr>
|
||||
<td>Show files using tag:</td>
|
||||
<td>
|
||||
<select name=only_with_tag onchange="submit()">
|
||||
[if-any branch_tags]
|
||||
<option value="">- Branches -</option>
|
||||
[for branch_tags]
|
||||
[is branch_tags view_tag]
|
||||
<option selected>[branch_tags]</option>
|
||||
[else]
|
||||
<option>[branch_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
<option value="">- Non-branch tags -</option>
|
||||
[for plain_tags]
|
||||
[is plain_tags view_tag]
|
||||
<option selected>[plain_tags]</option>
|
||||
[else]
|
||||
<option>[plain_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select></td>
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
[is cfg.options.use_re_search "1"]
|
||||
<tr>
|
||||
<td>Show files containing the regular expression:</td>
|
||||
<td><input type="text" name="search" value="[search_re]">
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
<tr><td> </td><td><input type="submit" value="Show"></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</td>
|
||||
|
||||
[if-any view_tag]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[else][if-any search_re]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[end][end]
|
||||
|
||||
</tr></table>
|
||||
[end]
|
||||
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
[if-any tarball_href]
|
||||
<a href="[tarball_href]">Download tarball</a>
|
||||
[end]
|
||||
|
||||
[include "footer.ezt"]
|
@@ -15,7 +15,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">Help</a></b></h3></td>
|
||||
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -42,13 +42,22 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
<p><a name="dirlist"></a></p>
|
||||
|
||||
[if-any where]
|
||||
<p>Current directory: <b>[nav_path]</b></p>
|
||||
<table>
|
||||
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
|
||||
[if-any view_tag]
|
||||
<p>Current tag: <b>[view_tag]</b></p>
|
||||
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
|
||||
[end]
|
||||
[if-any search_re]
|
||||
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
|
||||
[end]
|
||||
[is num_files "0"]
|
||||
[else]
|
||||
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
|
||||
[end]
|
||||
</table>
|
||||
[end]
|
||||
|
||||
<p></p><hr noshade>
|
||||
<hr noshade>
|
||||
|
||||
[# if you want a colored border around the table of directory
|
||||
information, then add this additional table tag:
|
||||
@@ -60,44 +69,82 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
|
||||
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||
<tr>
|
||||
[for headers]
|
||||
[is headers.which sortby]
|
||||
<th align=left bgcolor="#88ff88" colspan=[headers.colspan]>[headers.title]</th>
|
||||
[if-any have_logs]
|
||||
[is sortby "file"]
|
||||
<th align=left bgcolor="#88ff88"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
>File</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc" colspan=[headers.colspan]>
|
||||
<a href="[headers.href]">[headers.title]</a>
|
||||
</th>
|
||||
<th align=left bgcolor="#cccccc"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
><a href="./[sortby_file_href]#dirlist">File</a></th>
|
||||
[end]
|
||||
[is sortby "rev"]
|
||||
<th align=left bgcolor="#88ff88">Rev.</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
|
||||
[end]
|
||||
[is sortby "date"]
|
||||
<th align=left bgcolor="#88ff88">Age</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_date_href]#dirlist">Age</a></th>
|
||||
[end]
|
||||
[is sortby "author"]
|
||||
<th align=left bgcolor="#88ff88">Author</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_author_href]#dirlist">Author</a></th>
|
||||
[end]
|
||||
[is sortby "log"]
|
||||
<th align=left bgcolor="#88ff88">Last log entry</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
|
||||
[end]
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc">File</th>
|
||||
[end]
|
||||
</tr>
|
||||
|
||||
[for rows]
|
||||
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
|
||||
[is rows.type "unreadable"]
|
||||
[is rev_in_front "1"]<td width="0%"> </td>[end]
|
||||
<td><a name="[rows.anchor]">[rows.name]</a></td>
|
||||
<td colspan=[rows.span]><i>this entry is unreadable</i></td>
|
||||
<td><a name="[rows.anchor]">[rows.name]</a>
|
||||
[if-any have_logs]
|
||||
</td>
|
||||
<td colspan=[is cfg.options.use_cvsgraph "1"]5[else]4[end]>
|
||||
<i>this entry is unreadable</i>
|
||||
</td>
|
||||
[else]
|
||||
- <i>this entry is unreadable</i>
|
||||
[end]
|
||||
</td>
|
||||
[else]
|
||||
[is rows.type "dir"]
|
||||
[is rev_in_front "1"]<td width="0%"> </td>[end]
|
||||
<td><a name="[rows.anchor]" href="[rows.href]">
|
||||
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
|
||||
<a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[if-any rows.hide_attic_href]
|
||||
<a href="[rows.hide_attic_href]">[[]Don't hide]</a>
|
||||
[is rows.name "Attic/"]
|
||||
<a href="./[show_attic_href]#dirlist">[[]show contents]</a>
|
||||
[end]
|
||||
</td>
|
||||
[is rows.cvs "error"]
|
||||
<td colspan=[rows.span]><i>CVS information is unreadable</i></td>
|
||||
[# for an error to occur, we must have some logs. always use colspan]
|
||||
<td colspan=4>
|
||||
<i>Last modification unavailable - could not read CVS information</i>
|
||||
</td>
|
||||
[else]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[is rows.cvs "none"]
|
||||
[for rows.cols]<td> </td>[end]
|
||||
<td> </td> [# age ]
|
||||
<td> </td> [# author ]
|
||||
<td> </td> [# log ]
|
||||
[else]
|
||||
[if-any rows.graph_href]
|
||||
<td width="1%"> </td>
|
||||
[end]
|
||||
[is cfg.options.flip_links_in_dirview "0"]<td> </td>[end]
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
@@ -108,46 +155,36 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[else]
|
||||
[is rows.cvs "error"]
|
||||
[is rev_in_front "1"]<td> </td>[end]
|
||||
<td><a name="[rows.anchor]">[rows.name]</a></td>
|
||||
<td colspan=[rows.span]><i>CVS information is unreadable</i></td>
|
||||
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
|
||||
<a name="[rows.anchor]">[rows.name]</a>
|
||||
</td>
|
||||
<td colspan=4><i>CVS information is unreadable</i></td>
|
||||
[else]
|
||||
[is cfg.options.flip_links_in_dirview "1"]
|
||||
<td><a name="[rows.anchor]" href="[rows.href]"><b>[rows.rev]</b></a>
|
||||
</td>
|
||||
[end]
|
||||
<td><a name="[rows.anchor]"
|
||||
[is cfg.options.flip_links_in_dirview "0"]
|
||||
href="[rows.href]">
|
||||
[else]
|
||||
href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
[# to display the revision in a separate window, you could use:
|
||||
|
||||
href="{rows.rev_href}" target="cvs_checkout"
|
||||
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')">
|
||||
|
||||
(substitute brackets for the braces)
|
||||
] [end]<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||
width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[rows.attic]
|
||||
</td>
|
||||
<td><a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||
width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.state "dead"]
|
||||
[# don't let this phrase/link be wrapped ]
|
||||
[if-any view_tag](not exist)[else](in the Attic)[end] <a href="./[hide_attic_href]#dirlist">[[]hide]</a>
|
||||
[end]
|
||||
</td>
|
||||
[if-any rows.graph_href]
|
||||
<td width="1%"><a href="[rows.graph_href]"><img
|
||||
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
|
||||
alt="(graph)" width=16 height=16 border=0>
|
||||
</a></td>
|
||||
[end]
|
||||
[is cfg.options.flip_links_in_dirview "0"]
|
||||
<td>
|
||||
<a href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
<b>[rows.rev]</b>
|
||||
</a>
|
||||
|
||||
<td>
|
||||
<a href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
<b>[rows.rev]</b>
|
||||
</a>
|
||||
[# to display the revision in a separate window, you could use:
|
||||
|
||||
<a href="{rows.rev_href}" target="cvs_checkout"
|
||||
@@ -156,8 +193,7 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
</td>
|
||||
[end]
|
||||
</td>
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
@@ -180,7 +216,7 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
|
||||
[if-any no_match]
|
||||
<p><b>NOTE:</b> There are [num_files] files, but none match the
|
||||
current tag ([view_tag])
|
||||
current selection criteria.
|
||||
[end]
|
||||
|
||||
[if-any unreadable]
|
||||
@@ -190,36 +226,75 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
report this condition to the administrator of this CVS repository.
|
||||
[end]
|
||||
|
||||
[if-any has_tags]
|
||||
[if-any selection_form]
|
||||
<hr size=1 noshade>
|
||||
|
||||
[# this table holds the selectors on the left, and reset on the right ]
|
||||
<table><tr><td>
|
||||
|
||||
<form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
|
||||
Show only files with tag:
|
||||
<table>
|
||||
[if-any has_tags]
|
||||
<tr>
|
||||
<td>Show files using tag:</td>
|
||||
<td>
|
||||
<select name=only_with_tag onchange="submit()">
|
||||
[if-any branch_tags]
|
||||
<option value="">- Branches -</option>
|
||||
[for branch_tags]
|
||||
[is branch_tags view_tag]
|
||||
<option selected>[branch_tags]</option>
|
||||
[else]
|
||||
<option>[branch_tags]</option>
|
||||
[if-any branch_tags]
|
||||
<option value="">- Branches -</option>
|
||||
[for branch_tags]
|
||||
[is branch_tags view_tag]
|
||||
<option selected>[branch_tags]</option>
|
||||
[else]
|
||||
<option>[branch_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
<option value="">- Non-branch tags -</option>
|
||||
[for plain_tags]
|
||||
[is plain_tags view_tag]
|
||||
<option selected>[plain_tags]</option>
|
||||
[else]
|
||||
<option>[plain_tags]</option>
|
||||
<option value="">- Non-branch tags -</option>
|
||||
[for plain_tags]
|
||||
[is plain_tags view_tag]
|
||||
<option selected>[plain_tags]</option>
|
||||
[else]
|
||||
<option>[plain_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select></td>
|
||||
</tr>
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value="Go">
|
||||
|
||||
[is cfg.options.use_re_search "1"]
|
||||
<tr>
|
||||
<td>Show files containing the regular expression:</td>
|
||||
<td><input type="text" name="search" value="[search_re]">
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
<tr><td> </td><td><input type="submit" value="Show"></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</td>
|
||||
|
||||
[if-any view_tag]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[else][if-any search_re]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[end][end]
|
||||
|
||||
</tr></table>
|
||||
[end]
|
||||
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
@@ -227,10 +302,4 @@ href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.
|
||||
<a href="[tarball_href]">Download tarball</a>
|
||||
[end]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
[include "footer.ezt"]
|
||||
|
9
templates/footer.ezt
Normal file
9
templates/footer.ezt
Normal file
@@ -0,0 +1,9 @@
|
||||
[# standard footer used by all ViewCVS pages ]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[cfg.general.address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
12
templates/graph.ezt
Normal file
12
templates/graph.ezt
Normal file
@@ -0,0 +1,12 @@
|
||||
[include "header.ezt" "graph"]
|
||||
|
||||
<center>
|
||||
<h1>Revision graph of [request.where]</h1>
|
||||
|
||||
[imagemap]
|
||||
<img border="0" usemap="#MyMapName"
|
||||
src="[request.url]?graph=[rev]&makeimage=1[request.amp_query]"
|
||||
alt="Revisions of [request.where]">
|
||||
</center>
|
||||
|
||||
[include "footer.ezt"]
|
25
templates/header.ezt
Normal file
25
templates/header.ezt
Normal file
@@ -0,0 +1,25 @@
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
[### NOTE: the "diff" is the TITLE param to navigate_header() ]
|
||||
<title>[path]/[filename] - [arg0] - [rev]</title>
|
||||
</head>
|
||||
<body bgcolor="#eeeeee">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=1 bgcolor="#9999ee">
|
||||
<tr valign=bottom>
|
||||
<td>
|
||||
<a href="[file_url][qquery]#rev[rev]">
|
||||
<img src="/icons/small/back.gif" alt="(file)" border=0 width=16 height=16>
|
||||
</a>
|
||||
<b>Return to
|
||||
<a href="[file_url][qquery]#rev[rev]">[filename]</a>
|
||||
CVS log</b>
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0 width=16 height=16>
|
||||
</td>
|
||||
<td align=right>
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
<b>Up to [nav_path]</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
@@ -9,10 +9,14 @@
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td><h1>CVS log for [where]</h1></td>
|
||||
<td rowspan=2><h1>CVS log for [where]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_log.html">Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||
@@ -206,7 +210,7 @@ and
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "c"]selected[end]>Side by Side</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
|
||||
<input type=submit value=" Get Diffs "></form>
|
||||
@@ -239,10 +243,4 @@ and
|
||||
<input type=submit value=" Sort ">
|
||||
</form>
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
[include "footer.ezt"]
|
||||
|
@@ -9,10 +9,14 @@
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td><h1>CVS log for [where]</h1></td>
|
||||
<td rowspan=2><h1>CVS log for [where]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_logtable.html">Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||
@@ -211,7 +215,7 @@ and
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "c"]selected[end]>Side by Side</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
|
||||
<input type=submit value=" Get Diffs "></form>
|
||||
@@ -244,10 +248,4 @@ and
|
||||
<input type=submit value=" Sort ">
|
||||
</form>
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
[include "footer.ezt"]
|
||||
|
49
templates/markup.ezt
Normal file
49
templates/markup.ezt
Normal file
@@ -0,0 +1,49 @@
|
||||
[include "header.ezt" "view"]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%"><tr><td bgcolor="#ffffff">
|
||||
File: [nav_file]
|
||||
|
||||
(<a href="[href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank','cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]');"
|
||||
><b>download</b></a>)
|
||||
[is mime_type "text/plain"]
|
||||
[else]
|
||||
/
|
||||
(<a href="[text_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
|
||||
[end]
|
||||
<br>
|
||||
|
||||
[if-any log]
|
||||
Revision: <b>[rev]</b>[if-any vendor_branch] <i>(vendor branch)</i>[end],
|
||||
<i>[utc_date] UTC</i> ([ago] ago) by <i>[author]</i>
|
||||
|
||||
[if-any branches]
|
||||
<br>Branch: <b>[branches]</b>
|
||||
[end]
|
||||
[if-any tags]
|
||||
<br>CVS Tags: <b>[tags]</b>
|
||||
[end]
|
||||
[if-any branch_points]
|
||||
<br>Branch point for: <b>[branch_points]</b>
|
||||
[end]
|
||||
[if-any prev]
|
||||
[if-any changed]
|
||||
<br>Changes since <b>[prev]: [changed] lines</b>
|
||||
[end]
|
||||
[end]
|
||||
[is state "dead"]
|
||||
<br><b><i>FILE REMOVED</i></b>
|
||||
[end]
|
||||
<pre>[log]</pre>
|
||||
[else]
|
||||
Revision: <b>[rev]</b><br>
|
||||
[if-any tag]
|
||||
Tag: <b>[tag]</b><br>
|
||||
[end]
|
||||
[end]
|
||||
</td></tr></table>
|
||||
<hr noshade>
|
@@ -225,10 +225,4 @@
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
[include "footer.ezt"]
|
||||
|
@@ -46,7 +46,7 @@ find $2 -print | xargs chmod uoa+r
|
||||
find $2 -type d -print | xargs chmod uoa+x
|
||||
|
||||
# cut the tarball:
|
||||
tar cfz - $2 | gzip -9 > $2.tar.gz
|
||||
tar cf - $2 | gzip -9 > $2.tar.gz
|
||||
# create also a ZIP file for those poor souls :-) still using Windows:
|
||||
zip -qor9 $2.zip $2
|
||||
|
||||
|
125
viewcvs-install
125
viewcvs-install
@@ -27,12 +27,14 @@ import string
|
||||
import re
|
||||
import traceback
|
||||
import py_compile
|
||||
import StringIO
|
||||
|
||||
# get access to our library modules
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
|
||||
|
||||
import compat
|
||||
import viewcvs
|
||||
import ndiff
|
||||
version = viewcvs.__version__
|
||||
|
||||
|
||||
@@ -84,10 +86,18 @@ went into the new files in subdirectory templates.""", 0),
|
||||
("lib/viewcvs.py", "lib/viewcvs.py", 0644, 1, 0, 1),
|
||||
("lib/ezt.py", "lib/ezt.py", 0644, 0, 0, 1),
|
||||
("lib/apache_icons.py", "lib/apache_icons.py", 0644, 0, 0, 1),
|
||||
("lib/accept.py", "lib/accept.py", 0644, 0, 0, 1),
|
||||
|
||||
("templates/annotate.ezt", "templates/annotate.ezt", 0644, 0, 1, 0),
|
||||
("templates/diff.ezt", "templates/diff.ezt", 0644, 0, 1, 0),
|
||||
("templates/directory.ezt", "templates/directory.ezt", 0644, 0, 1, 0),
|
||||
("templates/dir_alternate.ezt", "templates/dir_alternate.ezt", 0644, 0, 1, 0),
|
||||
("templates/footer.ezt", "templates/footer.ezt", 0644, 0, 1, 0),
|
||||
("templates/graph.ezt", "templates/graph.ezt", 0644, 0, 1, 0),
|
||||
("templates/header.ezt", "templates/header.ezt", 0644, 0, 1, 0),
|
||||
("templates/log.ezt", "templates/log.ezt", 0644, 0, 1, 0),
|
||||
("templates/log_table.ezt", "templates/log_table.ezt", 0644, 0, 1, 0),
|
||||
("templates/markup.ezt", "templates/markup.ezt", 0644, 0, 1, 0),
|
||||
("templates/query.ezt", "templates/query.ezt", 0644, 0, 1, 0),
|
||||
|
||||
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
|
||||
@@ -96,6 +106,9 @@ went into the new files in subdirectory templates.""", 0),
|
||||
|
||||
("website/help_rootview.html", "doc/help_rootview.html", 0644, 0, 0, 0),
|
||||
("website/help_dirview.html", "doc/help_dirview.html", 0644, 0, 0, 0),
|
||||
("website/help_query.html", "doc/help_query.html", 0644, 0, 0, 0),
|
||||
("website/help_log.html", "doc/help_log.html", 0644, 0, 0, 0),
|
||||
("website/help_logtable.html", "doc/help_logtable.html", 0644, 0, 0, 0),
|
||||
|
||||
("website/images/logo.png", "doc/images/logo.png", 0644, 0, 0, 0),
|
||||
("website/images/chalk.jpg", "doc/images/chalk.jpg", 0644, 0, 0, 0),
|
||||
@@ -132,6 +145,7 @@ def SetOnePath(contents, var, value):
|
||||
repl = '%s = "%s"' % (var, os.path.join(ROOT_DIR, value))
|
||||
return re.sub(pattern, repl, contents)
|
||||
|
||||
|
||||
def SetPythonPaths(contents):
|
||||
if contents[:2] == '#!':
|
||||
shbang = '#!' + sys.executable
|
||||
@@ -144,39 +158,86 @@ def SetPythonPaths(contents):
|
||||
|
||||
def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
|
||||
compile_it):
|
||||
dest_path = os.path.join(ROOT_DIR, dest_path)
|
||||
dest_path = os.path.join(ROOT_DIR, dest_path)
|
||||
|
||||
if prompt_replace and os.path.exists(dest_path):
|
||||
if type(prompt_replace) == type(""):
|
||||
print prompt_replace
|
||||
temp = raw_input("File %s exists, overwrite? [y/N]: " % (dest_path))
|
||||
if not temp or string.lower(temp[0]) != "y":
|
||||
return
|
||||
if prompt_replace and os.path.exists(dest_path):
|
||||
# Collect ndiff output from ndiff
|
||||
sys.stdout = StringIO.StringIO()
|
||||
ndiff.main([dest_path,src_path])
|
||||
ndiff_output = sys.stdout.getvalue()
|
||||
|
||||
# Return everything to normal
|
||||
sys.stdout = sys.__stdout__
|
||||
|
||||
# Collect the '+ ' and '- ' lines
|
||||
# total collects the difference lines to be printed later
|
||||
total = ""
|
||||
# I use flag to throw out match lines.
|
||||
flag = 1
|
||||
for line in string.split(ndiff_output,'\n'):
|
||||
# Print line if it is a difference line
|
||||
if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
|
||||
total = total + line + "\n"
|
||||
flag = 1
|
||||
else:
|
||||
# Compress lines that are the same to print one blank line
|
||||
if flag:
|
||||
total = total + "\n"
|
||||
flag = 0
|
||||
|
||||
if total == "\n":
|
||||
print " File %s exists,\n but there is no difference between target and source files.\n" % (dest_path)
|
||||
return
|
||||
|
||||
if type(prompt_replace) == type(""):
|
||||
print prompt_replace
|
||||
while 1:
|
||||
temp = raw_input("\n File %s\n exists and is different from source file.\n DO YOU WANT TO,\n overwrite [o]\n do not overwrite [d]\n view differences [v]: " % (dest_path))
|
||||
print
|
||||
|
||||
temp = string.lower(temp[0])
|
||||
|
||||
if temp == "d":
|
||||
return
|
||||
|
||||
if temp == "v":
|
||||
print total
|
||||
print "\nLEGEND\n A leading '- ' indicates line to remove from installed file\n A leading '+ ' indicates line to add to installed file\n A leading '? ' shows intraline differences."
|
||||
|
||||
if temp == "o":
|
||||
ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it)
|
||||
return
|
||||
else:
|
||||
ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it)
|
||||
return
|
||||
|
||||
def ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it):
|
||||
try:
|
||||
contents = open(src_path, "r").read()
|
||||
except IOError, e:
|
||||
Error(str(e))
|
||||
|
||||
if set_python_paths:
|
||||
contents = SetPythonPaths(contents)
|
||||
|
||||
## write the file to the destination location
|
||||
path, basename = os.path.split(dest_path)
|
||||
MkDir(path)
|
||||
|
||||
try:
|
||||
open(dest_path, "w").write(contents)
|
||||
except IOError, e:
|
||||
if e[0] == 13:
|
||||
# EACCES: permission denied
|
||||
Error("You do not have permission to write file %s" % dest_path)
|
||||
Error("Unknown error writing file %s" % dest_path, IOError, e)
|
||||
|
||||
try:
|
||||
contents = open(src_path, "r").read()
|
||||
except IOError, e:
|
||||
Error(str(e))
|
||||
|
||||
if set_python_paths:
|
||||
contents = SetPythonPaths(contents)
|
||||
|
||||
## write the file to the destination location
|
||||
path, basename = os.path.split(dest_path)
|
||||
MkDir(path)
|
||||
|
||||
try:
|
||||
open(dest_path, "w").write(contents)
|
||||
except IOError, e:
|
||||
if e[0] == 13:
|
||||
# EACCES: permission denied
|
||||
Error("You do not have permission to write file %s" % dest_path)
|
||||
Error("Unknown error writing file %s" % dest_path, IOError, e)
|
||||
|
||||
os.chmod(dest_path, mode)
|
||||
|
||||
if compile_it:
|
||||
py_compile.compile(dest_path)
|
||||
os.chmod(dest_path, mode)
|
||||
|
||||
if compile_it:
|
||||
py_compile.compile(dest_path)
|
||||
|
||||
return
|
||||
|
||||
|
||||
## MAIN
|
||||
@@ -194,7 +255,7 @@ if __name__ == "__main__":
|
||||
print "Installing ViewCVS to:", ROOT_DIR
|
||||
|
||||
for args in FILE_INFO_LIST:
|
||||
print " ", args[0]
|
||||
print " ", args[0]
|
||||
apply(InstallFile, args)
|
||||
|
||||
print
|
||||
|
@@ -15,9 +15,13 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Other Help</h3>
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<b>Directory View</b><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
|
61
website/help_log.html
Normal file
61
website/help_log.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: Classic CVS Log View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: Classic CVS Log View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<b>Classic Log View</b><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
<p>
|
||||
The log view displays the revision history of the selected source
|
||||
file. For each revision the following information is displayed:
|
||||
<ul>
|
||||
<li>the revision number (clickable to download it)</li>
|
||||
<li>a link to view this revision annotated</li>
|
||||
<li>a link to select this revision for diffs (see below)</li>
|
||||
<li>the date and age of this change</li>
|
||||
<li>and the author of this modification.</li>
|
||||
<li>The CVS branch (usually <code>MAIN</code>, if not on a branch).</li>
|
||||
<li>Possibly a list of CVS tags bound to this revision (if any).</li>
|
||||
<li>The size of this change measured in added and removed lines of
|
||||
code.</li>
|
||||
<li>Links to view Diffs to the previous revision or possibly to
|
||||
an arbitrary selected revision (if any, see above).
|
||||
<li>And last but not least, the commit log message which should tell
|
||||
about the reason for this change.</li>
|
||||
</ul>
|
||||
</p><p>
|
||||
At the bottom of such a page you will find a form which allows
|
||||
to request diffs between arbitrary revisions.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Dec 12 13:56:52 CET 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
59
website/help_logtable.html
Normal file
59
website/help_logtable.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: CVS Log Table View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: CVS Log Table View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<b>Alternative Log View</b><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
<p>
|
||||
The table based log view must be enabled in the site wide
|
||||
ViewCVS configuration file.
|
||||
</p><p>
|
||||
It displays the revision history of the selected source
|
||||
file in a table containing the following columns:
|
||||
<ul>
|
||||
<li><b>Revision</b>: The CVS revision number.</li>
|
||||
<li><b>Tasks</b>: This column contains several links to view
|
||||
a revision.</li>
|
||||
<li><b>Diffs</b>: Several links to view the changes between this
|
||||
and other revisions.
|
||||
<li><b>Branches and Tags</b>: Displays the branch and a pulldown
|
||||
menu button displaying all tags bound to that revision</li>
|
||||
<li>FIXME: Document the other columns</li>
|
||||
</ul>
|
||||
</p><p>
|
||||
At the bottom of such a page you will find a form which allows
|
||||
to request diffs between arbitrary revisions.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sat Dec 8 23:26:52 CET 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
@@ -16,6 +16,10 @@
|
||||
<h3>Other Help:</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<b>Query Database</b>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
|
@@ -15,9 +15,13 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Other Help</h3>
|
||||
<h3>Help</h3>
|
||||
<b>General</b>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
</td>
|
||||
<td align=center valign=top bgcolor="white" width="1%">
|
||||
<b>Quickstart:</b>
|
||||
<a href="viewcvs-0.8.tar.gz">download</a>
|
||||
<a href="viewcvs-0.9.4.tar.gz">download</a>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
@@ -128,6 +128,11 @@
|
||||
ViewCVS by editing the provided EZT templates, which are used
|
||||
to generate the pages.
|
||||
</li>
|
||||
<li>
|
||||
Internationalization support: ViewCVS will parse and handle
|
||||
the Accept-Language request header, and can select different
|
||||
inputs for localized page generation.
|
||||
</li>
|
||||
<li>
|
||||
Colorization for many file types via <code>enscript</code>.
|
||||
</li>
|
||||
@@ -145,11 +150,18 @@
|
||||
across virtual hosts, yet still be able to fine-tune the
|
||||
options when necessary.
|
||||
</li>
|
||||
<li>
|
||||
Automatic generation of tarballs for the HEAD or a specified
|
||||
tag.
|
||||
</li>
|
||||
<li>
|
||||
Runs either as CGI script, called from an installed web server
|
||||
(such as <a href="http://httpd.apache.org/"><i>Apache</i></a>),
|
||||
or as a standalone server.
|
||||
</li>
|
||||
<li>
|
||||
Searching files for matches for a regular expression.
|
||||
</li>
|
||||
<li>Better reporting for unreadable files.</li>
|
||||
<li>
|
||||
More robust when given varying <code>rcsdiff</code> or
|
||||
@@ -198,10 +210,10 @@
|
||||
The software is available for download:
|
||||
</p>
|
||||
<blockquote>
|
||||
<a href="viewcvs-0.8.tar.gz">Version 0.8 of ViewCVS as a gzipped
|
||||
<a href="viewcvs-0.9.4.tar.gz">Version 0.9.4 of ViewCVS as a gzipped
|
||||
tar</a>
|
||||
<br>
|
||||
<a href="viewcvs-0.8.zip">Version 0.8 of ViewCVS as a ZIP
|
||||
<a href="viewcvs-0.9.4.zip">Version 0.9.4 of ViewCVS as a ZIP
|
||||
file</a>
|
||||
</blockquote>
|
||||
<p>
|
||||
@@ -287,7 +299,7 @@
|
||||
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Dec 10 05:38:45 PST 2001
|
||||
Last modified: Tue Jan 15 01:51:03 PST 2002
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -41,18 +41,185 @@
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="#from8">Upgrading from ViewCVS 0.8</a></li>
|
||||
<li><a href="#from7">Upgrading from ViewCVS 0.7 or earlier</a></li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2><a name="from7">Upgrading from ViewCVS 0.7 or earlier</a></h2>
|
||||
<h2><a name="from8">Upgrading from ViewCVS 0.8</a></h2>
|
||||
<p>
|
||||
This section discusses how to upgrade ViewCVS 0.7 or earlier to
|
||||
0.8 or a later version of the software.
|
||||
This section discusses how to upgrade ViewCVS 0.8 to version
|
||||
0.9 or a later version of the software.
|
||||
</p>
|
||||
|
||||
<h3>Templates</h3>
|
||||
<h3>Configuration Options</h3>
|
||||
<p>
|
||||
More templates were introduced in version 0.8 of the software,
|
||||
which made many of the configuration options obsolete. This
|
||||
section covers which options were removed. If you made any
|
||||
changes to these options, then you will need to make
|
||||
corresponding changes in the templates.
|
||||
</p>
|
||||
<blockquote>
|
||||
<dl>
|
||||
<dt>
|
||||
Colors:
|
||||
<strong>diff_heading</strong>,
|
||||
<strong>diff_empty</strong>,
|
||||
<strong>diff_remove</strong>,
|
||||
<strong>diff_change</strong>,
|
||||
<strong>diff_add</strong>,
|
||||
and <strong>diff_dark_change</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>diff.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>markup_log</strong></dt>
|
||||
<dd>
|
||||
This option has been incorporated into the
|
||||
<code>markup.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>Colors: <strong>nav_header</strong>
|
||||
and <strong>alt_background</strong></dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>header.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
Images:
|
||||
<strong>back_icon</strong>,
|
||||
<strong>dir_icon</strong>,
|
||||
and <strong>file_icon</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>directory.ezt</code>, <code>header.ezt</code>,
|
||||
<code>log.ezt</code>, <code>log_table.ezt</code>, and
|
||||
<code>query.ezt</code> templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>use_java_script</strong>
|
||||
and <strong>open_extern_window</strong></dt>
|
||||
<dd>
|
||||
The templates now use JavaScript in all applicable places,
|
||||
and open external windows for most downloading and viewing
|
||||
of files. If you wish to not use JavaScript and/or external
|
||||
windows, then remove the feature(s) from the templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>show_author</strong></dt>
|
||||
<dd>
|
||||
Changing this option would be quite strange and rare. If you
|
||||
do not want to show the author for the revisions, then you
|
||||
should remove it from the various templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>hide_non_readable</strong></dt>
|
||||
<dd>
|
||||
This option was never used, so it has been removed.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>flip_links_in_dirview</strong></dt>
|
||||
<dd>
|
||||
This option is no longer available. If you want the links in
|
||||
your directory view flipped, then you may use the
|
||||
<code>dir_alternate.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</blockquote>
|
||||
|
||||
<h3>Template Variables</h3>
|
||||
<p>
|
||||
Some template variables that were available in 0.8 have been
|
||||
removed in 0.9. If you have custom templates that refer to these
|
||||
variables, then you will need to modify your templates.
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<dl>
|
||||
<dt><code>directory.ezt</code>: <var>headers</var></dt>
|
||||
<dd>
|
||||
The headers are now listed explicitly in the template,
|
||||
rather than made available through a list.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
<code>directory.ezt</code>:
|
||||
<var>rows.cols</var>,
|
||||
and <var>rows.span</var>
|
||||
</dt>
|
||||
<dd>
|
||||
These variables were used in conjunction with the
|
||||
<var>headers</var> variable to control the column
|
||||
displays. This is now controlled explicitly within the
|
||||
templates.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><code>directory.ezt</code>:
|
||||
<var>rev_in_front</var></dt>
|
||||
<dd>
|
||||
This was used to indicate that revision links should
|
||||
be used in the first column, rather than in their
|
||||
standard place in the second column. Changing the
|
||||
links should now be done in the template, rather than
|
||||
according to this variable. You may want to look at
|
||||
the <code>dir_alternate.ezt</code> template, which has
|
||||
the revision in front.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><code>directory.ezt</code>:
|
||||
<var>rows.attic</var>
|
||||
and <var>rows.hide_attic_href</var></dt>
|
||||
<dd>
|
||||
These variable were used to manage the hide and
|
||||
showing of the contents of the <code>Attic/</code>
|
||||
subdirectory. Several new variables were introduced
|
||||
which can be used to replace this functionality:
|
||||
<var>show_attic_href</var>,
|
||||
<var>hide_attic_href</var>, and <var>rows.state</var>.
|
||||
<p></p>
|
||||
</dd>
|
||||
</dl>
|
||||
</blockquote>
|
||||
<hr>
|
||||
|
||||
<h2><a name="from7">Upgrading from ViewCVS 0.7 or earlier</a></h2>
|
||||
<p>
|
||||
This section discusses how to upgrade ViewCVS 0.7, or earlier,
|
||||
to 0.8 or a later version of the software.
|
||||
</p>
|
||||
<p>
|
||||
<strong>NOTE:</strong> these changes will bring you up to the
|
||||
requirements of version 0.8. You must also follow the directions
|
||||
for <a href="#from8">upgrading from 0.8</a>.
|
||||
</p>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<p>
|
||||
The largest change from 0.7 to 0.8, that you will need to deal
|
||||
with, is the introduction of templates. This shifted many
|
||||
@@ -164,6 +331,17 @@
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>Colors: <strong>text</strong>
|
||||
and <strong>background</strong></dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>directory.ezt</code>, <code>log.ezt</code>, and
|
||||
<code>log_table.ezt</code> templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</blockquote>
|
||||
|
||||
@@ -172,7 +350,7 @@
|
||||
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||
<!-- Created: Mon Sep 24 04:23:53 PDT 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Dec 10 02:06:31 PST 2001
|
||||
Last modified: Sat Dec 22 20:05:14 PST 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user