mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
55 Commits
0.3.0
...
CVSDB_PROD
Author | SHA1 | Date | |
---|---|---|---|
![]() |
89603a4b38 | ||
![]() |
1cf42799f7 | ||
![]() |
fb144dfa9d | ||
![]() |
7963916b11 | ||
![]() |
f79830925a | ||
![]() |
2217f50d1c | ||
![]() |
7cd158651d | ||
![]() |
b35c4f4e9f | ||
![]() |
1f5eb1b433 | ||
![]() |
0eee32c64c | ||
![]() |
61d80c751e | ||
![]() |
db44b541e0 | ||
![]() |
3d2559276c | ||
![]() |
0c963cf529 | ||
![]() |
50b3d40958 | ||
![]() |
7bb0ae1b29 | ||
![]() |
01b472e9fd | ||
![]() |
1f7ea22511 | ||
![]() |
3044ae7fba | ||
![]() |
fafa768bea | ||
![]() |
9bc63db2b4 | ||
![]() |
d87876bb54 | ||
![]() |
cd7aa6b5da | ||
![]() |
024ccc6da3 | ||
![]() |
36684bde60 | ||
![]() |
d08fb441d7 | ||
![]() |
ebe4f1cbeb | ||
![]() |
0f8b06c9e0 | ||
![]() |
f707ad1d3c | ||
![]() |
186b003a76 | ||
![]() |
621d08cb9f | ||
![]() |
8288be7f6f | ||
![]() |
95e1ab3303 | ||
![]() |
05ec3b535d | ||
![]() |
5e26bb149a | ||
![]() |
94a26cadd1 | ||
![]() |
22b7f9fcac | ||
![]() |
3c5f8ff0e9 | ||
![]() |
b9bf86058a | ||
![]() |
3fcd99d50d | ||
![]() |
2ca1c8b41b | ||
![]() |
59ced64e13 | ||
![]() |
d25232f0e9 | ||
![]() |
89bb1bcef4 | ||
![]() |
16feeb9778 | ||
![]() |
edaa77d018 | ||
![]() |
867499fda8 | ||
![]() |
67c6042340 | ||
![]() |
c2bb58552c | ||
![]() |
15bdf6df63 | ||
![]() |
6ba92b91ad | ||
![]() |
0676636d07 | ||
![]() |
9d91a0e574 | ||
![]() |
98288899fa | ||
![]() |
3bf1ede685 |
42
INSTALL
42
INSTALL
@@ -1,8 +1,8 @@
|
|||||||
INSTALLING VIEWCVS
|
INSTALLING VIEWCVS
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
1) To get viewcvs.cgi to work, make sure that you have Python 1.5.2
|
1) To get viewcvs.cgi to work, make sure that you have Python 1.5
|
||||||
installed and a webserver which is capable to execute cgi-scripts
|
installed and a webserver which is capable of executing cgi-scripts
|
||||||
(either based on the .cgi extension, or by placing the script
|
(either based on the .cgi extension, or by placing the script
|
||||||
within a specific directory).
|
within a specific directory).
|
||||||
|
|
||||||
@@ -13,21 +13,29 @@ INSTALLING VIEWCVS
|
|||||||
access to the repository (or a copy of it). Therefore, rsh/ssh or
|
access to the repository (or a copy of it). Therefore, rsh/ssh or
|
||||||
pserver access doesn't work yet.
|
pserver access doesn't work yet.
|
||||||
|
|
||||||
2) Copy viewcvs.cgi to the cgi-script location of your web
|
2) Copy viewcvs.cgi to the cgi-script location of your web server.
|
||||||
server. Edit the file for your specific configuration. In
|
|
||||||
particular, examine the following configuration variables:
|
If Python is not located in /usr/local/bin, then you'll need to
|
||||||
|
edit the first line of viewcvs.cgi.
|
||||||
|
|
||||||
|
3) Copy viewcvs.conf.dist to the same directory and RENAME it to
|
||||||
|
viewcvs.conf
|
||||||
|
|
||||||
|
4) Edit viewcvs.conf for your specific configuration. In particular,
|
||||||
|
examine the following configuration options:
|
||||||
|
|
||||||
cvs_roots
|
cvs_roots
|
||||||
default_root
|
default_root
|
||||||
rcs_path
|
rcs_path
|
||||||
mime_types_file
|
mime_types_file
|
||||||
|
|
||||||
If Python is not located in /usr/local/bin, then you'll need to
|
There are some other options that are usually nice to change. See
|
||||||
edit the first line of the file.
|
viewcvs.conf for more information.
|
||||||
|
|
||||||
ViewCVS has not been tested on the Win32 platform.
|
5) That's it. Try it out.
|
||||||
|
|
||||||
3) That's it. Try it out.
|
Warning: ViewCVS has not been tested on web servers operating on the
|
||||||
|
Win32 platform.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -62,3 +70,19 @@ If you've trouble to make viewcvs.cgi work:
|
|||||||
or 'httpd' ..
|
or 'httpd' ..
|
||||||
|
|
||||||
o does viewcvs find your RCS utililties? (edit rcs_path)
|
o does viewcvs find your RCS utililties? (edit rcs_path)
|
||||||
|
|
||||||
|
=== If something else happens or you can't get it to work:
|
||||||
|
|
||||||
|
o check the ViewCVS home page:
|
||||||
|
|
||||||
|
http://www.lyra.org/greg/python/viewcvs/
|
||||||
|
|
||||||
|
o review the ViewCVS mailing list archive to see if somebody else had
|
||||||
|
the same problem, and it was solved:
|
||||||
|
|
||||||
|
http://mailman.lyra.org/pipermail/viewcvs/
|
||||||
|
|
||||||
|
o send mail to the ViewCVS mailing list: viewcvs@lyra.org
|
||||||
|
|
||||||
|
NOTE: make sure you provide an accurate description of the problem
|
||||||
|
and any relevant tracebacks or error logs.
|
||||||
|
42
TODO
Normal file
42
TODO
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
TODO ITEMS
|
||||||
|
----------
|
||||||
|
*) add Tamminen Eero's comments on how to make Linux directly execute
|
||||||
|
the Python script. From email on Feb 19.
|
||||||
|
[ add other examples, such as my /bin/sh hack or the teeny CGI stub
|
||||||
|
importing the bulk hack ]
|
||||||
|
|
||||||
|
*) insert rcs_path into PATH before calling "rcsdiff". rcsdiff might
|
||||||
|
use "co" and needs to find it on the path.
|
||||||
|
|
||||||
|
*) show the "locked" flag (attach it to the LogEntry objects).
|
||||||
|
Idea from Russell Gordon <russell@hoopscotch.dhs.org>
|
||||||
|
|
||||||
|
*) committing with a specific revision number:
|
||||||
|
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
||||||
|
|
||||||
|
*) add an "allowed" modules. process the forbidden modules first. any
|
||||||
|
match will throw it out immediately. if an allowed is present, then
|
||||||
|
disallow any module unless it is present in the allowed list.
|
||||||
|
|
||||||
|
*) provide a virtual host capability. consider multiple hosts which
|
||||||
|
map a particular Location to the same directory (i.e. the same
|
||||||
|
ViewCVS script and config file). provide for a way to have global
|
||||||
|
options plus vhost-specific options.
|
||||||
|
|
||||||
|
*) add capability similar to cvs2cl.pl:
|
||||||
|
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
||||||
|
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
||||||
|
|
||||||
|
|
||||||
|
KNOWN BUGS
|
||||||
|
----------
|
||||||
|
*) from Harri Pasanen, Feb 7: when hideattic is set, files added in a
|
||||||
|
branch are not visible.
|
||||||
|
|
||||||
|
*) from Dieter Deyke, Jan 12: if the CVS revisions differ by just a
|
||||||
|
keyword, then the diff output chokes.
|
||||||
|
|
||||||
|
*) no scroll bar in Netscape browser when you click "as text" on an
|
||||||
|
HTML document.
|
||||||
|
|
||||||
|
*) time.timezone seems to not be available on some 1.5.2 installs
|
529
cgi/granny.cgi
Executable file
529
cgi/granny.cgi
Executable file
@@ -0,0 +1,529 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- Mode: python -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Granny.py - display CVS annotations in HTML
|
||||||
|
with lines colored by code age in days.
|
||||||
|
|
||||||
|
Original Perl version by J. Gabriel Foster (gabe@sgrail.com)
|
||||||
|
Posted to info-cvs on 02/27/1997
|
||||||
|
You can still get the original shell archive here:
|
||||||
|
http://www.geocrawler.com/archives/3/382/1997/2/0/2103824/
|
||||||
|
|
||||||
|
Perl modifications for NT by Marc Paquette (marcpa@cam.org)
|
||||||
|
|
||||||
|
Python port and CGI modifications by Brian Lenihan (brianl@real.com)
|
||||||
|
|
||||||
|
From the original granny.pl README:
|
||||||
|
|
||||||
|
--
|
||||||
|
What is Granny? Why do I care?
|
||||||
|
|
||||||
|
Granny is a tool for viewing cvs annotation information graphically. Using
|
||||||
|
Netscape, granny indicates the age of lines by color. Red lines are new,
|
||||||
|
blue lines are old. This information can be very useful in determining
|
||||||
|
what lines are 'hot'. New lines are more likely to contain bugs, and this
|
||||||
|
is an easy way to visualize that information.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
Netscape (version 2.0 or better)
|
||||||
|
Perl5
|
||||||
|
CVS 1.9 (1.8 should work, but I have not tried it.)
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
Put granny somewhere in your path. You may need to edit the
|
||||||
|
first line to point to your perl5 binary and libraries.
|
||||||
|
|
||||||
|
What to do:
|
||||||
|
Run granny just like you would 'cvs annotate' for a single file.
|
||||||
|
granny thefile.C
|
||||||
|
|
||||||
|
To find out who is to blame for that new 'feature'.
|
||||||
|
granny -U thefile.C
|
||||||
|
|
||||||
|
For all your options:
|
||||||
|
granny -h
|
||||||
|
|
||||||
|
Questions, Comments, Assertions?
|
||||||
|
send e-mail to the author: Gabe Foster (gabe@sgrail.com)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
I'm not the first person to try this sort of display, I just read about it
|
||||||
|
in a magazine somewhere and decided that cvs had all the information
|
||||||
|
I needed. To whomever first had this idea, it's a great one.
|
||||||
|
|
||||||
|
Granny is free, please use it as you see fit. I give no warranties.
|
||||||
|
As a courtesy, I ask that you tell me about any modifications you have made,
|
||||||
|
and also ask that you acknowledge my work if you use Granny in your own
|
||||||
|
software.
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
Granny.py:
|
||||||
|
|
||||||
|
granny.py [-h][-d days][-i input][-o output][-DUV] file
|
||||||
|
|
||||||
|
|
||||||
|
-h: Get this help display.
|
||||||
|
-i: Specify the input file. (Use - for stdin.)
|
||||||
|
-o: Specify the output file. (Use - for stdout.)
|
||||||
|
-d: Specify the day range for the coloring.\n
|
||||||
|
-r: Specify the cvs revision of the file.
|
||||||
|
-D: Display the date the line was last edited.
|
||||||
|
-U Display the user that last edited the line.
|
||||||
|
-V: Display the version the line was last edited.
|
||||||
|
|
||||||
|
By default, granny.py executes cvs annotate on a FILE and
|
||||||
|
runs netscape to display the graphic.
|
||||||
|
|
||||||
|
It is assumed that cvs and Netscape (for command line version) are
|
||||||
|
in your path.
|
||||||
|
|
||||||
|
If granny.py is placed in the cgi-bin directory of your Web
|
||||||
|
server, it will act as a CGI script. The working directory
|
||||||
|
defaults to /usr/tmp, but it can be overridden in the class
|
||||||
|
constructor:
|
||||||
|
|
||||||
|
A = CGIAnnotate(tempdir='/tmp')
|
||||||
|
|
||||||
|
Required fields:
|
||||||
|
|
||||||
|
root The full path to the cvs root directory.
|
||||||
|
Name The module/filename of the annotated file.
|
||||||
|
|
||||||
|
Optional fields:
|
||||||
|
|
||||||
|
rev The cvs revision number to use. (default HEAD).
|
||||||
|
|
||||||
|
Set the following fields to display extra info:
|
||||||
|
|
||||||
|
showUser Display the user that last edited the line.
|
||||||
|
showVersion Display version that the line was last edited in.
|
||||||
|
showDate Display the date the line was last edited.
|
||||||
|
|
||||||
|
http://yourserver.yourdomain.com/cgi-bin/granny.py?root=/cvsroot&Name=module/file
|
||||||
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
Add support for determining the MIME type of files and/or a binary check.
|
||||||
|
- easily done by parsing Apache (mime.conf) or Roxen (extensions) MIME
|
||||||
|
files.
|
||||||
|
Consider adding buttons to HTML for optional display fields.
|
||||||
|
Add support for launching other browsers.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import getopt
|
||||||
|
import cStringIO
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
month_num = {
|
||||||
|
'Jan' : 1, 'Feb' : 2, 'Mar' : 3, 'Apr' : 4, 'May' : 5, 'Jun' : 6,
|
||||||
|
'Jul' : 7, 'Aug' : 8, 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12
|
||||||
|
}
|
||||||
|
|
||||||
|
class Annotate:
|
||||||
|
def __init__(self):
|
||||||
|
self.day_range = 365
|
||||||
|
self.counter = 0
|
||||||
|
self.color_table = {}
|
||||||
|
self.user = {}
|
||||||
|
self.version = {}
|
||||||
|
self.rtime = {}
|
||||||
|
self.source = {}
|
||||||
|
self.tmp = None
|
||||||
|
self.tmpfile = None
|
||||||
|
self.revision = ''
|
||||||
|
self.showUser = 0
|
||||||
|
self.showDate = 0
|
||||||
|
self.showVersion = 0
|
||||||
|
self.set_today()
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.process_args()
|
||||||
|
self.parse_raw_annotated_file()
|
||||||
|
self.write_annotated_html_file()
|
||||||
|
if self.tmp:
|
||||||
|
self.display_annotated_html_file()
|
||||||
|
finally:
|
||||||
|
if sys.exc_info()[0] is not None:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
if self.tmp:
|
||||||
|
self.tmp.close()
|
||||||
|
os.unlink(self.tmpfile)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def getoutput(self, cmd):
|
||||||
|
"""
|
||||||
|
Get stdin and stderr from cmd and
|
||||||
|
return exit status and captured output
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
# os.popen is broken on win32, but seems to work so far...
|
||||||
|
pipe = os.popen('%s 2>&1' % cmd, 'r')
|
||||||
|
else:
|
||||||
|
pipe = os.popen('{ %s ; } 2>&1' % cmd, 'r')
|
||||||
|
text = pipe.read()
|
||||||
|
sts = pipe.close()
|
||||||
|
if sts == None:
|
||||||
|
sts = 0
|
||||||
|
if text[:-1] == '\n':
|
||||||
|
text = text[-1:]
|
||||||
|
return sts, text
|
||||||
|
|
||||||
|
|
||||||
|
def set_today(self):
|
||||||
|
"""
|
||||||
|
compute the start of this day
|
||||||
|
"""
|
||||||
|
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(time.time())
|
||||||
|
self.today = time.mktime((year,mon,day,0,0,0,0,0,0))
|
||||||
|
|
||||||
|
|
||||||
|
def get_today(self):
|
||||||
|
return self.today
|
||||||
|
|
||||||
|
|
||||||
|
# entify stuff which breaks HTML display
|
||||||
|
# this was lifted from some Zope Code in
|
||||||
|
# StructuredText.py
|
||||||
|
#
|
||||||
|
# XXX try it with string.replace and run it in the profiler
|
||||||
|
|
||||||
|
def html_quote(self,v,
|
||||||
|
character_entities=((re.compile('&'), '&'),
|
||||||
|
(re.compile("<"), '<' ),
|
||||||
|
(re.compile(">"), '>' ),
|
||||||
|
(re.compile('"'), '"'))):
|
||||||
|
gsub = re.sub
|
||||||
|
|
||||||
|
text=str(v)
|
||||||
|
for regexp,name in character_entities:
|
||||||
|
text=gsub(regexp,name,text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def display_annotated_html_file(self):
|
||||||
|
if os.name == 'nt':
|
||||||
|
path = '"C:\\Program Files\\Netscape\\Communicator'\
|
||||||
|
'\\Program\\Netscape"'
|
||||||
|
|
||||||
|
if os.system('%s %s' % (path, self.tmpfile)) != 0:
|
||||||
|
sys.stderr.write('%s: Unable to start Netscape' % sys.argv[0])
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
if os.system('netscape -remote openFile\(%s\)' %
|
||||||
|
self.tmpfile) != 0:
|
||||||
|
sys.stderr.write('%s: Trying to run netscape, please wait\n' %
|
||||||
|
sys.argv[0])
|
||||||
|
if os.system('netscape &') == 0:
|
||||||
|
for i in range(10):
|
||||||
|
time.sleep(1)
|
||||||
|
if os.system('netscape -remote openFile\(%s\)' %
|
||||||
|
self.tmpfile) == 0:
|
||||||
|
break
|
||||||
|
if i == 10:
|
||||||
|
sys.stderr.write('%s:Unable to start netscape\n' %
|
||||||
|
sys.argv[0])
|
||||||
|
else:
|
||||||
|
sys.stderr.write('%s:Unable to start netscape\n' %
|
||||||
|
sys.argv[0])
|
||||||
|
|
||||||
|
# give Netscape time to read the file
|
||||||
|
# XXX big files may raise an OSError exception on NT
|
||||||
|
# if the sleep is too short.
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
def get_opts(self):
|
||||||
|
opt_dict = {}
|
||||||
|
if not len(sys.argv[1:]) > 0:
|
||||||
|
self.usage()
|
||||||
|
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'DUVhi:o:d:r:')
|
||||||
|
for k,v in opts:
|
||||||
|
opt_dict[k] = v
|
||||||
|
return opt_dict
|
||||||
|
|
||||||
|
|
||||||
|
def process_args(self):
|
||||||
|
opts = self.get_opts()
|
||||||
|
|
||||||
|
if opts.has_key('-r'):
|
||||||
|
self.revision = '-r%s' % opts['-r']
|
||||||
|
|
||||||
|
if opts.has_key('-h'):
|
||||||
|
self.usage(help=1)
|
||||||
|
|
||||||
|
if opts.has_key('-i'):
|
||||||
|
if opts['-i'] != '-':
|
||||||
|
self.filename = v
|
||||||
|
infile = open(filename, 'r')
|
||||||
|
sys.stdin = infile
|
||||||
|
else:
|
||||||
|
self.file = sys.stdin
|
||||||
|
else:
|
||||||
|
self.filename = sys.argv[len(sys.argv) - 1]
|
||||||
|
|
||||||
|
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
|
||||||
|
|
||||||
|
status, text = self.getoutput(cmd)
|
||||||
|
|
||||||
|
if status != 0 or text == '':
|
||||||
|
sys.stderr.write("Can't run cvs annotate on %s\n" %
|
||||||
|
self.filename)
|
||||||
|
sys.stderr.write('%s\n' % text)
|
||||||
|
sys.exit(1)
|
||||||
|
self.file = cStringIO.StringIO(text)
|
||||||
|
|
||||||
|
|
||||||
|
if opts.has_key('-o'):
|
||||||
|
if opts['-o'] != '-':
|
||||||
|
outfile = open(v, 'w')
|
||||||
|
sys.stdout = outfile
|
||||||
|
else:
|
||||||
|
# this could be done without a temp file
|
||||||
|
target = sys.argv[len(sys.argv) -1]
|
||||||
|
self.tmpfile = tempfile.mktemp()
|
||||||
|
self.tmp = open(self.tmpfile, 'w')
|
||||||
|
sys.stdout = self.tmp
|
||||||
|
|
||||||
|
|
||||||
|
if opts.has_key('-d'):
|
||||||
|
if opts['-d'] > 0:
|
||||||
|
self.day_range = opts['-d']
|
||||||
|
|
||||||
|
if opts.has_key('-D'):
|
||||||
|
self.showDate = 1
|
||||||
|
|
||||||
|
if opts.has_key('-U'):
|
||||||
|
self.showUser = 1
|
||||||
|
|
||||||
|
if opts.has_key('-V'):
|
||||||
|
self.showVersion = 1
|
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_annotated_file(self):
|
||||||
|
ann = re.compile('((\d+\.?)+)\s+\((\w+)\s+(\d\d)-'\
|
||||||
|
'(\w{3})-(\d\d)\): (.*)')
|
||||||
|
|
||||||
|
text = self.file.read()
|
||||||
|
lines = string.split(text, '\n')
|
||||||
|
for line in lines:
|
||||||
|
# Parse an annotate string
|
||||||
|
m = ann.search(line)
|
||||||
|
if m:
|
||||||
|
self.version[self.counter] = m.group(1)
|
||||||
|
self.user[self.counter] = m.group(3)
|
||||||
|
oldtime = self.today - time.mktime((
|
||||||
|
int(m.group(6)),
|
||||||
|
int(month_num[m.group(5)]),
|
||||||
|
int(m.group(4)),0,0,0,0,0,0))
|
||||||
|
|
||||||
|
self.rtime[self.counter] = oldtime / 86400
|
||||||
|
self.source[self.counter] = self.html_quote(m.group(7))
|
||||||
|
else:
|
||||||
|
self.source[self.counter] = self.html_quote(line)
|
||||||
|
pass
|
||||||
|
self.counter = self.counter + 1
|
||||||
|
|
||||||
|
|
||||||
|
def write_annotated_html_file(self):
|
||||||
|
if os.environ.has_key('SCRIPT_NAME'):
|
||||||
|
print 'Status: 200 OK\r\n'
|
||||||
|
print 'Content-type: text/html\r\n\r\n'
|
||||||
|
|
||||||
|
print ('<html><head><title>%s</title></head>\n' \
|
||||||
|
'<body bgcolor="#000000">\n' \
|
||||||
|
'<font color="#FFFFFF"><H1>File %s</H1>\n' \
|
||||||
|
'<H3>Code age in days</H2>' % (self.filename, self.filename))
|
||||||
|
|
||||||
|
for i in range(self.day_range + 1):
|
||||||
|
self.color_table[i] = \
|
||||||
|
self.hsvtorgb(240.0 * i / self.day_range, 1.0, 1.0)
|
||||||
|
|
||||||
|
step = self.day_range / 40
|
||||||
|
if step < 5:
|
||||||
|
step = 1
|
||||||
|
while self.day_range/step > 40:
|
||||||
|
step = step + 1
|
||||||
|
if step >= 5:
|
||||||
|
if step != 5:
|
||||||
|
step = step + 5 - (step % 5)
|
||||||
|
while self.day_range / step > 20:
|
||||||
|
step = step + 5
|
||||||
|
|
||||||
|
for i in range(self.day_range + 1, step):
|
||||||
|
print '<font color=%s>%s ' % (self.color_table[i], i),
|
||||||
|
print '<pre><code>'
|
||||||
|
|
||||||
|
for i in range(self.counter):
|
||||||
|
if self.showUser and self.user.has_key(i):
|
||||||
|
print '%s%s ' % ('<font color=#FFFFFF>',
|
||||||
|
string.ljust(self.user[i],10)),
|
||||||
|
|
||||||
|
if self.showVersion and self.version.has_key(i):
|
||||||
|
print '%s%s ' % ('<font color=#FFFFFF>',
|
||||||
|
string.ljust(self.version[i],6)),
|
||||||
|
|
||||||
|
if self.showDate and self.rtime.has_key(i):
|
||||||
|
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(
|
||||||
|
self.today - self.rtime[i] * 86400)
|
||||||
|
|
||||||
|
print '<font color=#FFFFFF>%02d/%02d/%4d ' % (mon, day, year),
|
||||||
|
|
||||||
|
|
||||||
|
if self.rtime.get(i, self.day_range) < self.day_range:
|
||||||
|
fcolor = self.color_table.get(
|
||||||
|
self.rtime[i],
|
||||||
|
self.color_table[self.day_range])
|
||||||
|
else:
|
||||||
|
fcolor = self.color_table[self.day_range]
|
||||||
|
|
||||||
|
print '<font color=%s> %s' % (fcolor, self.source[i])
|
||||||
|
|
||||||
|
print ('</code></pre>\n' \
|
||||||
|
'<font color=#FFFFFF>\n' \
|
||||||
|
'<H5>Granny original Perl version by' \
|
||||||
|
'<I>J. Gabriel Foster</I>\n' \
|
||||||
|
'<ADDRESS><A HREF=\"mailto:gabe@sgrail.com\">'\
|
||||||
|
'gabe@sgrail.com</A></ADDRESS>\n' \
|
||||||
|
'Python version by <I>Brian Lenihan</I>\n' \
|
||||||
|
'<ADDRESS><A HREF=\"mailto:brianl@real.com\">' \
|
||||||
|
'brianl@real.com</A></ADDRESS>\n' \
|
||||||
|
'</body></html>')
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def hsvtorgb(self,h,s,v):
|
||||||
|
"""
|
||||||
|
a veritable technicolor spew
|
||||||
|
"""
|
||||||
|
if s == 0.0:
|
||||||
|
r = v; g = v; b = v
|
||||||
|
else:
|
||||||
|
if h < 0:
|
||||||
|
h = h + 360.0
|
||||||
|
elif h >= 360.0:
|
||||||
|
h = h - 360.0
|
||||||
|
h = h / 60.0
|
||||||
|
i = int(h)
|
||||||
|
f = h - i
|
||||||
|
|
||||||
|
if s > 1.0:
|
||||||
|
s = 1.0
|
||||||
|
p = v * (1.0 - s)
|
||||||
|
q = v * (1.0 - (s * f))
|
||||||
|
t = v * (1.0 - (s * (1.0 - f)))
|
||||||
|
|
||||||
|
if i == 0: r = v; g = t; b = p
|
||||||
|
elif i == 1: r = q; g = v; b = p
|
||||||
|
elif i == 2: r = p; g = v; b = t
|
||||||
|
elif i == 3: r = p; g = q; b = v
|
||||||
|
elif i == 4: r = t; g = p; b = v
|
||||||
|
elif i == 5: r = v; g = p; b = q
|
||||||
|
|
||||||
|
return '#%02X%02X%02X' % (r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def usage(self, help=None):
|
||||||
|
sys.stderr.write('\nusage: %s %s\n\n' % (
|
||||||
|
sys.argv[0],
|
||||||
|
'[-hDUV][-d days][-i input][-o output][-r rev] FILE')
|
||||||
|
)
|
||||||
|
|
||||||
|
if help is not None:
|
||||||
|
sys.stderr.write(
|
||||||
|
'-h: Get this help display.\n' \
|
||||||
|
'-i: Specify the input file. (Use - for stdin.)\n' \
|
||||||
|
'-o: Specify the output file. (Use - for stdout.)\n' \
|
||||||
|
'-d: Specify the day range for the coloring.\n' \
|
||||||
|
'-r: Specify the cvs revision of the file.\n' \
|
||||||
|
'-D: Display the date the line was last edited.\n' \
|
||||||
|
'-U Display the user that last edited the line.\n' \
|
||||||
|
'-V: Display the version the line was last edited.\n\n' \
|
||||||
|
'By default, %s executes a cvs annotate on a FILE and\n' \
|
||||||
|
'runs netscape to display the graphical ' \
|
||||||
|
'annotation\n' % sys.argv[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CGIAnnotate(Annotate):
|
||||||
|
def __init__(self,tempdir='/usr/tmp'):
|
||||||
|
Annotate.__init__(self)
|
||||||
|
if os.name == 'nt':
|
||||||
|
self.tempdir = os.environ.get('TEMP') or os.environ.get('TMP')
|
||||||
|
else:
|
||||||
|
# XXX need a sanity check here
|
||||||
|
self.tempdir = tempdir
|
||||||
|
os.chdir(self.tempdir)
|
||||||
|
|
||||||
|
def process_args(self):
|
||||||
|
f = cgi.FieldStorage()
|
||||||
|
cvsroot = f['root'].value
|
||||||
|
if f.has_key('showUser'):
|
||||||
|
self.showUser = 1
|
||||||
|
if f.has_key('showDate'):
|
||||||
|
self.showDate = 1
|
||||||
|
if f.has_key('showVersion'):
|
||||||
|
self.showVersion = 1
|
||||||
|
if f.has_key('rev'):
|
||||||
|
self.revision = '-r%s' % f['rev'].value
|
||||||
|
path = f['Name'].value
|
||||||
|
module = os.path.dirname(path)
|
||||||
|
self.workingdir = 'ann.%s' % os.getpid()
|
||||||
|
self.filename = os.path.basename(path)
|
||||||
|
|
||||||
|
os.mkdir(self.workingdir)
|
||||||
|
os.chdir(os.path.join(self.tempdir, self.workingdir))
|
||||||
|
os.system('cvs -d %s co %s' % (cvsroot, path))
|
||||||
|
os.chdir(module)
|
||||||
|
|
||||||
|
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
|
||||||
|
status, text = self.getoutput(cmd)
|
||||||
|
|
||||||
|
if status != 0 or text == '':
|
||||||
|
text = "Can't run cvs annotate on %s\n" % path
|
||||||
|
self.file = cStringIO.StringIO(text)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
os.chdir(self.tempdir)
|
||||||
|
os.system('rm -rf %s' % self.workingdir)
|
||||||
|
|
||||||
|
|
||||||
|
def display_annotated_html_file(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def usage(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if os.environ.has_key('SCRIPT_NAME'):
|
||||||
|
import cgi
|
||||||
|
A = CGIAnnotate()
|
||||||
|
else:
|
||||||
|
A = Annotate()
|
||||||
|
A.run()
|
||||||
|
|
198
cgi/query.cgi
Executable file
198
cgi/query.cgi
Executable file
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# CGI script to process and display queries to CVSdb
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
## BOOTSTRAP
|
||||||
|
import sys, os, string
|
||||||
|
_viewcvs_root = string.strip(open("/etc/viewcvs/root", "r").read())
|
||||||
|
sys.path.append(os.path.join(_viewcvs_root, "lib"))
|
||||||
|
##
|
||||||
|
|
||||||
|
import cgi, time, cvsdbapi
|
||||||
|
|
||||||
|
## tuple of alternating row colors
|
||||||
|
Colors = ("#ccccee", "#ffffff")
|
||||||
|
|
||||||
|
|
||||||
|
def HTMLHeader():
|
||||||
|
print "Content-type: text/html"
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def FormToCheckinQuery(form):
|
||||||
|
query = cvsdbapi.CreateCheckinQuery()
|
||||||
|
|
||||||
|
if form.has_key("repository"):
|
||||||
|
temp = form["repository"].value
|
||||||
|
query.SetRepository(temp)
|
||||||
|
|
||||||
|
if form.has_key("branch"):
|
||||||
|
temp = form["branch"].value
|
||||||
|
query.SetBranch(temp)
|
||||||
|
|
||||||
|
if form.has_key("directory"):
|
||||||
|
temp = form["directory"].value
|
||||||
|
query.SetDirectory(temp)
|
||||||
|
|
||||||
|
if form.has_key("file"):
|
||||||
|
temp = form["file"].value
|
||||||
|
query.SetFile(temp)
|
||||||
|
|
||||||
|
if form.has_key("who"):
|
||||||
|
temp = form["who"].value
|
||||||
|
query.SetAuthor(temp)
|
||||||
|
|
||||||
|
if form.has_key("sortby"):
|
||||||
|
temp = form["sortby"].value
|
||||||
|
if temp == "date":
|
||||||
|
query.SetSortMethod(query.SORT_DATE)
|
||||||
|
elif temp == "author":
|
||||||
|
query.SetSortMethod(query.SORT_AUTHOR)
|
||||||
|
else:
|
||||||
|
query.SetSortMethod(query.SORT_FILE)
|
||||||
|
|
||||||
|
if form.has_key("date"):
|
||||||
|
temp = form["date"].value
|
||||||
|
if temp == "hours":
|
||||||
|
if form.has_key("hours"):
|
||||||
|
hours = string.atoi(form["hours"].value)
|
||||||
|
else:
|
||||||
|
hours = 2
|
||||||
|
query.SetFromDateHoursAgo(hours)
|
||||||
|
elif temp == "day":
|
||||||
|
query.SetFromDateDaysAgo(1)
|
||||||
|
elif temp == "week":
|
||||||
|
query.SetFromDateDaysAgo(7)
|
||||||
|
elif temp == "month":
|
||||||
|
query.SetFromDateDaysAgo(31)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def PrintCommitRow(commit, color):
|
||||||
|
cTime = commit.GetTime()
|
||||||
|
if not cTime:
|
||||||
|
cTime = " ";
|
||||||
|
else:
|
||||||
|
cTime = time.strftime("%y/%m/%d %H:%M", time.localtime(cTime))
|
||||||
|
|
||||||
|
cAuthor = commit.GetAuthor()
|
||||||
|
if not cAuthor:
|
||||||
|
cAuthor = " ";
|
||||||
|
|
||||||
|
cFile = os.path.join(commit.GetDirectory(), commit.GetFile())
|
||||||
|
if not cFile:
|
||||||
|
cFile = " ";
|
||||||
|
|
||||||
|
cRevision = commit.GetRevision()
|
||||||
|
if not cRevision:
|
||||||
|
cRevision = " ";
|
||||||
|
|
||||||
|
cBranch = commit.GetBranch()
|
||||||
|
if not cBranch:
|
||||||
|
cBranch = " ";
|
||||||
|
|
||||||
|
cPlusMinus = '%d/%d' % (commit.GetPlusCount(), commit.GetMinusCount())
|
||||||
|
|
||||||
|
cDescription = commit.GetDescription()
|
||||||
|
if not cDescription:
|
||||||
|
cDescription = " ";
|
||||||
|
else:
|
||||||
|
cDescription = cgi.escape(cDescription)
|
||||||
|
cDescription = string.replace(cDescription, '\n', '<br>')
|
||||||
|
|
||||||
|
print '<tr bgcolor="%s"><td align=left valign=top>%s</td>\
|
||||||
|
<td align=left valign=top>%s</td>\
|
||||||
|
<td align=left valign=top>%s</td>\
|
||||||
|
<td align=left valign=top>%s</td>\
|
||||||
|
<td align=left valign=top>%s</td>\
|
||||||
|
<td aligh=left valign=top>%s</td>\
|
||||||
|
<td align=left valign=top>%s</td></tr>' % (
|
||||||
|
color, cTime, cAuthor, cFile, cRevision, cBranch,
|
||||||
|
cPlusMinus, cDescription)
|
||||||
|
|
||||||
|
|
||||||
|
def PrintCommitRows(commit_list):
|
||||||
|
color_index = 0
|
||||||
|
for commit in commit_list:
|
||||||
|
PrintCommitRow(commit, Colors[color_index])
|
||||||
|
color_index = (color_index + 1) % len(Colors)
|
||||||
|
|
||||||
|
|
||||||
|
g_iColorIndex = 0
|
||||||
|
def CommitCallback(commit):
|
||||||
|
global g_iColorIndex
|
||||||
|
PrintCommitRow(commit, Colors[g_iColorIndex])
|
||||||
|
g_iColorIndex = (g_iColorIndex + 1) % len(Colors)
|
||||||
|
|
||||||
|
|
||||||
|
def RunQuery(query):
|
||||||
|
query.SetCommitCB(CommitCallback)
|
||||||
|
db = cvsdbapi.ConnectDatabaseReadOnly()
|
||||||
|
db.RunQuery(query)
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLTemplate:
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.template = open(filename, 'r').read()
|
||||||
|
|
||||||
|
def Print1(self):
|
||||||
|
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
|
||||||
|
print self.template[:index]
|
||||||
|
|
||||||
|
def Print2(self):
|
||||||
|
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
|
||||||
|
print self.template[index:]
|
||||||
|
|
||||||
|
|
||||||
|
def Main():
|
||||||
|
HTMLHeader()
|
||||||
|
|
||||||
|
template_path = os.path.join(
|
||||||
|
_viewcvs_root, "html-templates", "querytemplate.html")
|
||||||
|
template = HTMLTemplate(template_path)
|
||||||
|
template.Print1()
|
||||||
|
|
||||||
|
form = cgi.FieldStorage()
|
||||||
|
query = FormToCheckinQuery(form)
|
||||||
|
RunQuery(query)
|
||||||
|
|
||||||
|
template.Print2()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Main()
|
59
cgi/queryform.cgi
Executable file
59
cgi/queryform.cgi
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# nothing here yet!
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
## BOOTSTRAP
|
||||||
|
import sys, os, string
|
||||||
|
_viewcvs_root = string.strip(open("/etc/viewcvs/root", "r").read())
|
||||||
|
sys.path.append(os.path.join(_viewcvs_root, "lib"))
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
def HTMLHeader():
|
||||||
|
print "Content-type: text/html"
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def Main():
|
||||||
|
HTMLHeader()
|
||||||
|
template_path = os.path.join(
|
||||||
|
_viewcvs_root, "html-templates", "queryformtemplate.html")
|
||||||
|
print open(template_path, "r").read()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Main()
|
966
cgi/viewcvs.cgi
966
cgi/viewcvs.cgi
File diff suppressed because it is too large
Load Diff
381
cgi/viewcvs.conf.dist
Normal file
381
cgi/viewcvs.conf.dist
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
#---------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Configuration file for ViewCVS
|
||||||
|
#
|
||||||
|
# Information on ViewCVS is located at the following web site:
|
||||||
|
# http://www.lyra.org/viewcvs/
|
||||||
|
#
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# BASIC CONFIGURATION
|
||||||
|
#
|
||||||
|
# For correct operation, you will probably need to change the following
|
||||||
|
# configuration variables:
|
||||||
|
#
|
||||||
|
# cvs_roots
|
||||||
|
# default_root
|
||||||
|
# rcs_path
|
||||||
|
# mime_types_file
|
||||||
|
#
|
||||||
|
# It is usually desirable to change the following variables:
|
||||||
|
#
|
||||||
|
# address
|
||||||
|
# main_title
|
||||||
|
# logo
|
||||||
|
# forbidden
|
||||||
|
#
|
||||||
|
# long_intro
|
||||||
|
# repository_info
|
||||||
|
#
|
||||||
|
# For Python source colorization:
|
||||||
|
#
|
||||||
|
# py2html_path
|
||||||
|
#
|
||||||
|
# If your icons are in a special location:
|
||||||
|
#
|
||||||
|
# icons
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# FORMAT INFORMATION
|
||||||
|
#
|
||||||
|
# This file is delineated by sections, specified in [brackets]. Within each
|
||||||
|
# section, are a number of configuration settings. These settings take the
|
||||||
|
# form of: name = value. Values may be continued on the following line by
|
||||||
|
# indenting the continued line.
|
||||||
|
#
|
||||||
|
# WARNING: indentation *always* means continuation. name=value lines should
|
||||||
|
# always start in column zero.
|
||||||
|
#
|
||||||
|
# Comments should always start in column zero, and are identified with "#".
|
||||||
|
#
|
||||||
|
# Certain configuration settings may have multiple values. These should be
|
||||||
|
# separated by a comma. The settings where this is allowed are noted below.
|
||||||
|
#
|
||||||
|
# Any other setting that requires special syntax is noted at that setting.
|
||||||
|
#
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[general]
|
||||||
|
|
||||||
|
#
|
||||||
|
# This setting specifies each of the CVS roots on your system and assigns
|
||||||
|
# names to them. Each root should be given by a "name: path" value. Multiple
|
||||||
|
# roots should be separated by commas.
|
||||||
|
#
|
||||||
|
cvs_roots =
|
||||||
|
Development : /home/cvsroot
|
||||||
|
|
||||||
|
# this is the name of the default CVS root.
|
||||||
|
default_root = Development
|
||||||
|
|
||||||
|
# uncomment if the RCS binaries are not on the standard path
|
||||||
|
# NOTE: the trailing slash is required!
|
||||||
|
#rcs_path = /usr/bin/
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a pathname to a MIME types file to help viewcvs to guess the
|
||||||
|
# correct MIME type on checkout.
|
||||||
|
#
|
||||||
|
# If you are having problems with the default guess on the MIME type, then
|
||||||
|
# uncomment this option and point it at a MIME type file.
|
||||||
|
#
|
||||||
|
# For example, you can use the mime.types from apache here:
|
||||||
|
#mime_types_file = /usr/local/apache/conf/mime.types
|
||||||
|
|
||||||
|
# This address is shown in the footer. It should contain the CVS maintainer.
|
||||||
|
address = <a href="mailto:gstein@lyra.org">gstein@lyra.org</a>
|
||||||
|
|
||||||
|
# this title is used on the main entry page
|
||||||
|
main_title = CVS Repository
|
||||||
|
|
||||||
|
#
|
||||||
|
# This should contain a list of modules in the repository that should not be
|
||||||
|
# displayed (by default or by explicit path specification).
|
||||||
|
#
|
||||||
|
forbidden =
|
||||||
|
# forbidden = example
|
||||||
|
# forbidden = example1, example2
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[cvsdb]
|
||||||
|
|
||||||
|
host = master
|
||||||
|
database_name = bonsai
|
||||||
|
user = root
|
||||||
|
passwd = ogieta50
|
||||||
|
readonly_user = root
|
||||||
|
readonly_passwd = ogieta50
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[images]
|
||||||
|
#
|
||||||
|
# All images are defined with three values: URL, WIDTH, HEIGHT
|
||||||
|
#
|
||||||
|
|
||||||
|
# this logo will appear on the page
|
||||||
|
logo = /icons/apache_pb.gif, 259, 32
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# color of change-section headings in a diff
|
||||||
|
diff_heading = #99cccc
|
||||||
|
|
||||||
|
# color of "empty" lines
|
||||||
|
diff_empty = #cccccc
|
||||||
|
|
||||||
|
# removed lines
|
||||||
|
diff_remove = #ff9999
|
||||||
|
|
||||||
|
# changed lines
|
||||||
|
diff_change = #99ff99
|
||||||
|
|
||||||
|
# added lines
|
||||||
|
diff_add = #ccccff
|
||||||
|
|
||||||
|
# empty lines in a change block (one part smaller than the other)
|
||||||
|
diff_dark_change = #99cc99
|
||||||
|
|
||||||
|
# even/odd row colors
|
||||||
|
even_odd = #ccccee, #ffffff
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# table header colors (normal and the sorted-by column)
|
||||||
|
column_header_normal = #cccccc
|
||||||
|
column_header_sorted = #88ff88
|
||||||
|
|
||||||
|
# Uncomment the following line for colored borders in tables
|
||||||
|
#table_border = #999999
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[text]
|
||||||
|
|
||||||
|
#
|
||||||
|
# WARNING: if you continue the text onto multiple lines, then make SURE that
|
||||||
|
# you indent the continuations.
|
||||||
|
#
|
||||||
|
|
||||||
|
short_intro =
|
||||||
|
<p>
|
||||||
|
Click on a directory to enter that directory. Click on a file to display
|
||||||
|
its revision history and to get a chance to display diffs between
|
||||||
|
revisions.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
long_intro =
|
||||||
|
<p>
|
||||||
|
This is a WWW interface for CVS Repositories.
|
||||||
|
You can browse the file hierarchy by picking directories
|
||||||
|
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
|
||||||
|
If you pick a file, you will see the revision history
|
||||||
|
for that file.
|
||||||
|
Selecting a revision number will download that revision of
|
||||||
|
the file. There is a link at each revision to display
|
||||||
|
diffs between that revision and the previous one, and
|
||||||
|
a form at the bottom of the page that allows you to
|
||||||
|
display diffs between arbitrary revisions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This script
|
||||||
|
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
|
||||||
|
has been written by Greg Stein
|
||||||
|
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
||||||
|
based on the
|
||||||
|
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
||||||
|
script by Henner Zeller
|
||||||
|
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
||||||
|
it is covered by the
|
||||||
|
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD
|
||||||
|
Licence</a>.
|
||||||
|
If you would like to use this CGI script on your own web server and
|
||||||
|
CVS tree, see Greg's
|
||||||
|
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
|
||||||
|
site</a>.
|
||||||
|
Please send any suggestions, comments, etc. to
|
||||||
|
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
doc_info =
|
||||||
|
<h3>CVS Documentation</h3>
|
||||||
|
<blockquote>
|
||||||
|
<p>
|
||||||
|
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||||
|
User's Guide</a><br>
|
||||||
|
<a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
|
||||||
|
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
|
||||||
|
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet
|
||||||
|
another CVS tutorial (a little old, but nice)</a><br>
|
||||||
|
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but
|
||||||
|
very useful FAQ about CVS</a>
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
repository_info =
|
||||||
|
<!-- insert repository access instructions here -->
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
[options]
|
||||||
|
### DOC
|
||||||
|
|
||||||
|
# sort_by: File sort order
|
||||||
|
# file Sort by filename
|
||||||
|
# rev Sort by revision number
|
||||||
|
# date Sort by commit date
|
||||||
|
# author Sort by author
|
||||||
|
# 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 = 1
|
||||||
|
|
||||||
|
# log_sort: Sort order for CVS logs
|
||||||
|
# date Sort revisions by date
|
||||||
|
# rev Sort revision by revision number
|
||||||
|
# cvs Don't sort them. Same order as CVS/RCS shows them.
|
||||||
|
log_sort = date
|
||||||
|
|
||||||
|
# diff_format: Default diff format
|
||||||
|
# h Human readable
|
||||||
|
# u Unified diff
|
||||||
|
# c Context diff
|
||||||
|
# s Side by side
|
||||||
|
# H Long human readable
|
||||||
|
diff_format = h
|
||||||
|
|
||||||
|
# hide_cvsroot: Don't show the CVSROOT directory
|
||||||
|
# 1 Hide CVSROOT directory
|
||||||
|
# 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
|
||||||
|
hr_breakable = 1
|
||||||
|
|
||||||
|
# give out function names in human readable diffs
|
||||||
|
# this just makes sense if we have C-files, otherwise
|
||||||
|
# diff's heuristic doesn't work well ..
|
||||||
|
# ( '-p' option to diff)
|
||||||
|
hr_funout = 0
|
||||||
|
|
||||||
|
# ignore whitespaces for human readable diffs
|
||||||
|
# (indendation and stuff ..)
|
||||||
|
# ( '-w' option to diff)
|
||||||
|
hr_ignore_white = 1
|
||||||
|
|
||||||
|
# ignore diffs which are caused by
|
||||||
|
# keyword-substitution like $Id - Stuff
|
||||||
|
# ( '-kk' option to rcsdiff)
|
||||||
|
hr_ignore_keyword_subst = 1
|
||||||
|
|
||||||
|
# allow annotation of files.
|
||||||
|
# NOTE: this requires rw-access to the CVSROOT/history file, and rw-access to
|
||||||
|
# the subdirectory to place the lock... so you maybe don't want it
|
||||||
|
# WARNING: this is not yet implemented!!
|
||||||
|
allow_annotate = 0
|
||||||
|
|
||||||
|
# allow pretty-printed version of files
|
||||||
|
allow_markup = 1
|
||||||
|
|
||||||
|
# allow compression with gzip of output if the Browser accepts it
|
||||||
|
# (HTTP_ACCEPT_ENCODING=gzip)
|
||||||
|
# [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
|
||||||
|
|
||||||
|
# The size of this extern window; this size option needs use_java_script
|
||||||
|
# to be defined
|
||||||
|
extern_window_width = 600
|
||||||
|
extern_window_height = 440
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
checkout_magic = 1
|
||||||
|
|
||||||
|
# Show last changelog message for sub directories
|
||||||
|
# The current implementation makes many assumptions and may show the
|
||||||
|
# incorrect file at some times. The main assumption is that the last
|
||||||
|
# modified file has the newest filedate. But some CVS operations
|
||||||
|
# touches the file without even when a new version is't checked in,
|
||||||
|
# and TAG based browsing essientially puts this out of order, unless
|
||||||
|
# the last checkin was on the same tag as you are viewing.
|
||||||
|
# Enable this if you like the feature, but don't rely on correct results.
|
||||||
|
show_subdir_lastmod = 0
|
||||||
|
|
||||||
|
# show a portion of the most recent log entry in directory listings
|
||||||
|
show_logs = 1
|
||||||
|
|
||||||
|
# Show CVS log when viewing file contents
|
||||||
|
show_log_in_markup = 1
|
||||||
|
|
||||||
|
# == Configuration defaults ==
|
||||||
|
# Defaults for configuration variables that shouldn't need
|
||||||
|
# to be configured..
|
||||||
|
allow_version_select = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# If you want to use Marc-Andrew Lemburg's py2html (and Just van Rossum's
|
||||||
|
# PyFontify) to colorize Python files, then you may need to change this
|
||||||
|
# variable to point to their directory location.
|
||||||
|
#
|
||||||
|
# This directory AND the standard Python path will be searched.
|
||||||
|
#
|
||||||
|
py2html_path = .
|
||||||
|
#py2html_path = /usr/local/lib/python1.5/site-python
|
||||||
|
|
||||||
|
# the length to which the most recent log entry should be truncated when
|
||||||
|
# shown in the directory view
|
||||||
|
short_log_len = 80
|
||||||
|
|
||||||
|
table_padding = 2
|
||||||
|
|
||||||
|
diff_font_face = Helvetica,Arial
|
||||||
|
diff_font_size = -1
|
||||||
|
|
||||||
|
# the width of the textinput in the request-diff-form
|
||||||
|
input_text_size = 12
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
81
html-templates/queryformtemplate.html
Normal file
81
html-templates/queryformtemplate.html
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<html>
|
||||||
|
<head><title>CVSdb Query Form</title></head>
|
||||||
|
<body bgcolor="#ffffff">
|
||||||
|
|
||||||
|
<h1>CVSdb Query Form for GNOME CVS Repository</h1>
|
||||||
|
|
||||||
|
<form method=get action='query.cgi'>
|
||||||
|
<table border cellpading=8 cellspacing=0>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>Repository:</td>
|
||||||
|
<td><input type=text name=repository size=25></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>Branch:</td>
|
||||||
|
<td><input type=text name=branch size=25></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>Directory:</td>
|
||||||
|
<td><input type=text name=directory value='' size=45></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>File:</td>
|
||||||
|
<td><input type=text name=file value='' size=45></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>Who:</td>
|
||||||
|
<td><input type=text name=who value='' size=45></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right>Sort By:</td>
|
||||||
|
<td>
|
||||||
|
<select name=sortby>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
<option value="author">Author</option>
|
||||||
|
<option value="file">File</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align=right valign=top><br>Date:</td>
|
||||||
|
<td colspan=2>
|
||||||
|
<table BORDER=0 CELLSPACING=0 CELLPADDING=0>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date checked value=hours></td>
|
||||||
|
<td>
|
||||||
|
In the last <input type=text name=hours value=2 size=4> hours
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=day></td>
|
||||||
|
<td>In the last day</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=week></td>
|
||||||
|
<td>In the last week</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=month></td>
|
||||||
|
<td>In the last month</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type=radio name=date value=all></td>
|
||||||
|
<td>Since the beginning of time</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan=2><input type=submit value='Run Query'></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</html>
|
30
html-templates/querytemplate.html
Normal file
30
html-templates/querytemplate.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<html>
|
||||||
|
<head><title>CVSdb Query</title></head>
|
||||||
|
<body bgcolor="#ffffff">
|
||||||
|
|
||||||
|
<h1>CVSdb Query</h1>
|
||||||
|
|
||||||
|
<table width="100%" border=0 cellspacing=0 cellpadding=2>
|
||||||
|
<tr bgcolor="#88ff88">
|
||||||
|
<th align=left valign=top>Date</th>
|
||||||
|
<th align=left valign=top>Author</th>
|
||||||
|
<th align=left valign=top>File</th>
|
||||||
|
<th align=left valign=top>Revision</th>
|
||||||
|
<th align=left valign=top>Branch</th>
|
||||||
|
<th align=left valign=top>+/-</th>
|
||||||
|
<th align=left valign=top>Description</th>
|
||||||
|
</tr>
|
||||||
|
<!-- INSERT QUERY ROWS -->
|
||||||
|
<tr bgcolor="#88ff88">
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
<th align=left valign=top> </th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
153
lib/PyFontify.py
Normal file
153
lib/PyFontify.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
"""Module to analyze Python source code; for syntax coloring tools.
|
||||||
|
|
||||||
|
Interface:
|
||||||
|
tags = fontify(pytext, searchfrom, searchto)
|
||||||
|
|
||||||
|
The 'pytext' argument is a string containing Python source code.
|
||||||
|
The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext.
|
||||||
|
The returned value is a list of tuples, formatted like this:
|
||||||
|
[('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ]
|
||||||
|
The tuple contents are always like this:
|
||||||
|
(tag, startindex, endindex, sublist)
|
||||||
|
tag is one of 'keyword', 'string', 'comment' or 'identifier'
|
||||||
|
sublist is not used, hence always None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Based on FontText.py by Mitchell S. Chapman,
|
||||||
|
# which was modified by Zachary Roadhouse,
|
||||||
|
# then un-Tk'd by Just van Rossum.
|
||||||
|
# Many thanks for regular expression debugging & authoring are due to:
|
||||||
|
# Tim (the-incredib-ly y'rs) Peters and Cristian Tismer
|
||||||
|
# So, who owns the copyright? ;-) How about this:
|
||||||
|
# Copyright 1996-1997:
|
||||||
|
# Mitchell S. Chapman,
|
||||||
|
# Zachary Roadhouse,
|
||||||
|
# Tim Peters,
|
||||||
|
# Just van Rossum
|
||||||
|
|
||||||
|
__version__ = "0.3.1"
|
||||||
|
|
||||||
|
import string, regex
|
||||||
|
|
||||||
|
# First a little helper, since I don't like to repeat things. (Tismer speaking)
|
||||||
|
import string
|
||||||
|
def replace(where, what, with):
|
||||||
|
return string.join(string.split(where, what), with)
|
||||||
|
|
||||||
|
# This list of keywords is taken from ref/node13.html of the
|
||||||
|
# Python 1.3 HTML documentation. ("access" is intentionally omitted.)
|
||||||
|
keywordsList = [
|
||||||
|
"del", "from", "lambda", "return",
|
||||||
|
"and", "elif", "global", "not", "try",
|
||||||
|
"break", "else", "if", "or", "while",
|
||||||
|
"class", "except", "import", "pass",
|
||||||
|
"continue", "finally", "in", "print",
|
||||||
|
"def", "for", "is", "raise"]
|
||||||
|
|
||||||
|
# Build up a regular expression which will match anything
|
||||||
|
# interesting, including multi-line triple-quoted strings.
|
||||||
|
commentPat = "#.*"
|
||||||
|
|
||||||
|
pat = "q[^\q\n]*\(\\\\[\000-\377][^\q\n]*\)*q"
|
||||||
|
quotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
|
||||||
|
|
||||||
|
# Way to go, Tim!
|
||||||
|
pat = """
|
||||||
|
qqq
|
||||||
|
[^\\q]*
|
||||||
|
\(
|
||||||
|
\( \\\\[\000-\377]
|
||||||
|
\| q
|
||||||
|
\( \\\\[\000-\377]
|
||||||
|
\| [^\\q]
|
||||||
|
\| q
|
||||||
|
\( \\\\[\000-\377]
|
||||||
|
\| [^\\q]
|
||||||
|
\)
|
||||||
|
\)
|
||||||
|
\)
|
||||||
|
[^\\q]*
|
||||||
|
\)*
|
||||||
|
qqq
|
||||||
|
"""
|
||||||
|
pat = string.join(string.split(pat), '') # get rid of whitespace
|
||||||
|
tripleQuotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
|
||||||
|
|
||||||
|
# Build up a regular expression which matches all and only
|
||||||
|
# Python keywords. This will let us skip the uninteresting
|
||||||
|
# identifier references.
|
||||||
|
# nonKeyPat identifies characters which may legally precede
|
||||||
|
# a keyword pattern.
|
||||||
|
nonKeyPat = "\(^\|[^a-zA-Z0-9_.\"']\)"
|
||||||
|
|
||||||
|
keyPat = nonKeyPat + "\("
|
||||||
|
for keyword in keywordsList:
|
||||||
|
keyPat = keyPat + keyword + "\|"
|
||||||
|
keyPat = keyPat[:-2] + "\)" + nonKeyPat
|
||||||
|
|
||||||
|
matchPat = keyPat + "\|" + commentPat + "\|" + tripleQuotePat + "\|" + quotePat
|
||||||
|
matchRE = regex.compile(matchPat)
|
||||||
|
|
||||||
|
idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*" # Ident w. leading whitespace.
|
||||||
|
idRE = regex.compile(idKeyPat)
|
||||||
|
|
||||||
|
|
||||||
|
def fontify(pytext, searchfrom = 0, searchto = None):
|
||||||
|
if searchto is None:
|
||||||
|
searchto = len(pytext)
|
||||||
|
# Cache a few attributes for quicker reference.
|
||||||
|
search = matchRE.search
|
||||||
|
group = matchRE.group
|
||||||
|
idSearch = idRE.search
|
||||||
|
idGroup = idRE.group
|
||||||
|
|
||||||
|
tags = []
|
||||||
|
tags_append = tags.append
|
||||||
|
commentTag = 'comment'
|
||||||
|
stringTag = 'string'
|
||||||
|
keywordTag = 'keyword'
|
||||||
|
identifierTag = 'identifier'
|
||||||
|
|
||||||
|
start = 0
|
||||||
|
end = searchfrom
|
||||||
|
while 1:
|
||||||
|
start = search(pytext, end)
|
||||||
|
if start < 0 or start >= searchto:
|
||||||
|
break # EXIT LOOP
|
||||||
|
match = group(0)
|
||||||
|
end = start + len(match)
|
||||||
|
c = match[0]
|
||||||
|
if c not in "#'\"":
|
||||||
|
# Must have matched a keyword.
|
||||||
|
if start <> searchfrom:
|
||||||
|
# there's still a redundant char before and after it, strip!
|
||||||
|
match = match[1:-1]
|
||||||
|
start = start + 1
|
||||||
|
else:
|
||||||
|
# this is the first keyword in the text.
|
||||||
|
# Only a space at the end.
|
||||||
|
match = match[:-1]
|
||||||
|
end = end - 1
|
||||||
|
tags_append((keywordTag, start, end, None))
|
||||||
|
# If this was a defining keyword, look ahead to the
|
||||||
|
# following identifier.
|
||||||
|
if match in ["def", "class"]:
|
||||||
|
start = idSearch(pytext, end)
|
||||||
|
if start == end:
|
||||||
|
match = idGroup(0)
|
||||||
|
end = start + len(match)
|
||||||
|
tags_append((identifierTag, start, end, None))
|
||||||
|
elif c == "#":
|
||||||
|
tags_append((commentTag, start, end, None))
|
||||||
|
else:
|
||||||
|
tags_append((stringTag, start, end, None))
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def test(path):
|
||||||
|
f = open(path)
|
||||||
|
text = f.read()
|
||||||
|
f.close()
|
||||||
|
tags = fontify(text)
|
||||||
|
for tag, start, end, sublist in tags:
|
||||||
|
print tag, `text[start:end]`
|
48
lib/cfg.py
Normal file
48
lib/cfg.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
class EmptyNameSpace:
|
||||||
|
"Placehoder empty class for nesting namespaces."
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [cvsdb]
|
||||||
|
cvsdb = EmptyNameSpace()
|
||||||
|
cvsdb.host = 'master'
|
||||||
|
cvsdb.database_name = 'bonsai'
|
||||||
|
cvsdb.user = 'root'
|
||||||
|
cvsdb.passwd = 'ogieta50'
|
||||||
|
cvsdb.readonly_user = 'root'
|
||||||
|
cvsdb.readonly_passwd = 'ogieta50'
|
179
lib/commit.py
Normal file
179
lib/commit.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
## the Commit class holds data on one commit, the representation is as
|
||||||
|
## close as possible to how it should be committed and retrieved to the
|
||||||
|
## database engine
|
||||||
|
|
||||||
|
class Commit:
|
||||||
|
|
||||||
|
## static constants for type of commit
|
||||||
|
CHANGE = 0
|
||||||
|
ADD = 1
|
||||||
|
REMOVE = 2
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__directory = ''
|
||||||
|
self.__file = ''
|
||||||
|
self.__repository = ''
|
||||||
|
self.__revision = ''
|
||||||
|
self.__author = ''
|
||||||
|
self.__branch = ''
|
||||||
|
self.__pluscount = ''
|
||||||
|
self.__minuscount = ''
|
||||||
|
self.__description = ''
|
||||||
|
self.__gmt_time = 0.0
|
||||||
|
self.__type = Commit.CHANGE
|
||||||
|
|
||||||
|
def SetRepository(self, repository):
|
||||||
|
## clean up repository path; make sure it doesn't end with a
|
||||||
|
## path seperator
|
||||||
|
while repository[-1] == os.sep:
|
||||||
|
repository = repository[:-1]
|
||||||
|
|
||||||
|
self.__repository = repository
|
||||||
|
|
||||||
|
def GetRepository(self):
|
||||||
|
return self.__repository
|
||||||
|
|
||||||
|
def SetDirectory(self, dir):
|
||||||
|
## clean up directory path; make sure it doesn't begin
|
||||||
|
## or end with a path seperator
|
||||||
|
while dir[0] == os.sep:
|
||||||
|
dir = dir[1:]
|
||||||
|
while dir[-1] == os.sep:
|
||||||
|
dir = dir[:-1]
|
||||||
|
|
||||||
|
self.__directory = dir
|
||||||
|
|
||||||
|
def GetDirectory(self):
|
||||||
|
return self.__directory
|
||||||
|
|
||||||
|
def SetFile(self, file):
|
||||||
|
## clean up filename; make sure it doesn't begin
|
||||||
|
## or end with a path seperator
|
||||||
|
while file[0] == os.sep:
|
||||||
|
file = file[1:]
|
||||||
|
while file[-1] == os.sep:
|
||||||
|
file = file[:-1]
|
||||||
|
|
||||||
|
self.__file = file
|
||||||
|
|
||||||
|
def GetFile(self):
|
||||||
|
return self.__file
|
||||||
|
|
||||||
|
def SetRevision(self, revision):
|
||||||
|
self.__revision = revision
|
||||||
|
|
||||||
|
def GetRevision(self):
|
||||||
|
return self.__revision
|
||||||
|
|
||||||
|
def SetTime(self, gmt_time):
|
||||||
|
self.__gmt_time = float(gmt_time)
|
||||||
|
|
||||||
|
def GetTime(self):
|
||||||
|
return self.__gmt_time
|
||||||
|
|
||||||
|
def SetAuthor(self, author):
|
||||||
|
self.__author = author
|
||||||
|
|
||||||
|
def GetAuthor(self):
|
||||||
|
return self.__author
|
||||||
|
|
||||||
|
def SetBranch(self, branch):
|
||||||
|
if not branch:
|
||||||
|
self.__branch = ''
|
||||||
|
else:
|
||||||
|
self.__branch = branch
|
||||||
|
|
||||||
|
def GetBranch(self):
|
||||||
|
return self.__branch
|
||||||
|
|
||||||
|
def SetPlusCount(self, pluscount):
|
||||||
|
self.__pluscount = pluscount
|
||||||
|
|
||||||
|
def GetPlusCount(self):
|
||||||
|
return self.__pluscount
|
||||||
|
|
||||||
|
def SetMinusCount(self, minuscount):
|
||||||
|
self.__minuscount = minuscount
|
||||||
|
|
||||||
|
def GetMinusCount(self):
|
||||||
|
return self.__minuscount
|
||||||
|
|
||||||
|
def SetDescription(self, description):
|
||||||
|
self.__description = description
|
||||||
|
|
||||||
|
def GetDescription(self):
|
||||||
|
return self.__description
|
||||||
|
|
||||||
|
def SetTypeChange(self):
|
||||||
|
self.__type = Commit.CHANGE
|
||||||
|
|
||||||
|
def SetTypeAdd(self):
|
||||||
|
self.__type = Commit.ADD
|
||||||
|
|
||||||
|
def SetTypeRemove(self):
|
||||||
|
self.__type = Commit.REMOVE
|
||||||
|
|
||||||
|
def GetType(self):
|
||||||
|
return self.__type
|
||||||
|
|
||||||
|
def GetTypeString(self):
|
||||||
|
if self.__type == Commit.CHANGE:
|
||||||
|
return 'Change'
|
||||||
|
elif self.__type == Commit.ADD:
|
||||||
|
return 'Add'
|
||||||
|
elif self.__type == Commit.REMOVE:
|
||||||
|
return 'Remove'
|
||||||
|
|
||||||
|
|
||||||
|
## entrypoints
|
||||||
|
|
||||||
|
def CreateCommit():
|
||||||
|
return Commit()
|
||||||
|
|
||||||
|
|
||||||
|
def PrintCommit(commit):
|
||||||
|
print os.path.join(commit.GetDirectory(), commit.GetFile()),\
|
||||||
|
commit.GetRevision(),\
|
||||||
|
commit.GetAuthor()
|
||||||
|
|
||||||
|
if commit.GetBranch():
|
||||||
|
print commit.GetBranch()
|
||||||
|
print commit.GetDescription()
|
||||||
|
print
|
||||||
|
|
40
lib/compat.py
Normal file
40
lib/compat.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#
|
||||||
|
# ### add license stuff
|
||||||
|
#
|
||||||
|
# compat.py: compatibility functions for operation across Python 1.5.x
|
||||||
|
#
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
#
|
||||||
|
# urllib.urlencode() is new to Python 1.5.2
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
urlencode = urllib.urlencode
|
||||||
|
except AttributeError:
|
||||||
|
def urlencode(dict):
|
||||||
|
"Encode a dictionary as application/x-url-form-encoded."
|
||||||
|
if not dict:
|
||||||
|
return ''
|
||||||
|
quote = urllib.quote_plus
|
||||||
|
keyvalue = [ ]
|
||||||
|
for key, value in dict.items():
|
||||||
|
keyvalue.append(quote(key) + '=' + quote(str(value)))
|
||||||
|
return '?' + string.join(keyvalue, '&')
|
||||||
|
|
||||||
|
#
|
||||||
|
# time.strptime() is new to Python 1.5.2
|
||||||
|
#
|
||||||
|
if hasattr(time, 'strptime'):
|
||||||
|
def cvs_strptime(timestr):
|
||||||
|
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
|
||||||
|
else:
|
||||||
|
_re_rev_date = re.compile('([0-9]{4})/([0-9][0-9])/([0-9][0-9]) '
|
||||||
|
'([0-9][0-9]):([0-9][0-9]):([0-9][0-9])')
|
||||||
|
def cvs_strptime(timestr):
|
||||||
|
matches = _re_rev_date.match(timestr).groups()
|
||||||
|
return tuple(map(int, matches)) + (0, 1, -1)
|
||||||
|
cvs_strptime.__doc__ = 'Parse a CVS-style date/time value.'
|
214
lib/config.py
Normal file
214
lib/config.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#
|
||||||
|
# ### add license stuff
|
||||||
|
#
|
||||||
|
# config.py: configuration utilities
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
#
|
||||||
|
# CONFIGURATION
|
||||||
|
#
|
||||||
|
# There are three forms of configuration:
|
||||||
|
#
|
||||||
|
# 1) copy viewcvs.conf.dist to viewcvs.conf and edit
|
||||||
|
# 2) as (1), but delete all unchanged entries from viewcvs.conf
|
||||||
|
# 3) do not use viewcvs.conf and just edit the defaults in this file
|
||||||
|
#
|
||||||
|
# Most users will want to use (1), but there are slight speed advantages
|
||||||
|
# to the other two options. Note that viewcvs.conf values are a bit easier
|
||||||
|
# to work with since it is raw text, rather than python literal values.
|
||||||
|
#
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
_sections = ('general', 'images', 'options', 'colors', 'text', 'cvsdb')
|
||||||
|
_force_multi_value = ('cvs_roots', 'forbidden', 'even_odd')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
for section in self._sections:
|
||||||
|
setattr(self, section, _sub_config())
|
||||||
|
|
||||||
|
def load_config(self, fname):
|
||||||
|
this_dir = os.path.dirname(sys.argv[0])
|
||||||
|
pathname = os.path.join(this_dir, fname)
|
||||||
|
parser = ConfigParser.ConfigParser()
|
||||||
|
parser.read(pathname)
|
||||||
|
|
||||||
|
for section in self._sections:
|
||||||
|
if not parser.has_section(section):
|
||||||
|
continue
|
||||||
|
|
||||||
|
sc = getattr(self, section)
|
||||||
|
|
||||||
|
for opt in parser.options(section):
|
||||||
|
value = parser.get(section, opt)
|
||||||
|
if opt in self._force_multi_value or section == 'images':
|
||||||
|
value = map(string.strip, string.split(value, ','))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = int(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if opt == 'cvs_roots':
|
||||||
|
roots = { }
|
||||||
|
for root in value:
|
||||||
|
name, path = map(string.strip, string.split(root, ':'))
|
||||||
|
roots[name] = path
|
||||||
|
value = roots
|
||||||
|
setattr(sc, opt, value)
|
||||||
|
|
||||||
|
def set_defaults(self):
|
||||||
|
"Set some default values in the configuration."
|
||||||
|
|
||||||
|
self.general.cvs_roots = {
|
||||||
|
# user-visible-name : path
|
||||||
|
"Development" : "/home/cvsroot",
|
||||||
|
}
|
||||||
|
self.general.default_root = "Development"
|
||||||
|
self.general.rcs_path = ''
|
||||||
|
self.general.mime_types_file = ''
|
||||||
|
self.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
|
||||||
|
self.general.main_title = 'CVS Repository'
|
||||||
|
self.general.forbidden = ()
|
||||||
|
|
||||||
|
self.cvsdb.enabled = 0
|
||||||
|
self.cvsdb.host = ''
|
||||||
|
self.cvsdb.database_name = ''
|
||||||
|
self.cvsdb.user = ''
|
||||||
|
self.cvsdb.passwd = ''
|
||||||
|
self.cvsdb.readonly_user = ''
|
||||||
|
self.cvsdb.readonly_passwd = ''
|
||||||
|
|
||||||
|
self.images.logo = "/icons/apache_pb.gif", 259, 32
|
||||||
|
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"
|
||||||
|
self.colors.diff_remove = "#ff9999"
|
||||||
|
self.colors.diff_change = "#99ff99"
|
||||||
|
self.colors.diff_add = "#ccccff"
|
||||||
|
self.colors.diff_dark_change = "#99cc99"
|
||||||
|
|
||||||
|
self.colors.even_odd = ("#ccccee", "#ffffff")
|
||||||
|
|
||||||
|
self.colors.nav_header = "#9999ee"
|
||||||
|
|
||||||
|
self.colors.text = "#000000"
|
||||||
|
self.colors.background = "#ffffff"
|
||||||
|
self.colors.alt_background = "#eeeeee"
|
||||||
|
|
||||||
|
self.colors.column_header_normal = "#cccccc"
|
||||||
|
self.colors.column_header_sorted = "#88ff88"
|
||||||
|
|
||||||
|
self.colors.table_border = None # no border
|
||||||
|
|
||||||
|
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
|
||||||
|
self.options.hr_ignore_keyword_subst = 1
|
||||||
|
self.options.allow_annotate = 0 ### doesn't work yet!
|
||||||
|
self.options.allow_markup = 1
|
||||||
|
self.options.allow_compress = 1
|
||||||
|
self.options.use_java_script = 1
|
||||||
|
self.options.open_extern_window = 1
|
||||||
|
self.options.extern_window_width = 600
|
||||||
|
self.options.extern_window_height = 440
|
||||||
|
self.options.checkout_magic = 1
|
||||||
|
self.options.show_subdir_lastmod = 0
|
||||||
|
self.options.show_logs = 1
|
||||||
|
self.options.show_log_in_markup = 1
|
||||||
|
self.options.allow_version_select = 1
|
||||||
|
self.options.py2html_path = '.'
|
||||||
|
self.options.short_log_len = 80
|
||||||
|
self.options.table_padding = 2
|
||||||
|
self.options.diff_font_face = 'Helvetica,Arial'
|
||||||
|
self.options.diff_font_size = -1
|
||||||
|
self.options.input_text_size = 12
|
||||||
|
|
||||||
|
self.text.long_intro = """\
|
||||||
|
<p>
|
||||||
|
This is a WWW interface for CVS Repositories.
|
||||||
|
You can browse the file hierarchy by picking directories
|
||||||
|
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
|
||||||
|
If you pick a file, you will see the revision history
|
||||||
|
for that file.
|
||||||
|
Selecting a revision number will download that revision of
|
||||||
|
the file. There is a link at each revision to display
|
||||||
|
diffs between that revision and the previous one, and
|
||||||
|
a form at the bottom of the page that allows you to
|
||||||
|
display diffs between arbitrary revisions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This script
|
||||||
|
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
|
||||||
|
has been written by Greg Stein
|
||||||
|
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
||||||
|
based on the
|
||||||
|
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
||||||
|
script by Henner Zeller
|
||||||
|
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
||||||
|
it is covered by the
|
||||||
|
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-License</a>.
|
||||||
|
If you would like to use this CGI script on your own web server and
|
||||||
|
CVS tree, see Greg's
|
||||||
|
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
|
||||||
|
site</a>.
|
||||||
|
Please send any suggestions, comments, etc. to
|
||||||
|
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
# ' stupid emacs...
|
||||||
|
|
||||||
|
self.text.doc_info = """
|
||||||
|
<h3>CVS Documentation</h3>
|
||||||
|
<blockquote>
|
||||||
|
<p>
|
||||||
|
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||||
|
User's Guide</a><br>
|
||||||
|
<a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
|
||||||
|
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
|
||||||
|
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet another CVS tutorial (a little old, but nice)</a><br>
|
||||||
|
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but very useful FAQ about CVS</a>
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Fill in stuff on (say) anonymous pserver access here. For example, what
|
||||||
|
# access mechanism, login, path, etc should be used.
|
||||||
|
self.text.repository_info = """
|
||||||
|
<!-- insert repository access instructions here -->
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.text.short_intro = """\
|
||||||
|
<p>
|
||||||
|
Click on a directory to enter that directory. Click on a file to display
|
||||||
|
its revision history and to get a chance to display diffs between revisions.
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
138
lib/cvsdbapi.py
Normal file
138
lib/cvsdbapi.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os, cfg, database, rlog, commit
|
||||||
|
|
||||||
|
## error
|
||||||
|
error = 'cvsdbapi error'
|
||||||
|
|
||||||
|
## database
|
||||||
|
CreateCheckinDatabase = database.CreateCheckinDatabase
|
||||||
|
CreateCheckinQuery = database.CreateCheckinQuery
|
||||||
|
|
||||||
|
## rlog
|
||||||
|
GetRLogData = rlog.GetRLogData
|
||||||
|
|
||||||
|
## commit
|
||||||
|
CreateCommit = commit.CreateCommit
|
||||||
|
PrintCommit = commit.PrintCommit
|
||||||
|
|
||||||
|
## cached (active) database connections
|
||||||
|
gCheckinDatabase = None
|
||||||
|
gCheckinDatabaseReadOnly = None
|
||||||
|
|
||||||
|
|
||||||
|
def ConnectDatabaseReadOnly():
|
||||||
|
global gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
if gCheckinDatabaseReadOnly:
|
||||||
|
return gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
gCheckinDatabaseReadOnly = database.CreateCheckinDatabase(
|
||||||
|
cfg.cvsdb.host,
|
||||||
|
cfg.cvsdb.readonly_user,
|
||||||
|
cfg.cvsdb.readonly_passwd,
|
||||||
|
cfg.cvsdb.database_name)
|
||||||
|
|
||||||
|
gCheckinDatabaseReadOnly.Connect()
|
||||||
|
return gCheckinDatabaseReadOnly
|
||||||
|
|
||||||
|
|
||||||
|
def ConnectDatabase():
|
||||||
|
global gCheckinDatabase
|
||||||
|
|
||||||
|
gCheckinDatabase = database.CreateCheckinDatabase(
|
||||||
|
cfg.cvsdb.host,
|
||||||
|
cfg.cvsdb.user,
|
||||||
|
cfg.cvsdb.passwd,
|
||||||
|
cfg.cvsdb.database_name)
|
||||||
|
|
||||||
|
gCheckinDatabase.Connect()
|
||||||
|
return gCheckinDatabase
|
||||||
|
|
||||||
|
|
||||||
|
def RLogDataToCommitList(repository, rlog_data):
|
||||||
|
commit_list = []
|
||||||
|
|
||||||
|
## the filename in rlog_data contains the entire path of the
|
||||||
|
## repository; we strip that out here
|
||||||
|
temp = rlog_data.filename[len(repository):]
|
||||||
|
directory, file = os.path.split(temp)
|
||||||
|
|
||||||
|
for rlog_entry in rlog_data.rlog_entry_list:
|
||||||
|
commit = CreateCommit()
|
||||||
|
commit.SetRepository(repository)
|
||||||
|
commit.SetDirectory(directory)
|
||||||
|
commit.SetFile(file)
|
||||||
|
commit.SetRevision(rlog_entry.revision)
|
||||||
|
commit.SetAuthor(rlog_entry.author)
|
||||||
|
commit.SetDescription(rlog_entry.description)
|
||||||
|
commit.SetTime(rlog_entry.time)
|
||||||
|
commit.SetPlusCount(rlog_entry.pluscount)
|
||||||
|
commit.SetMinusCount(rlog_entry.minuscount)
|
||||||
|
commit.SetBranch(rlog_data.LookupBranch(rlog_entry))
|
||||||
|
|
||||||
|
if rlog_entry.type == rlog_entry.CHANGE:
|
||||||
|
commit.SetTypeChange()
|
||||||
|
elif rlog_entry.type == rlog_entry.ADD:
|
||||||
|
commit.SetTypeAdd()
|
||||||
|
elif rlog_entry.type == rlog_entry.REMOVE:
|
||||||
|
commit.SetTypeRemove()
|
||||||
|
|
||||||
|
commit_list.append(commit)
|
||||||
|
|
||||||
|
return commit_list
|
||||||
|
|
||||||
|
|
||||||
|
def GetCommitListFromRCSFile(repository, filename):
|
||||||
|
try:
|
||||||
|
rlog_data = GetRLogData(filename)
|
||||||
|
except rlog.error, e:
|
||||||
|
raise error, e
|
||||||
|
|
||||||
|
commit_list = RLogDataToCommitList(repository, rlog_data)
|
||||||
|
return commit_list
|
||||||
|
|
||||||
|
|
||||||
|
def GetUnrecordedCommitList(repository, filename):
|
||||||
|
commit_list = GetCommitListFromRCSFile(repository, filename)
|
||||||
|
db = ConnectDatabase()
|
||||||
|
|
||||||
|
unrecorded_commit_list = []
|
||||||
|
for commit in commit_list:
|
||||||
|
result = db.CheckCommit(commit)
|
||||||
|
if not result:
|
||||||
|
unrecorded_commit_list.append(commit)
|
||||||
|
|
||||||
|
return unrecorded_commit_list
|
495
lib/database.py
Normal file
495
lib/database.py
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os, sys, string, time
|
||||||
|
|
||||||
|
## imports from the database API; we re-assign the namespace here so it
|
||||||
|
## is easier to switch databases
|
||||||
|
import MySQLdb
|
||||||
|
DBI = MySQLdb
|
||||||
|
|
||||||
|
from commit import CreateCommit, PrintCommit
|
||||||
|
|
||||||
|
|
||||||
|
## base strings used in SQL querries, these should be static members
|
||||||
|
## of the CheckinDatabase class
|
||||||
|
|
||||||
|
sqlBase = 'SELECT checkins.type, checkins.ci_when,checkins. whoid, checkins.repositoryid, checkins.dirid, checkins.fileid, checkins.revision, checkins.stickytag, checkins.branchid, checkins.addedlines, checkins.removedlines, checkins.descid FROM %s WHERE %s %s'
|
||||||
|
sqlRepository = '(checkins.repositoryid=repositories.id AND repositories.repository="%s")'
|
||||||
|
sqlBranch = '(checkins.branchid=branches.id AND branches.branch="%s")'
|
||||||
|
sqlDirectory = '(checkins.dirid=dirs.id AND dirs.dir LIKE "%s%%")'
|
||||||
|
sqlFile = '(checkins.fileid=files.id AND files.file="%s")'
|
||||||
|
sqlFromDate ='(checkins.ci_when>="%s")'
|
||||||
|
sqlToDate = '(checkins.ci_when<="%s")'
|
||||||
|
sqlAuthor = '(checkins.whoid=people.id AND people.who="%s")'
|
||||||
|
sqlSortByDate = 'ORDER BY checkins.ci_when DESC'
|
||||||
|
sqlSortByAuthor = 'ORDER BY checkins.whoid'
|
||||||
|
sqlSortByFile = 'ORDER BY checkins.fileid'
|
||||||
|
sqlExcludeVersionFiles = '(checkins.fileid=files.id AND files.file NOT LIKE "%%.ver")'
|
||||||
|
sqlCheckCommit = 'SELECT * FROM checkins WHERE checkins.repositoryid=%s AND checkins.dirid=%s AND checkins.fileid=%s AND checkins.revision=%s'
|
||||||
|
|
||||||
|
## CheckinDatabase provides all interfaces needed to the SQL database
|
||||||
|
## back-end; it needs to be subclassed, and have its "Connect" method
|
||||||
|
## defined to actually be complete; it should run well off of any DBI 2.0
|
||||||
|
## complient database interface
|
||||||
|
|
||||||
|
class CheckinDatabase:
|
||||||
|
def __init__(self, host, user, passwd, database):
|
||||||
|
self.dbHost = host
|
||||||
|
self.dbUser = user
|
||||||
|
self.dbPasswd = passwd
|
||||||
|
self.dbDatabase = database
|
||||||
|
|
||||||
|
## cache Value lookups
|
||||||
|
self.dbGetCache = {}
|
||||||
|
self.dbGetIDCache = {}
|
||||||
|
self.dbDescriptionIDCache = {}
|
||||||
|
|
||||||
|
def Connect(self):
|
||||||
|
self.dbConn = self.SQLConnect()
|
||||||
|
|
||||||
|
def SQLGetID(self, table, field, identifier, auto_set):
|
||||||
|
sql = 'SELECT id FROM %s x WHERE x.%s="%s"' % (
|
||||||
|
table, field, identifier)
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
if not auto_set:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## insert the new identifier
|
||||||
|
sql = 'INSERT INTO %s (%s) VALUES ("%s")' % (table, field, identifier)
|
||||||
|
cursor.execute(sql)
|
||||||
|
return self.SQLGetID(table, field, identifier, 0)
|
||||||
|
|
||||||
|
def GetID(self, table, field, identifier, auto_set):
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
try:
|
||||||
|
return self.dbGetIDCache[table][field][identifier]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
id = self.SQLGetID(table, field, identifier, auto_set)
|
||||||
|
if not id:
|
||||||
|
return id
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
if not self.dbGetIDCache.has_key(table):
|
||||||
|
self.dbGetIDCache[table] = {}
|
||||||
|
if not self.dbGetIDCache[table].has_key(field):
|
||||||
|
self.dbGetIDCache[table][field] = {}
|
||||||
|
self.dbGetIDCache[table][field][identifier] = id
|
||||||
|
return id
|
||||||
|
|
||||||
|
def SQLGet(self, table, field, id):
|
||||||
|
sql = 'SELECT %s FROM %s x WHERE x.id="%s"' % (field, table, id)
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
def Get(self, table, field, id):
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
try:
|
||||||
|
return self.dbGetCache[table][field][id]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
value = self.SQLGet(table, field, id)
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
if not self.dbGetCache.has_key(table):
|
||||||
|
self.dbGetCache[table] = {}
|
||||||
|
if not self.dbGetCache[table].has_key(field):
|
||||||
|
self.dbGetCache[table][field] = {}
|
||||||
|
self.dbGetCache[table][field][id] = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def GetBranchID(self, branch, auto_set = 1):
|
||||||
|
return self.GetID('branches', 'branch', branch, auto_set)
|
||||||
|
|
||||||
|
def GetBranch(self, id):
|
||||||
|
return self.Get('branches', 'branch', id)
|
||||||
|
|
||||||
|
def GetDirectoryID(self, dir, auto_set = 1):
|
||||||
|
return self.GetID('dirs', 'dir', dir, auto_set)
|
||||||
|
|
||||||
|
def GetDirectory(self, id):
|
||||||
|
return self.Get('dirs', 'dir', id)
|
||||||
|
|
||||||
|
def GetFileID(self, file, auto_set = 1):
|
||||||
|
return self.GetID('files', 'file', file, auto_set)
|
||||||
|
|
||||||
|
def GetFile(self, id):
|
||||||
|
return self.Get('files', 'file', id)
|
||||||
|
|
||||||
|
def GetAuthorID(self, author, auto_set = 1):
|
||||||
|
return self.GetID('people', 'who', author, auto_set)
|
||||||
|
|
||||||
|
def GetAuthor(self, id):
|
||||||
|
return self.Get('people', 'who', id)
|
||||||
|
|
||||||
|
def GetRepositoryID(self, repository, auto_set = 1):
|
||||||
|
return self.GetID('repositories', 'repository', repository, auto_set)
|
||||||
|
|
||||||
|
def GetRepository(self, id):
|
||||||
|
return self.Get('repositories', 'repository', id)
|
||||||
|
|
||||||
|
def SQLGetDescriptionID(self, description, auto_set = 1):
|
||||||
|
## lame string hash, blame Netscape -JMP
|
||||||
|
hash = len(description)
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
'SELECT id FROM descs WHERE hash=%s and description=%s',
|
||||||
|
(hash, description))
|
||||||
|
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
if not auto_set:
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
'INSERT INTO descs (hash, description) values (%s, %s)',
|
||||||
|
(hash, description))
|
||||||
|
|
||||||
|
return self.GetDescriptionID(description, 0)
|
||||||
|
|
||||||
|
def GetDescriptionID(self, description, auto_set = 1):
|
||||||
|
## lame string hash, blame Netscape -JMP
|
||||||
|
hash = len(description)
|
||||||
|
|
||||||
|
## attempt to retrieve from cache
|
||||||
|
try:
|
||||||
|
return self.dbDescriptionIDCache[hash][description]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
id = self.SQLGetDescriptionID(description, auto_set)
|
||||||
|
if not id:
|
||||||
|
return id
|
||||||
|
|
||||||
|
## add to cache
|
||||||
|
if not self.dbDescriptionIDCache.has_key(hash):
|
||||||
|
self.dbDescriptionIDCache[hash] = {}
|
||||||
|
self.dbDescriptionIDCache[hash][description] = id
|
||||||
|
return id
|
||||||
|
|
||||||
|
def GetDescription(self, id):
|
||||||
|
return self.Get('descs', 'description', id)
|
||||||
|
|
||||||
|
def GetList(self, table, field_index):
|
||||||
|
sql = 'SELECT * FROM %s' % (table)
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
list = []
|
||||||
|
while 1:
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
break
|
||||||
|
list.append(row[field_index])
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
def GetRepositoryList(self):
|
||||||
|
return self.GetList('repositories', 1)
|
||||||
|
|
||||||
|
def GetBranchList(self):
|
||||||
|
return self.GetList('branches', 1)
|
||||||
|
|
||||||
|
def GetAuthorList(self):
|
||||||
|
return self.GetList('people', 1)
|
||||||
|
|
||||||
|
def AddCommitList(self, commit_list):
|
||||||
|
for commit in commit_list:
|
||||||
|
self.AddCommit(commit)
|
||||||
|
|
||||||
|
def AddCommit(self, commit):
|
||||||
|
dbType = commit.GetTypeString()
|
||||||
|
|
||||||
|
## MORE TIME HELL: the MySQLdb module doesn't construct times
|
||||||
|
## correctly when created with TimestampFromTicks -- it doesn't
|
||||||
|
## account for daylight savings time, so we use Python's time
|
||||||
|
## module to do the conversion
|
||||||
|
temp = time.localtime(commit.GetTime())
|
||||||
|
|
||||||
|
dbCI_When = DBI.Timestamp(
|
||||||
|
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
|
||||||
|
|
||||||
|
dbWhoID = self.GetAuthorID(commit.GetAuthor())
|
||||||
|
dbRepositoryID = self.GetRepositoryID(commit.GetRepository())
|
||||||
|
dbDirectoryID = self.GetDirectoryID(commit.GetDirectory())
|
||||||
|
dbFileID = self.GetFileID(commit.GetFile())
|
||||||
|
dbRevision = commit.GetRevision()
|
||||||
|
dbStickyTag = 'NULL'
|
||||||
|
dbBranchID = self.GetBranchID(commit.GetBranch())
|
||||||
|
dbPlusCount = commit.GetPlusCount()
|
||||||
|
dbMinusCount = commit.GetMinusCount()
|
||||||
|
dbDescriptionID = self.GetDescriptionID(commit.GetDescription())
|
||||||
|
|
||||||
|
sql = 'REPLACE INTO checkins(type, ci_when, whoid, repositoryid, dirid, fileid, revision, stickytag, branchid, addedlines, removedlines, descid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)'
|
||||||
|
sqlArguments = (
|
||||||
|
dbType, dbCI_When, dbWhoID, dbRepositoryID, dbDirectoryID,
|
||||||
|
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbPlusCount,
|
||||||
|
dbMinusCount, dbDescriptionID)
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sql, sqlArguments)
|
||||||
|
|
||||||
|
def CreateSQLQueryString(self, query):
|
||||||
|
tableList = ['checkins']
|
||||||
|
condList = []
|
||||||
|
|
||||||
|
tableList.append('files')
|
||||||
|
condList.append(sqlExcludeVersionFiles)
|
||||||
|
|
||||||
|
if query.repository:
|
||||||
|
tableList.append('repositories')
|
||||||
|
condList.append(sqlRepository % (query.repository))
|
||||||
|
|
||||||
|
if query.branch:
|
||||||
|
tableList.append('branches')
|
||||||
|
condList.append(sqlBranch % (query.branch))
|
||||||
|
|
||||||
|
if query.from_date:
|
||||||
|
condList.append(sqlFromDate % (str(query.from_date)))
|
||||||
|
|
||||||
|
if query.to_date:
|
||||||
|
condList.append(sqlToDate % (str(query.to_date)))
|
||||||
|
|
||||||
|
if query.author:
|
||||||
|
tableList.append('people')
|
||||||
|
condList.append(sqlAuthor % (query.author))
|
||||||
|
|
||||||
|
if query.directory:
|
||||||
|
tableList.append('dirs')
|
||||||
|
condList.append(sqlDirectory % (query.directory))
|
||||||
|
|
||||||
|
if query.file:
|
||||||
|
#tableList.append('files')
|
||||||
|
condList.append(sqlFile % (query.file))
|
||||||
|
|
||||||
|
if query.sort == query.SORT_DATE:
|
||||||
|
order_by = sqlSortByDate
|
||||||
|
elif query.sort == query.SORT_AUTHOR:
|
||||||
|
order_by = sqlSortByAuthor
|
||||||
|
elif query.sort == query.SORT_FILE:
|
||||||
|
order_by = sqlSortByFile
|
||||||
|
|
||||||
|
sql = sqlBase % (
|
||||||
|
string.join(tableList, ', '),
|
||||||
|
string.join(condList, ' AND '),
|
||||||
|
order_by)
|
||||||
|
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def RunQuery(self, query):
|
||||||
|
sql = self.CreateSQLQueryString(query)
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
break
|
||||||
|
|
||||||
|
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
|
||||||
|
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
|
||||||
|
dbRemovedLines, dbDescID) = row
|
||||||
|
|
||||||
|
commit = CreateCommit()
|
||||||
|
|
||||||
|
## TIME, TIME, TIME is all fucked up; dateobject.gmticks()
|
||||||
|
## is broken, dateobject.ticks() returns somthing like
|
||||||
|
## GMT ticks, except it forgets about daylight savings
|
||||||
|
## time -- we handle it ourself in the following painful way
|
||||||
|
gmt_time = time.mktime(
|
||||||
|
(dbCI_When.year, dbCI_When.month, dbCI_When.day,
|
||||||
|
dbCI_When.hour, dbCI_When.minute, dbCI_When.second,
|
||||||
|
0, 0, dbCI_When.dst))
|
||||||
|
|
||||||
|
commit.SetTime(gmt_time)
|
||||||
|
|
||||||
|
commit.SetFile(self.GetFile(dbFileID))
|
||||||
|
commit.SetDirectory(self.GetDirectory(dbDirID))
|
||||||
|
commit.SetRevision(dbRevision)
|
||||||
|
commit.SetRepository(self.GetRepository(dbRepositoryID))
|
||||||
|
commit.SetAuthor(self.GetAuthor(dbAuthorID))
|
||||||
|
commit.SetBranch(self.GetBranch(dbBranchID))
|
||||||
|
commit.SetPlusCount(dbAddedLines)
|
||||||
|
commit.SetMinusCount(dbRemovedLines)
|
||||||
|
commit.SetDescription(self.GetDescription(dbDescID))
|
||||||
|
|
||||||
|
query.AddCommit(commit)
|
||||||
|
|
||||||
|
def CheckCommit(self, commit):
|
||||||
|
dbRepositoryID = self.GetRepositoryID(commit.GetRepository(), 0)
|
||||||
|
if dbRepositoryID == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dbDirID = self.GetDirectoryID(commit.GetDirectory(), 0)
|
||||||
|
if dbDirID == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dbFileID = self.GetFileID(commit.GetFile(), 0)
|
||||||
|
if dbFileID == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
sqlArguments = (dbRepositoryID, dbDirID, dbFileID,
|
||||||
|
commit.GetRevision())
|
||||||
|
|
||||||
|
cursor = self.dbConn.cursor()
|
||||||
|
cursor.execute(sqlCheckCommit, sqlArguments)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return commit
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLCheckinDatabase(CheckinDatabase):
|
||||||
|
def SQLConnect(self):
|
||||||
|
return MySQLdb.connect(
|
||||||
|
host = self.dbHost,
|
||||||
|
user = self.dbUser,
|
||||||
|
passwd = self.dbPasswd,
|
||||||
|
db = self.dbDatabase)
|
||||||
|
|
||||||
|
|
||||||
|
## CheckinDatabaseQueryData is a object which contains the search parameters
|
||||||
|
## for a query to the CheckinDatabase
|
||||||
|
|
||||||
|
class CheckinDatabaseQuery:
|
||||||
|
|
||||||
|
SORT_DATE = 0
|
||||||
|
SORT_AUTHOR = 1
|
||||||
|
SORT_FILE = 2
|
||||||
|
SORT_CHANGESIZE = 3
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
## repository to query
|
||||||
|
self.repository = None
|
||||||
|
|
||||||
|
## branch
|
||||||
|
self.branch = None
|
||||||
|
|
||||||
|
## directory to seach
|
||||||
|
self.directory = None
|
||||||
|
|
||||||
|
## file to search for
|
||||||
|
self.file = None
|
||||||
|
|
||||||
|
## sorting method
|
||||||
|
self.sort = CheckinDatabaseQuery.SORT_DATE;
|
||||||
|
|
||||||
|
## author to search for
|
||||||
|
self.author = None
|
||||||
|
|
||||||
|
## date range in DBI 2.0 timedate objects
|
||||||
|
self.from_date = None
|
||||||
|
self.to_date = None
|
||||||
|
|
||||||
|
## list of commits -- filled in by CVS query
|
||||||
|
self.commit_list = []
|
||||||
|
|
||||||
|
## commit_cb provides a callback for commits as they
|
||||||
|
## are added
|
||||||
|
self.commit_cb = None
|
||||||
|
|
||||||
|
def SetRepository(self, repository):
|
||||||
|
self.repository = repository
|
||||||
|
|
||||||
|
def SetBranch(self, branch):
|
||||||
|
self.branch = branch
|
||||||
|
|
||||||
|
def SetDirectory(self, directory):
|
||||||
|
self.directory = directory
|
||||||
|
|
||||||
|
def SetFile(self, file):
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
def SetSortMethod(self, sort):
|
||||||
|
self.sort = sort
|
||||||
|
|
||||||
|
def SetAuthor(self, author):
|
||||||
|
self.author = author
|
||||||
|
|
||||||
|
def SetFromDateObject(self, date):
|
||||||
|
self.from_date = date
|
||||||
|
|
||||||
|
def SetToDateObject(self, date):
|
||||||
|
self.to_date = date
|
||||||
|
|
||||||
|
def SetFromDateHoursAgo(self, hours_ago):
|
||||||
|
ticks = time.time() - (3600 * hours_ago)
|
||||||
|
self.from_date = DBI.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetFromDateDaysAgo(self, days_ago):
|
||||||
|
ticks = time.time() - (86400 * days_ago)
|
||||||
|
self.from_date = DBI.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def SetToDateDaysAgo(self, days_ago):
|
||||||
|
ticks = time.time() - (86400 * days_ago)
|
||||||
|
self.to_date = DBI.TimestampFromTicks(ticks)
|
||||||
|
|
||||||
|
def AddCommit(self, commit):
|
||||||
|
self.commit_list.append(commit)
|
||||||
|
if self.commit_cb:
|
||||||
|
self.commit_cb(commit)
|
||||||
|
|
||||||
|
def SetCommitCB(self, callback):
|
||||||
|
self.commit_cb = callback
|
||||||
|
|
||||||
|
|
||||||
|
## entrypoints
|
||||||
|
|
||||||
|
def CreateCheckinDatabase(host, user, passwd, database):
|
||||||
|
return MySQLCheckinDatabase(host, user, passwd, database)
|
||||||
|
|
||||||
|
def CreateCheckinQuery():
|
||||||
|
return CheckinDatabaseQuery()
|
489
lib/py2html.py
Normal file
489
lib/py2html.py
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
#!/usr/local/bin/python -u
|
||||||
|
|
||||||
|
""" Python Highlighter for HTML Version: 0.5
|
||||||
|
|
||||||
|
py2html.py [options] files...
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h print help
|
||||||
|
- read from stdin, write to stdout
|
||||||
|
-stdout read from files, write to stdout
|
||||||
|
-files read from files, write to filename+'.html' (default)
|
||||||
|
-format:
|
||||||
|
html output HTML page (default)
|
||||||
|
rawhtml output pure HTML (without headers, titles, etc.)
|
||||||
|
-mode:
|
||||||
|
color output in color (default)
|
||||||
|
mono output b/w (for printing)
|
||||||
|
-title:Title use 'Title' as title of the generated page
|
||||||
|
-bgcolor:color use color as background-color for page
|
||||||
|
-header:file use contents of file as header
|
||||||
|
-footer:file use contents of file as footer
|
||||||
|
-URL replace all occurances of 'URL: link' with
|
||||||
|
'<A HREF="link">link</A>'; this is always enabled
|
||||||
|
in CGI mode
|
||||||
|
-v verbose
|
||||||
|
|
||||||
|
Takes the input, assuming it is Python code and formats it into
|
||||||
|
colored HTML. When called without parameters the script tries to
|
||||||
|
work in CGI mode. It looks for a field 'script=URL' and tries to
|
||||||
|
use that URL as input file. If it can't find this field, the path
|
||||||
|
info (the part of the URL following the CGI script name) is
|
||||||
|
tried. In case no host is given, the host where the CGI script
|
||||||
|
lives and HTTP are used.
|
||||||
|
|
||||||
|
* Uses Just van Rossum's PyFontify version 0.3 to tag Python scripts.
|
||||||
|
You can get it via his homepage on starship:
|
||||||
|
URL: http://starship.skyport.net/crew/just
|
||||||
|
"""
|
||||||
|
__comments__ = """
|
||||||
|
|
||||||
|
The following snippet is a small shell script I use for viewing
|
||||||
|
Python scripts per less on Unix:
|
||||||
|
#!/bin/sh
|
||||||
|
# Browse pretty printed Python code using ANSI codes for highlighting
|
||||||
|
py2html -stdout -format:ansi -mode:mono $* | less -r
|
||||||
|
|
||||||
|
History:
|
||||||
|
|
||||||
|
0.5: Added a few suggestions by Kevin Ng to make the CGI version
|
||||||
|
a little more robust.
|
||||||
|
|
||||||
|
"""
|
||||||
|
__copyright__ = """
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
(c) Copyright by Marc-Andre Lemburg, 1998 (mailto:mal@lemburg.com)
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software and its
|
||||||
|
documentation for any purpose and without fee or royalty is hereby granted,
|
||||||
|
provided that the above copyright notice appear in all copies and that
|
||||||
|
both that copyright notice and this permission notice appear in
|
||||||
|
supporting documentation or portions thereof, including modifications,
|
||||||
|
that you make.
|
||||||
|
|
||||||
|
THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||||
|
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||||
|
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||||
|
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||||
|
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = '0.5'
|
||||||
|
|
||||||
|
__cgifooter__ = ('\n<PRE># code highlighted using <A HREF='
|
||||||
|
'"http://starship.skyport.net/~lemburg/">py2html.py</A> '
|
||||||
|
'version %s</PRE>\n' % __version__)
|
||||||
|
|
||||||
|
import sys,string,re
|
||||||
|
|
||||||
|
# Adjust path so that PyFontify is found...
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
|
||||||
|
# URL of the input form the user is redirected to in case no script=xxx
|
||||||
|
# form field is given. The URL *must* be absolute. Leave blank to
|
||||||
|
# have the script issue an error instead.
|
||||||
|
INPUT_FORM = 'http://starship.skyport.net/~lemburg/SoftwareDescriptions.html#py2html.py'
|
||||||
|
|
||||||
|
### Helpers
|
||||||
|
|
||||||
|
def fileio(file, mode='rb', data=None, close=0):
|
||||||
|
|
||||||
|
if type(file) == type(''):
|
||||||
|
f = open(file,mode)
|
||||||
|
close = 1
|
||||||
|
else:
|
||||||
|
f = file
|
||||||
|
if data:
|
||||||
|
f.write(data)
|
||||||
|
else:
|
||||||
|
data = f.read()
|
||||||
|
if close: f.close()
|
||||||
|
return data
|
||||||
|
|
||||||
|
### Converter class
|
||||||
|
|
||||||
|
class PrettyPrint:
|
||||||
|
|
||||||
|
""" generic Pretty Printer class
|
||||||
|
|
||||||
|
* supports tagging Python scripts in the following ways:
|
||||||
|
|
||||||
|
# format/mode | color mono
|
||||||
|
# --------------------------
|
||||||
|
# rawhtml | x x (HTML without headers, etc.)
|
||||||
|
# html | x x (a HTML page with HEAD&BODY:)
|
||||||
|
# ansi | x (with Ansi-escape sequences)
|
||||||
|
|
||||||
|
* interfaces:
|
||||||
|
|
||||||
|
file_filter -- takes two files: input & output (may be stdin/stdout)
|
||||||
|
filter -- takes a string and returns the highlighted version
|
||||||
|
|
||||||
|
* to create an instance use:
|
||||||
|
|
||||||
|
c = PrettyPrint(tagfct,format,mode)
|
||||||
|
|
||||||
|
where format and mode must be strings according to the
|
||||||
|
above table if you plan to use PyFontify.fontify as
|
||||||
|
tagfct
|
||||||
|
|
||||||
|
* the tagfct has to take one argument, text, and return a taglist
|
||||||
|
(format: [(id,left,right,sublist),...], where id is the
|
||||||
|
"name" given to the slice left:right in text and sublist is a
|
||||||
|
taglist for tags inside the slice or None)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# misc settings
|
||||||
|
title = ''
|
||||||
|
bgcolor = '#FFFFFF'
|
||||||
|
header = ''
|
||||||
|
footer = ''
|
||||||
|
replace_URLs = 0
|
||||||
|
# formats to be used
|
||||||
|
formats = {}
|
||||||
|
|
||||||
|
def __init__(self,tagfct=None,format='html',mode='color'):
|
||||||
|
|
||||||
|
self.tag = tagfct
|
||||||
|
self.set_mode = getattr(self,'set_mode_'+format+'_'+mode)
|
||||||
|
self.filter = getattr(self,'filter_'+format)
|
||||||
|
self.set_mode()
|
||||||
|
|
||||||
|
def file_filter(self,infile,outfile):
|
||||||
|
|
||||||
|
text = fileio(infile,'r')
|
||||||
|
if type(infile) == type('') and self.title == '':
|
||||||
|
self.title = infile
|
||||||
|
fileio(outfile,'w',self.filter(text))
|
||||||
|
|
||||||
|
### set pre- and postfixes for formats & modes
|
||||||
|
#
|
||||||
|
# These methods must set self.formats to a dictionary having
|
||||||
|
# an entry for every tag returned by the tagging function.
|
||||||
|
#
|
||||||
|
# The format used is simple:
|
||||||
|
# tag:(prefix,postfix)
|
||||||
|
# where prefix and postfix are either strings or callable objects,
|
||||||
|
# that return a string (they are called with the matching tag text
|
||||||
|
# as only parameter). prefix is inserted in front of the tag, postfix
|
||||||
|
# is inserted right after the tag.
|
||||||
|
|
||||||
|
def set_mode_html_color(self):
|
||||||
|
|
||||||
|
self.formats = {
|
||||||
|
'all':('<PRE>','</PRE>'),
|
||||||
|
'comment':('<FONT COLOR=#1111CC>','</FONT>'),
|
||||||
|
'keyword':('<FONT COLOR=#3333CC><B>','</B></FONT>'),
|
||||||
|
'parameter':('<FONT COLOR=#000066>','</FONT>'),
|
||||||
|
'identifier':( lambda x,strip=string.strip:
|
||||||
|
'<A NAME="%s"><FONT COLOR=#CC0000><B>' % (strip(x)),
|
||||||
|
'</B></FONT></A>'),
|
||||||
|
'string':('<FONT COLOR=#115511>','</FONT>')
|
||||||
|
}
|
||||||
|
|
||||||
|
set_mode_rawhtml_color = set_mode_html_color
|
||||||
|
|
||||||
|
def set_mode_html_mono(self):
|
||||||
|
|
||||||
|
self.formats = {
|
||||||
|
'all':('<PRE>','</PRE>'),
|
||||||
|
'comment':('',''),
|
||||||
|
'keyword':( '<U>','</U>'),
|
||||||
|
'parameter':('',''),
|
||||||
|
'identifier':( lambda x,strip=string.strip:
|
||||||
|
'<A NAME="%s"><B>' % (strip(x)),
|
||||||
|
'</B>'),
|
||||||
|
'string':('','')
|
||||||
|
}
|
||||||
|
|
||||||
|
set_mode_rawhtml_mono = set_mode_html_mono
|
||||||
|
|
||||||
|
def set_mode_ansi_mono(self):
|
||||||
|
|
||||||
|
self.formats = {
|
||||||
|
'all':('',''),
|
||||||
|
'comment':('\033[2m','\033[m'),
|
||||||
|
'keyword':('\033[4m','\033[m'),
|
||||||
|
'parameter':('',''),
|
||||||
|
'identifier':('\033[1m','\033[m'),
|
||||||
|
'string':('','')
|
||||||
|
}
|
||||||
|
|
||||||
|
### filter for Python scripts given as string
|
||||||
|
|
||||||
|
def escape_html(self,text):
|
||||||
|
|
||||||
|
t = (('<','<'),('>','>'))
|
||||||
|
for x,y in t:
|
||||||
|
text = string.join(string.split(text,x),y)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def filter_html(self,text):
|
||||||
|
|
||||||
|
output = self.fontify(self.escape_html(text))
|
||||||
|
if self.replace_URLs:
|
||||||
|
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
|
||||||
|
'URL:\\1<A HREF="\\2">\\2</A>',output)
|
||||||
|
html = """<HTML><HEAD><TITLE>%s</TITLE></HEAD>
|
||||||
|
<BODY BGCOLOR=%s>
|
||||||
|
<!--header-->%s
|
||||||
|
<!--script-->%s
|
||||||
|
<!--footer-->%s
|
||||||
|
</BODY>\n"""%(self.title,self.bgcolor,self.header,output,self.footer)
|
||||||
|
return html
|
||||||
|
|
||||||
|
def filter_rawhtml(self,text):
|
||||||
|
|
||||||
|
output = self.fontify(self.escape_html(text))
|
||||||
|
if self.replace_URLs:
|
||||||
|
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
|
||||||
|
'URL:\\1<A HREF="\\2">\\2</A>',output)
|
||||||
|
return self.header+output+self.footer
|
||||||
|
|
||||||
|
def filter_ansi(self,text):
|
||||||
|
|
||||||
|
output = self.fontify(text)
|
||||||
|
return self.header+output+self.footer
|
||||||
|
|
||||||
|
### fontify engine
|
||||||
|
|
||||||
|
def fontify(self,pytext):
|
||||||
|
|
||||||
|
# parse
|
||||||
|
taglist = self.tag(pytext)
|
||||||
|
|
||||||
|
# prepend special 'all' tag:
|
||||||
|
taglist[:0] = [('all',0,len(pytext),None)]
|
||||||
|
|
||||||
|
# prepare splitting
|
||||||
|
splits = []
|
||||||
|
addsplits(splits,pytext,self.formats,taglist)
|
||||||
|
|
||||||
|
# do splitting & inserting
|
||||||
|
splits.sort()
|
||||||
|
l = []
|
||||||
|
li = 0
|
||||||
|
for ri,dummy,insert in splits:
|
||||||
|
if ri > li: l.append(pytext[li:ri])
|
||||||
|
l.append(insert)
|
||||||
|
li = ri
|
||||||
|
if li < len(pytext): l.append(pytext[li:])
|
||||||
|
|
||||||
|
return string.join(l,'')
|
||||||
|
|
||||||
|
def addsplits(splits,text,formats,taglist):
|
||||||
|
# helper for fontify()
|
||||||
|
for id,left,right,sublist in taglist:
|
||||||
|
try:
|
||||||
|
pre,post = formats[id]
|
||||||
|
except KeyError:
|
||||||
|
# sys.stderr.write('Warning: no format for %s specified\n'%repr(id))
|
||||||
|
pre,post = '',''
|
||||||
|
if type(pre) != type(''):
|
||||||
|
pre = pre(text[left:right])
|
||||||
|
if type(post) != type(''):
|
||||||
|
post = post(text[left:right])
|
||||||
|
# len(splits) is a dummy used to make sorting stable
|
||||||
|
splits.append((left,len(splits),pre))
|
||||||
|
if sublist:
|
||||||
|
addsplits(splits,text,formats,sublist)
|
||||||
|
splits.append((right,len(splits),post))
|
||||||
|
|
||||||
|
def write_html_error(titel,text):
|
||||||
|
|
||||||
|
print """\
|
||||||
|
<HTML><HEADER><TITLE>%s</TITLE></HEADER>
|
||||||
|
<BODY>
|
||||||
|
<H2>%s</H2>
|
||||||
|
%s
|
||||||
|
</BODY></HTML>
|
||||||
|
""" % (titel,titel,text)
|
||||||
|
|
||||||
|
def redirect_to(url):
|
||||||
|
|
||||||
|
sys.stdout.write('Content-Type: text/html\r\n')
|
||||||
|
sys.stdout.write('Status: 302\r\n')
|
||||||
|
sys.stdout.write('Location: %s\r\n\r\n' % url)
|
||||||
|
print """
|
||||||
|
<HTML><HEAD>
|
||||||
|
<TITLE>302 Moved Temporarily</TITLE>
|
||||||
|
</HEAD><BODY>
|
||||||
|
<H1>302 Moved Temporarily</H1>
|
||||||
|
The document has moved to <A HREF="%s">%s</A>.<P>
|
||||||
|
</BODY></HTML>
|
||||||
|
""" % (url,url)
|
||||||
|
|
||||||
|
def main(cmdline):
|
||||||
|
|
||||||
|
""" main(cmdline) -- process cmdline as if it were sys.argv
|
||||||
|
"""
|
||||||
|
# parse options/files
|
||||||
|
options = []
|
||||||
|
optvalues = {}
|
||||||
|
for o in cmdline[1:]:
|
||||||
|
if o[0] == '-':
|
||||||
|
if ':' in o:
|
||||||
|
k,v = tuple(string.split(o,':'))
|
||||||
|
optvalues[k] = v
|
||||||
|
options.append(k)
|
||||||
|
else:
|
||||||
|
options.append(o)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
files = cmdline[len(options)+1:]
|
||||||
|
|
||||||
|
# create converting object
|
||||||
|
|
||||||
|
# load fontifier
|
||||||
|
if '-marcs' in options:
|
||||||
|
# use mxTextTool's tagging engine
|
||||||
|
from mxTextTools import tag
|
||||||
|
from mxTextTools.Examples.Python import python_script
|
||||||
|
tagfct = lambda text,tag=tag,pytable=python_script: \
|
||||||
|
tag(text,pytable)[1]
|
||||||
|
print "Py2HTML: using Marc's tagging engine"
|
||||||
|
else:
|
||||||
|
# load Just's
|
||||||
|
try:
|
||||||
|
import PyFontify
|
||||||
|
if PyFontify.__version__ < '0.3': raise ValueError
|
||||||
|
tagfct = PyFontify.fontify
|
||||||
|
except:
|
||||||
|
print """
|
||||||
|
Sorry, but this script needs the PyFontify.py module version 0.3;
|
||||||
|
You can download it from Just's homepage at
|
||||||
|
|
||||||
|
URL: http://starship.skyport.net/crew/just
|
||||||
|
"""
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
if '-format' in options:
|
||||||
|
format = optvalues['-format']
|
||||||
|
else:
|
||||||
|
# use default
|
||||||
|
format = 'html'
|
||||||
|
|
||||||
|
if '-mode' in options:
|
||||||
|
mode = optvalues['-mode']
|
||||||
|
else:
|
||||||
|
# use default
|
||||||
|
mode = 'color'
|
||||||
|
|
||||||
|
c = PrettyPrint(tagfct,format,mode)
|
||||||
|
convert = c.file_filter
|
||||||
|
|
||||||
|
# start working
|
||||||
|
|
||||||
|
if '-title' in options:
|
||||||
|
c.title = optvalues['-title']
|
||||||
|
|
||||||
|
if '-bgcolor' in options:
|
||||||
|
c.bgcolor = optvalues['-bgcolor']
|
||||||
|
|
||||||
|
if '-header' in options:
|
||||||
|
try:
|
||||||
|
f = open(optvalues['-header'])
|
||||||
|
c.header = f.read()
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
if verbose: print 'IOError: header file not found'
|
||||||
|
|
||||||
|
if '-footer' in options:
|
||||||
|
try:
|
||||||
|
f = open(optvalues['-footer'])
|
||||||
|
c.footer = f.read()
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
if verbose: print 'IOError: footer file not found'
|
||||||
|
|
||||||
|
if '-URL' in options:
|
||||||
|
c.replace_URLs = 1
|
||||||
|
|
||||||
|
if '-' in options:
|
||||||
|
convert(sys.stdin,sys.stdout)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if '-h' in options:
|
||||||
|
print __doc__
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if len(files) == 0:
|
||||||
|
# Turn URL processing on
|
||||||
|
c.replace_URLs = 1
|
||||||
|
# Try CGI processing...
|
||||||
|
import cgi,urllib,urlparse,os
|
||||||
|
form = cgi.FieldStorage()
|
||||||
|
if not form.has_key('script'):
|
||||||
|
# Ok, then try pathinfo
|
||||||
|
if not os.environ.has_key('PATH_INFO'):
|
||||||
|
if INPUT_FORM:
|
||||||
|
redirect_to(INPUT_FORM)
|
||||||
|
else:
|
||||||
|
sys.stdout.write('Content-Type: text/html\r\n\r\n')
|
||||||
|
write_html_error('Missing Parameter',
|
||||||
|
'Missing script=URL field in request')
|
||||||
|
sys.exit(1)
|
||||||
|
url = os.environ['PATH_INFO'][1:] # skip the leading slash
|
||||||
|
else:
|
||||||
|
url = form['script'].value
|
||||||
|
sys.stdout.write('Content-Type: text/html\r\n\r\n')
|
||||||
|
scheme, host, path, params, query, frag = urlparse.urlparse(url)
|
||||||
|
if not host:
|
||||||
|
scheme = 'http'
|
||||||
|
if os.environ.has_key('HTTP_HOST'):
|
||||||
|
host = os.environ['HTTP_HOST']
|
||||||
|
else:
|
||||||
|
host = 'localhost'
|
||||||
|
url = urlparse.urlunparse((scheme, host, path, params, query, frag))
|
||||||
|
#print url; sys.exit()
|
||||||
|
network = urllib.URLopener()
|
||||||
|
try:
|
||||||
|
tempfile,headers = network.retrieve(url)
|
||||||
|
except IOError,reason:
|
||||||
|
write_html_error('Error opening "%s"' % url,
|
||||||
|
'The given URL could not be opened. Reason: %s' %\
|
||||||
|
str(reason))
|
||||||
|
sys.exit(1)
|
||||||
|
f = open(tempfile,'rb')
|
||||||
|
c.title = url
|
||||||
|
c.footer = __cgifooter__
|
||||||
|
convert(f,sys.stdout)
|
||||||
|
f.close()
|
||||||
|
network.close()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if '-stdout' in options:
|
||||||
|
filebreak = '-'*72
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
if len(files) > 1:
|
||||||
|
print filebreak
|
||||||
|
print 'File:',f
|
||||||
|
print filebreak
|
||||||
|
convert(f,sys.stdout)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
verbose = ('-v' in options)
|
||||||
|
if verbose:
|
||||||
|
print 'Py2HTML: working on',
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
if verbose: print f,
|
||||||
|
convert(f,f+'.html')
|
||||||
|
except IOError:
|
||||||
|
if verbose: print '(IOError!)',
|
||||||
|
if verbose:
|
||||||
|
print
|
||||||
|
print 'Done.'
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
main(sys.argv)
|
||||||
|
|
||||||
|
|
348
lib/rlog.py
Normal file
348
lib/rlog.py
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
# -*- Mode: python -*-
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os, sys, string, time, re
|
||||||
|
|
||||||
|
## RLogOutputParser uses the output of rlog to build a list of Commit
|
||||||
|
## objects describing all the checkins from a given RCS file; this
|
||||||
|
## parser is fairly optimized, and therefore can be delicate if the
|
||||||
|
## rlog output varies between versions of rlog; I don't know if it does;
|
||||||
|
## to make really fast, I should wrap the C rcslib
|
||||||
|
|
||||||
|
## there's definately not much error checking here; I'll assume things
|
||||||
|
## will go smoothly, and trap errors via exception handlers above this
|
||||||
|
## function
|
||||||
|
|
||||||
|
## exception for this class
|
||||||
|
error = 'rlog error'
|
||||||
|
|
||||||
|
|
||||||
|
class RLogData:
|
||||||
|
"Container object for all data parsed from a 'rlog' output."
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.filename = filename
|
||||||
|
self.symbolic_name_hash = {}
|
||||||
|
self.rlog_entry_list = []
|
||||||
|
|
||||||
|
def LookupBranch(self, rlog_entry):
|
||||||
|
index = string.rfind(rlog_entry.revision, '.')
|
||||||
|
branch_revision = rlog_entry.revision[:index]
|
||||||
|
|
||||||
|
try:
|
||||||
|
branch = self.symbolic_name_hash[branch_revision]
|
||||||
|
except KeyError:
|
||||||
|
branch = ''
|
||||||
|
|
||||||
|
return branch
|
||||||
|
|
||||||
|
|
||||||
|
class RLogEntry:
|
||||||
|
## static constants for type of log entry; this will be changed
|
||||||
|
## to strings I guess -JMP
|
||||||
|
CHANGE = 0
|
||||||
|
ADD = 1
|
||||||
|
REMOVE = 2
|
||||||
|
|
||||||
|
## Here's the init function, which isn't needed since this class
|
||||||
|
## is fully initalized by RLogParser when creating a new log entry.
|
||||||
|
## Let's keep this initializer as a description of what is held in
|
||||||
|
## the class, but keep it commented out since it only makes things
|
||||||
|
## slow.
|
||||||
|
##
|
||||||
|
## def __init__(self):
|
||||||
|
## self.revision = ''
|
||||||
|
## self.author = ''
|
||||||
|
## self.branch = ''
|
||||||
|
## self.pluscount = ''
|
||||||
|
## self.minuscount = ''
|
||||||
|
## self.description = ''
|
||||||
|
## self.time = None
|
||||||
|
## self.type = RLogEntry.CHANGE
|
||||||
|
|
||||||
|
|
||||||
|
class RLog:
|
||||||
|
"Provides a alternative file-like interface for running 'rlog'."
|
||||||
|
|
||||||
|
def __init__(self, filename, revision, date):
|
||||||
|
self.filename = self.fix_filename(filename)
|
||||||
|
self.checkout_filename = self.create_checkout_filename(self.filename)
|
||||||
|
self.revision = revision
|
||||||
|
self.date = date
|
||||||
|
|
||||||
|
arg_list = []
|
||||||
|
if self.revision:
|
||||||
|
arg_list.append('-r%s' % (self.revision))
|
||||||
|
if self.date:
|
||||||
|
arg_list.append('-d%s' % (self.date))
|
||||||
|
|
||||||
|
self.cmd = 'rlog %s "%s"' % (string.join(arg_list), self.filename)
|
||||||
|
self.rlog = os.popen(self.cmd, 'r')
|
||||||
|
|
||||||
|
def fix_filename(self, filename):
|
||||||
|
## all RCS files have the ",v" ending
|
||||||
|
if filename[-2:] != ",v":
|
||||||
|
filename = "%s,v" % (filename)
|
||||||
|
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return filename
|
||||||
|
|
||||||
|
## check the Attic for the RCS file
|
||||||
|
path, basename = os.path.split(filename)
|
||||||
|
filename = os.path.join(path, "Attic", basename)
|
||||||
|
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
return filename
|
||||||
|
|
||||||
|
raise error, "rlog file not found: %s" % (filename)
|
||||||
|
|
||||||
|
def create_checkout_filename(self, filename):
|
||||||
|
## cut off the ",v"
|
||||||
|
checkout_filename = filename[:-2]
|
||||||
|
|
||||||
|
## check if the file is in the Attic
|
||||||
|
path, basename = os.path.split(checkout_filename)
|
||||||
|
if path[-6:] != '/Attic':
|
||||||
|
return checkout_filename
|
||||||
|
|
||||||
|
## remove the "Attic" part of the path
|
||||||
|
checkout_filename = os.path.join(path[:-6], basename)
|
||||||
|
return checkout_filename
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
try:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
except AttributeError:
|
||||||
|
self.error()
|
||||||
|
|
||||||
|
if line:
|
||||||
|
return line
|
||||||
|
|
||||||
|
status = self.close()
|
||||||
|
if status:
|
||||||
|
self.error()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
status = self.rlog.close()
|
||||||
|
self.rlog = None
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
raise error, "unexpected rlog exit: %s" % (self.cmd)
|
||||||
|
|
||||||
|
|
||||||
|
## constants used in the output parser
|
||||||
|
|
||||||
|
_rlog_commit_sep = '----------------------------\n'
|
||||||
|
_rlog_end = '=============================================================================\n'
|
||||||
|
|
||||||
|
## regular expression used in the output parser
|
||||||
|
_re_symbolic_name = re.compile("\s+([^:]+):\s+(.+)$")
|
||||||
|
|
||||||
|
_re_revision = re.compile("^revision\s+([0-9.]+).*")
|
||||||
|
|
||||||
|
_re_data_line = re.compile(
|
||||||
|
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
|
||||||
|
"author:\s+([^;]+);\s+"\
|
||||||
|
"state:\s+([^;]+);\s+"\
|
||||||
|
"lines:\s+\+(\d+)\s+\-(\d+)$")
|
||||||
|
|
||||||
|
_re_data_line_add = re.compile(
|
||||||
|
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
|
||||||
|
"author:\s+([^;]+);\s+"\
|
||||||
|
"state:\s+([^;]+);$")
|
||||||
|
|
||||||
|
class RLogOutputParser:
|
||||||
|
|
||||||
|
def __init__(self, rlog):
|
||||||
|
self.rlog = rlog
|
||||||
|
self.rlog_data = RLogData(rlog.checkout_filename)
|
||||||
|
|
||||||
|
## run the parser
|
||||||
|
self.parse_to_symbolic_names()
|
||||||
|
self.parse_symbolic_names()
|
||||||
|
self.parse_to_description()
|
||||||
|
self.parse_rlog_entries()
|
||||||
|
|
||||||
|
def parse_to_symbolic_names(self):
|
||||||
|
while 1:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
if line[:15] == 'symbolic names:':
|
||||||
|
break
|
||||||
|
|
||||||
|
def parse_symbolic_names(self):
|
||||||
|
## parse all the tags int the branch_hash, it's used later to get
|
||||||
|
## the text names of non-head branches
|
||||||
|
while 1:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
match = _re_symbolic_name.match(line)
|
||||||
|
if not match:
|
||||||
|
break
|
||||||
|
|
||||||
|
(tag, revision) = match.groups()
|
||||||
|
|
||||||
|
## check if the tag represents a branch, in RCS this means
|
||||||
|
## the second-to-last number is a zero
|
||||||
|
index = string.rfind(revision, '.')
|
||||||
|
if revision[index-2:index] == '.0':
|
||||||
|
revision = revision[:index-2] + revision[index:]
|
||||||
|
|
||||||
|
self.rlog_data.symbolic_name_hash[revision] = tag
|
||||||
|
|
||||||
|
def parse_to_description(self):
|
||||||
|
while 1:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
if line[:12] == 'description:':
|
||||||
|
break
|
||||||
|
|
||||||
|
## eat all lines until we reach '-----' seperator
|
||||||
|
while 1:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
if line == _rlog_commit_sep:
|
||||||
|
break
|
||||||
|
|
||||||
|
def parse_rlog_entries(self):
|
||||||
|
while 1:
|
||||||
|
rlog_entry = self.parse_one_rlog_entry()
|
||||||
|
if not rlog_entry:
|
||||||
|
break
|
||||||
|
self.rlog_data.rlog_entry_list.append(rlog_entry)
|
||||||
|
|
||||||
|
def parse_one_rlog_entry(self):
|
||||||
|
## revision line/first line
|
||||||
|
line = self.rlog.readline()
|
||||||
|
if not line:
|
||||||
|
return None
|
||||||
|
|
||||||
|
## revision
|
||||||
|
match = _re_revision.match(line)
|
||||||
|
(revision,) = match.groups()
|
||||||
|
|
||||||
|
## data line
|
||||||
|
line = self.rlog.readline()
|
||||||
|
match = _re_data_line.match(line)
|
||||||
|
if not match:
|
||||||
|
match = _re_data_line_add.match(line)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
raise error, "bad rlog parser, no cookie!"
|
||||||
|
|
||||||
|
## retrieve the matched grops as a tuple in hopes
|
||||||
|
## this will be faster (ala profiler)
|
||||||
|
groups = match.groups()
|
||||||
|
|
||||||
|
year = string.atoi(groups[0])
|
||||||
|
month = string.atoi(groups[1])
|
||||||
|
day = string.atoi(groups[2])
|
||||||
|
hour = string.atoi(groups[3])
|
||||||
|
minute = string.atoi(groups[4])
|
||||||
|
second = string.atoi(groups[5])
|
||||||
|
author = groups[6]
|
||||||
|
state = groups[7]
|
||||||
|
|
||||||
|
## very strange; here's the deal: if this is a newly added file,
|
||||||
|
## then there is no plus/minus count count of lines; if there
|
||||||
|
## is, then this could be a "CHANGE" or "REMOVE", you can tell
|
||||||
|
## if the file has been removed by looking if state == 'dead'
|
||||||
|
try:
|
||||||
|
pluscount = groups[8]
|
||||||
|
minuscount = groups[9]
|
||||||
|
except IndexError:
|
||||||
|
pluscount = ''
|
||||||
|
minuscount = ''
|
||||||
|
cmit_type = RLogEntry.ADD
|
||||||
|
else:
|
||||||
|
if state == 'dead':
|
||||||
|
cmit_type = RLogEntry.REMOVE
|
||||||
|
else:
|
||||||
|
cmit_type = RLogEntry.CHANGE
|
||||||
|
|
||||||
|
## branch line: pretty much ignored if it's there
|
||||||
|
desc_line_list = []
|
||||||
|
|
||||||
|
line = self.rlog.readline()
|
||||||
|
if not line[:10] == 'branches: ':
|
||||||
|
desc_line_list.append(string.rstrip(line))
|
||||||
|
|
||||||
|
## suck up description
|
||||||
|
while 1:
|
||||||
|
line = self.rlog.readline()
|
||||||
|
|
||||||
|
## the last line printed out by rlog is '===='...
|
||||||
|
## or '------'... between entries
|
||||||
|
if line == _rlog_commit_sep or line == _rlog_end:
|
||||||
|
break
|
||||||
|
|
||||||
|
## append line to the descripton list
|
||||||
|
desc_line_list.append(string.rstrip(line))
|
||||||
|
|
||||||
|
## compute time using time routines in seconds from epoc GMT
|
||||||
|
## NOTE: mktime's arguments are in local time, and we have
|
||||||
|
## them in GMT from RCS; therefore, we have to manually
|
||||||
|
## subtract out the timezone correction
|
||||||
|
##
|
||||||
|
## XXX: Linux glib2.0.7 bug: it looks like mktime doesn't honor
|
||||||
|
## the '0' flag to force no timezone correction, so we look
|
||||||
|
## at the correction ourself and do the right thing after
|
||||||
|
## mktime mangles the date
|
||||||
|
gmt_time = \
|
||||||
|
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
|
||||||
|
|
||||||
|
if time.daylight:
|
||||||
|
gmt_time = gmt_time - time.altzone
|
||||||
|
else:
|
||||||
|
gmt_time = gmt_time - time.timezone
|
||||||
|
|
||||||
|
## now create and return the RLogEntry
|
||||||
|
rlog_entry = RLogEntry()
|
||||||
|
rlog_entry.type = cmit_type
|
||||||
|
rlog_entry.revision = revision
|
||||||
|
rlog_entry.author = author
|
||||||
|
rlog_entry.description = string.join(desc_line_list, '\n')
|
||||||
|
rlog_entry.time = gmt_time
|
||||||
|
rlog_entry.pluscount = pluscount
|
||||||
|
rlog_entry.minuscount = minuscount
|
||||||
|
|
||||||
|
return rlog_entry
|
||||||
|
|
||||||
|
|
||||||
|
## entrypoints
|
||||||
|
|
||||||
|
def GetRLogData(path, revision = '', date = ''):
|
||||||
|
rlog = RLog(path, revision, date)
|
||||||
|
rlog_parser = RLogOutputParser(rlog)
|
||||||
|
return rlog_parser.rlog_data
|
164
tools/cvsdbadmin
Executable file
164
tools/cvsdbadmin
Executable file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# administrative program for CVSdb; this is primarily
|
||||||
|
# used to add/rebuild CVS repositories to the database
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
## BOOTSTRAP
|
||||||
|
import sys, os, string
|
||||||
|
_viewcvs_root = string.strip(open("/etc/viewcvs/root", "r").read())
|
||||||
|
sys.path.append(os.path.join(_viewcvs_root, "lib"))
|
||||||
|
##
|
||||||
|
|
||||||
|
import cvsdbapi
|
||||||
|
|
||||||
|
|
||||||
|
def UpdateFile(db, repository, path):
|
||||||
|
try:
|
||||||
|
commit_list = cvsdbapi.GetUnrecordedCommitList(repository, path)
|
||||||
|
except cvsdbapi.error, e:
|
||||||
|
print '[ERROR] %s' % (e)
|
||||||
|
return
|
||||||
|
|
||||||
|
print '[%s[%d new commits]]' % (path, len(commit_list)),
|
||||||
|
|
||||||
|
## add the commits into the database
|
||||||
|
for commit in commit_list:
|
||||||
|
db.AddCommit(commit)
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def RecurseUpdate(db, repository, directory):
|
||||||
|
for path in os.listdir(directory):
|
||||||
|
path = os.path.join(directory, path)
|
||||||
|
|
||||||
|
if os.path.islink(path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
RecurseUpdate(db, repository, path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
if path[-2:] == ',v':
|
||||||
|
UpdateFile(db, repository, path)
|
||||||
|
|
||||||
|
|
||||||
|
def CommandUpdate():
|
||||||
|
## connect to the database we are updating
|
||||||
|
db = cvsdbapi.ConnectDatabase()
|
||||||
|
repository = sys.argv[2]
|
||||||
|
RecurseUpdate(db, repository, repository)
|
||||||
|
|
||||||
|
|
||||||
|
def RebuildFile(db, repository, path):
|
||||||
|
try:
|
||||||
|
commit_list = cvsdbapi.GetCommitListFromRCSFile(repository, path)
|
||||||
|
except cvsdbapi.error, e:
|
||||||
|
print '[ERROR] %s' % (e)
|
||||||
|
return
|
||||||
|
|
||||||
|
print '[%s[%d commits]]' % (path, len(commit_list)),
|
||||||
|
|
||||||
|
## add the commits into the database
|
||||||
|
for commit in commit_list:
|
||||||
|
db.AddCommit(commit)
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def RecurseRebuild(db, repository, directory):
|
||||||
|
for path in os.listdir(directory):
|
||||||
|
path = os.path.join(directory, path)
|
||||||
|
|
||||||
|
if os.path.islink(path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.isdir(path):
|
||||||
|
RecurseRebuild(db, repository, path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
if path[-2:] == ',v':
|
||||||
|
RebuildFile(db, repository, path)
|
||||||
|
|
||||||
|
|
||||||
|
def CommandRebuild():
|
||||||
|
## connect to the database we are updating
|
||||||
|
db = cvsdbapi.ConnectDatabase()
|
||||||
|
repository = sys.argv[2]
|
||||||
|
RecurseRebuild(db, repository, repository)
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print 'Usage: %s <command> [arguments]' % (sys.argv[0])
|
||||||
|
print 'Preforms administrative functions for the CVSdb database'
|
||||||
|
print 'Commands:'
|
||||||
|
print ' rebuild <repository> rebuilds the CVSdb database'
|
||||||
|
print ' for all files in the repository'
|
||||||
|
print ' update <repository> updates the CVSdb database for'
|
||||||
|
print ' all unrecorded commits'
|
||||||
|
print
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
## main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
## check that a command was given
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
## set the handler function for the command
|
||||||
|
command = sys.argv[1]
|
||||||
|
if string.lower(command) == 'rebuild':
|
||||||
|
commandFunction = CommandRebuild
|
||||||
|
elif string.lower(command) == 'update':
|
||||||
|
commandFunction = CommandUpdate
|
||||||
|
else:
|
||||||
|
print 'ERROR: unknown command %s' % (command)
|
||||||
|
usage()
|
||||||
|
|
||||||
|
## run command
|
||||||
|
try:
|
||||||
|
commandFunction()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print
|
||||||
|
print '** break **'
|
||||||
|
|
||||||
|
sys.exit(0)
|
190
tools/loginfo-handler
Executable file
190
tools/loginfo-handler
Executable file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- Mode: python -*-
|
||||||
|
#
|
||||||
|
# updates SQL database with new commit records
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2000 Jay Painter. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# By using this file, you agree to the terms and conditions set forth below:
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# For tracking purposes, this software is identified by:
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
## BOOTSTRAP
|
||||||
|
import sys, os, string
|
||||||
|
_viewcvs_root = string.strip(open("/etc/viewcvs/root", "r").read())
|
||||||
|
sys.path.append(os.path.join(_viewcvs_root, "lib"))
|
||||||
|
##
|
||||||
|
|
||||||
|
import getopt, re, cvsdbapi
|
||||||
|
|
||||||
|
DEBUG_FLAG = 0
|
||||||
|
|
||||||
|
## pre-compiled regular expressions
|
||||||
|
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
|
||||||
|
|
||||||
|
|
||||||
|
## output functions
|
||||||
|
def debug(text):
|
||||||
|
if DEBUG_FLAG:
|
||||||
|
print 'DEBUG(loginfo): %s' % (text)
|
||||||
|
|
||||||
|
def warning(text):
|
||||||
|
print 'WARNING(loginfo): %s' % (text)
|
||||||
|
|
||||||
|
def error(text):
|
||||||
|
print 'ERROR(loginfo): %s' % (text)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class FileData:
|
||||||
|
|
||||||
|
CHANGED = 1
|
||||||
|
ADDED = 2
|
||||||
|
REMOVED = 3
|
||||||
|
|
||||||
|
def __init__(self, file, directory, old_version, new_version):
|
||||||
|
self.file = file
|
||||||
|
self.directory = directory
|
||||||
|
self.old_version = old_version
|
||||||
|
self.new_version = new_version
|
||||||
|
|
||||||
|
## set the state of this file from the
|
||||||
|
## old_version and new_version information
|
||||||
|
if self.old_version == 'NONE':
|
||||||
|
self.type = self.ADDED
|
||||||
|
elif self.new_version == 'NONE':
|
||||||
|
self.type = self.REMOVED
|
||||||
|
else:
|
||||||
|
self.type = self.CHANGED
|
||||||
|
|
||||||
|
|
||||||
|
def CommitFromFileData(repository, file_data):
|
||||||
|
## consturct the full path for the RCS file
|
||||||
|
filename = os.path.join(repository, file_data.directory, file_data.file)
|
||||||
|
|
||||||
|
## get the 'rlog' output for just this revision, and then convert
|
||||||
|
## to a commit object
|
||||||
|
rlog_data = cvsdbapi.GetRLogData(filename, file_data.new_version)
|
||||||
|
commit_list = cvsdbapi.RLogDataToCommitList(repository, rlog_data)
|
||||||
|
commit = commit_list[0]
|
||||||
|
|
||||||
|
## set the type of commit from the file_data setting
|
||||||
|
if file_data.type == file_data.CHANGED:
|
||||||
|
commit.SetTypeChange()
|
||||||
|
elif file_data.type == file_data.ADDED:
|
||||||
|
commit.SetTypeAdd()
|
||||||
|
elif file_data.type == file_data.REMOVED:
|
||||||
|
commit.SetTypeRemove()
|
||||||
|
|
||||||
|
return commit
|
||||||
|
|
||||||
|
|
||||||
|
def GetUnrecordedCommitList(repository, file_data):
|
||||||
|
filename = os.path.join(repository, file_data.directory, file_data.file)
|
||||||
|
return cvsdbapi.GetUnrecordedCommitList(repository, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def ProcessLoginfo(repository, stdin_list):
|
||||||
|
## the first line in stdin is a space-separated list; the first
|
||||||
|
## item in the list is the directory path being updated this run;
|
||||||
|
## the rest of the items are the files being updated
|
||||||
|
list = string.split(stdin_list[0])
|
||||||
|
|
||||||
|
## clean up the directory the following way: we don't want it
|
||||||
|
## to begin with a path seperator, and we don't want it to end
|
||||||
|
## with a path seperator
|
||||||
|
directory = list[0]
|
||||||
|
while directory[0] == os.sep:
|
||||||
|
directory = directory[1:]
|
||||||
|
while directory[-1] == os.sep:
|
||||||
|
directory = directory[:-1]
|
||||||
|
|
||||||
|
## NOTE: SPECIAL HANDLING FOR NEW DIRECTORIES
|
||||||
|
## new directories have the first line form
|
||||||
|
## path/of/dir - New directory
|
||||||
|
if len(list) == 4:
|
||||||
|
if list[1] == '-' and list[2] == 'New' and list[3] == 'directory':
|
||||||
|
debug('new directory')
|
||||||
|
exit(0, file_hash, 1)
|
||||||
|
|
||||||
|
## each file in the file list _should_ be of the form:
|
||||||
|
## file-name,<old-ver>,<new-ver>
|
||||||
|
## a new file has the keyword 'NONE' for old-ver
|
||||||
|
file_data_list = []
|
||||||
|
for item in list[1:]:
|
||||||
|
temp = _re_fileversion.match(item)
|
||||||
|
if not temp:
|
||||||
|
debug('failed match %s' % (item))
|
||||||
|
continue
|
||||||
|
|
||||||
|
filename = temp.group(1)
|
||||||
|
old_version = temp.group(2)
|
||||||
|
new_version = temp.group(3)
|
||||||
|
file_data = FileData(filename, directory, old_version, new_version)
|
||||||
|
file_data_list.append(file_data)
|
||||||
|
|
||||||
|
## convert FileData objects into Commit objects so we can insert them
|
||||||
|
## into the database
|
||||||
|
commit_list = []
|
||||||
|
for file_data in file_data_list:
|
||||||
|
## XXX: this is nasty: in the case of a removed file, we are not
|
||||||
|
## given enough information to find it in the rlog output!
|
||||||
|
## So instead, we rlog everything in the removed file, and
|
||||||
|
## add any commits not already in the database
|
||||||
|
if file_data.type == file_data.REMOVED:
|
||||||
|
temp = GetUnrecordedCommitList(repository, file_data)
|
||||||
|
commit_list = commit_list + temp
|
||||||
|
else:
|
||||||
|
commit_list.append(CommitFromFileData(repository, file_data))
|
||||||
|
|
||||||
|
## add to the database
|
||||||
|
db = cvsdbapi.ConnectDatabase()
|
||||||
|
db.AddCommitList(commit_list)
|
||||||
|
|
||||||
|
|
||||||
|
## MAIN
|
||||||
|
if __name__ == '__main__':
|
||||||
|
## get the repository from the environment
|
||||||
|
try:
|
||||||
|
repository = os.environ['CVSROOT']
|
||||||
|
except KeyError:
|
||||||
|
error('CVSROOT not in environment')
|
||||||
|
|
||||||
|
## clean up the repository string: remove any trailing path seperater
|
||||||
|
while repository[-1] == os.sep:
|
||||||
|
repository = repository[:-1]
|
||||||
|
|
||||||
|
## read all the lines from stdin
|
||||||
|
stdin_list = []
|
||||||
|
for line in sys.stdin.readlines():
|
||||||
|
stdin_list.append(string.rstrip(line))
|
||||||
|
|
||||||
|
ProcessLoginfo(repository, stdin_list)
|
||||||
|
sys.exit(0)
|
@@ -13,7 +13,8 @@
|
|||||||
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
||||||
I wanted to make some changes and updates, but cvsweb was
|
I wanted to make some changes and updates, but cvsweb was
|
||||||
implemented in Perl (and rather poorly, IMO). So I undertook the
|
implemented in Perl (and rather poorly, IMO). So I undertook the
|
||||||
task to convert the software to Python.
|
task to convert the software to
|
||||||
|
<a href="http://www.python.org/"><i>Python</i></a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
ViewCVS can browse directories, change logs, and specific
|
ViewCVS can browse directories, change logs, and specific
|
||||||
@@ -21,11 +22,11 @@
|
|||||||
show selections of files based on tags or branches.
|
show selections of files based on tags or branches.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
ViewCVS is currently at version 0.2. It is almost a straight
|
ViewCVS is currently at version 0.4. It was a
|
||||||
port of the cvsweb script, but has had numerous cleanups and other
|
port of the cvsweb script, but has had numerous cleanups and other
|
||||||
modifications, based on some of Python's strengths. There is
|
modifications, based on some of Python's strengths. There is
|
||||||
still some "badness" in there, but I've been working on flushing
|
still some "badness" in there, but I've been working on flushing
|
||||||
that out, along with my work to start adding features. The
|
that out, along with a few new features. The
|
||||||
functionality of ViewCVS is equal to that of cvsweb, minus the
|
functionality of ViewCVS is equal to that of cvsweb, minus the
|
||||||
"annotation" support. Annotation requires read/write access to
|
"annotation" support. Annotation requires read/write access to
|
||||||
the CVS repository (at this time), and I believe that is a
|
the CVS repository (at this time), and I believe that is a
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
The software is available for download:
|
The software is available for download:
|
||||||
</p>
|
</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<a href="viewcvs-0.2.tar.gz">Version 0.2 of ViewCVS</a>
|
<a href="viewcvs-0.4.tar.gz">Version 0.4 of ViewCVS</a>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>
|
<p>
|
||||||
Of course, it is also available through ViewCVS itself:
|
Of course, it is also available through ViewCVS itself:
|
||||||
@@ -46,14 +47,9 @@
|
|||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
ViewCVS <font color=red>requires <strong>Python
|
ViewCVS requires <strong>Python 1.5</strong> (which has been out
|
||||||
1.5.2</strong></font>.
|
for a couple years and is readily available for your favorite
|
||||||
</p>
|
operating system).
|
||||||
|
|
||||||
<p>
|
|
||||||
If you have any comments, questions, or suggestions, then please
|
|
||||||
email me at
|
|
||||||
<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@@ -62,36 +58,79 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>FAQ</h2>
|
<h2>Mailing List</h2>
|
||||||
|
|
||||||
<dl>
|
<p>
|
||||||
<!-- one of (dd dt) -->
|
If you have any comments, questions, suggestions, or patches,
|
||||||
<dt><strong>I got an <code>AttributeError: urlencode</code></strong></dt>
|
then please send them to the
|
||||||
<dd>
|
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
|
||||||
If you see the following traceback in your browser:
|
mailing list</a>.
|
||||||
<blockquote>
|
</p>
|
||||||
<pre>
|
|
||||||
Traceback (innermost last):
|
<hr width="75%">
|
||||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2205, in ?
|
<h2>Additional features over cvsweb</h2>
|
||||||
main()
|
|
||||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2123, in main
|
<ul>
|
||||||
query_string = sticky_query(query_dict)
|
<li>Colorization for Python files</li>
|
||||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 444, in sticky_query
|
<li>Better reporting for unreadable files</li>
|
||||||
bare_query = urllib.urlencode(sticky_dict)
|
<li>More robust when given varying <code>rcsdiff</code> or
|
||||||
AttributeError: urlencode
|
<code>rlog</code> outputs</li>
|
||||||
</pre>
|
<li>Hard breaks in human-readable diffs</li>
|
||||||
</blockquote>
|
<li>
|
||||||
This is indicative of using an earlier version of
|
The configuration file is optional (you can change the values
|
||||||
Python. <code>urllib.urlencode()</code> was introduced in
|
right in the CGI script and avoid the config file, if you so
|
||||||
Python 1.5.2.
|
choose). The config file syntax is also cleaner, since it is
|
||||||
</dd>
|
human-manageable rather than source code.
|
||||||
</dl>
|
</li>
|
||||||
|
<li>
|
||||||
|
Directories with a large number of files can be viewed.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Future features, coming soon:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>UI streamlining/simplification</li>
|
||||||
|
<li>Integration with CVS checkin auto-mail scripts</li>
|
||||||
|
<li>
|
||||||
|
<i>Suggestions? Send mail to the
|
||||||
|
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
||||||
|
mailing list.
|
||||||
|
</i>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Longer term:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Annotation ("blame") support (similar to cvsweb)</li>
|
||||||
|
<li>Integration with an indexer such as LXR</li>
|
||||||
|
<li>Additional colorizers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr width="75%">
|
||||||
|
<h2>Colorization of Python files</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Christophe Pelte suggested this feature: colorize Python source
|
||||||
|
files using
|
||||||
|
<a href="http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py"><i>py2html</i></a>
|
||||||
|
(by Marc-Andrew Lemburg, based on
|
||||||
|
<a href="http://starship.python.net/crew/just/code/PyFontify.py"><i>PyFontify</i></a>
|
||||||
|
by Just van Rossum). I've added this feature to ViewCVS 0.3,
|
||||||
|
along with a generalized plugin mechanism for custom coloring other
|
||||||
|
types of files. See the instructions within the viewcvs.cgi for
|
||||||
|
setting the <code>py2html_path</code> configuration variable if
|
||||||
|
you want to use this feature.
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
||||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||||
<!-- hhmts start -->
|
<!-- hhmts start -->
|
||||||
Last modified: Wed Jan 12 03:12:22 PST 2000
|
Last modified: Fri Mar 24 03:12:34 PST 2000
|
||||||
<!-- hhmts end -->
|
<!-- hhmts end -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user