mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
397 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dad5311a28 | ||
![]() |
44528c0b7e | ||
![]() |
11f594b7ed | ||
![]() |
8a9719a5f8 | ||
![]() |
7ca272ab88 | ||
![]() |
d13e98ef34 | ||
![]() |
ba99093be8 | ||
![]() |
9171ad07a8 | ||
![]() |
cd03714ecd | ||
![]() |
df09de394d | ||
![]() |
f46fed363e | ||
![]() |
b41bd99aa3 | ||
![]() |
c9710b9ba2 | ||
![]() |
5ca27eee4c | ||
![]() |
caf33e7862 | ||
![]() |
1470ccd12f | ||
![]() |
b476f4a882 | ||
![]() |
eacb04ee26 | ||
![]() |
2910bc4a22 | ||
![]() |
ae049f281b | ||
![]() |
7e3baffea4 | ||
![]() |
c40b88fb8b | ||
![]() |
c7fc7d8885 | ||
![]() |
68336ffcdf | ||
![]() |
70db664ec6 | ||
![]() |
e88fcaebce | ||
![]() |
e2c82d9341 | ||
![]() |
3844161f7d | ||
![]() |
9da5df779a | ||
![]() |
f9d697db0c | ||
![]() |
8432cb1c1d | ||
![]() |
c8718c00cd | ||
![]() |
d71c208540 | ||
![]() |
830c6ba76c | ||
![]() |
e9a4e3bbe2 | ||
![]() |
62f37986d5 | ||
![]() |
a2b579b5ac | ||
![]() |
46d8987848 | ||
![]() |
73209134c9 | ||
![]() |
8e9831d6ec | ||
![]() |
83f6a7c7d5 | ||
![]() |
acbf138a6e | ||
![]() |
e872681988 | ||
![]() |
27430791cb | ||
![]() |
e72b133665 | ||
![]() |
3ea9aec854 | ||
![]() |
a807605e31 | ||
![]() |
d3ed0a9cb5 | ||
![]() |
77f1d6f2e4 | ||
![]() |
d59be9459d | ||
![]() |
1a8c6ac15b | ||
![]() |
4f5800cec5 | ||
![]() |
e4cf54b49e | ||
![]() |
24070dcc6f | ||
![]() |
afe213e140 | ||
![]() |
0dd6919af9 | ||
![]() |
1b4543ed92 | ||
![]() |
26259d1fb1 | ||
![]() |
6941197684 | ||
![]() |
02b0870390 | ||
![]() |
715f4dfa5a | ||
![]() |
5e5ee35046 | ||
![]() |
77355718c6 | ||
![]() |
19ab90b4fb | ||
![]() |
7a3c8bf9c4 | ||
![]() |
d11dffb713 | ||
![]() |
16281c1db5 | ||
![]() |
0da8dec361 | ||
![]() |
1606e4e6dd | ||
![]() |
0393ef47f7 | ||
![]() |
3d9eb3c638 | ||
![]() |
6a0c6f2029 | ||
![]() |
b564bd020a | ||
![]() |
889f71f657 | ||
![]() |
84188fafbb | ||
![]() |
eff0ddeabe | ||
![]() |
d69802487f | ||
![]() |
0ff4aed795 | ||
![]() |
a4aa00b5f1 | ||
![]() |
cef439958f | ||
![]() |
175d6f0ac4 | ||
![]() |
27df7480c5 | ||
![]() |
05bec0ce73 | ||
![]() |
d7c84f3149 | ||
![]() |
a0ece2036e | ||
![]() |
fcec542cfb | ||
![]() |
37166f065c | ||
![]() |
983a2c298e | ||
![]() |
51988428f2 | ||
![]() |
9a8c28d886 | ||
![]() |
02db1bf262 | ||
![]() |
b048907b6d | ||
![]() |
5e497355a4 | ||
![]() |
45d1849883 | ||
![]() |
1e082e2982 | ||
![]() |
55a94583fa | ||
![]() |
cb41e82e6b | ||
![]() |
2b2f813262 | ||
![]() |
679153a77c | ||
![]() |
674a2ee494 | ||
![]() |
8930cd8736 | ||
![]() |
bef73fd21a | ||
![]() |
9fdfe05d24 | ||
![]() |
70b05c8a5e | ||
![]() |
2bc61a621d | ||
![]() |
eae9e0131f | ||
![]() |
28ecb8c8f4 | ||
![]() |
0642725543 | ||
![]() |
ad9a3d8c07 | ||
![]() |
7232487cf0 | ||
![]() |
2e9bbd9be6 | ||
![]() |
34b639106b | ||
![]() |
18667d725d | ||
![]() |
d01364d0bd | ||
![]() |
c80469ba5e | ||
![]() |
c8f661e626 | ||
![]() |
2376c9a7ef | ||
![]() |
2be790dcb3 | ||
![]() |
61c8b3d41f | ||
![]() |
341525d3e4 | ||
![]() |
808cf5265c | ||
![]() |
e824f93fb5 | ||
![]() |
9dcdc1a660 | ||
![]() |
9da51dbec2 | ||
![]() |
a0503097c0 | ||
![]() |
1e5d9dd974 | ||
![]() |
9322e4e59e | ||
![]() |
657b50834d | ||
![]() |
09c4ee378a | ||
![]() |
af4ba05019 | ||
![]() |
946e480778 | ||
![]() |
227fa91673 | ||
![]() |
c2d403b482 | ||
![]() |
248c7998c4 | ||
![]() |
d867b9001f | ||
![]() |
9639a02d25 | ||
![]() |
f5fa59bbd3 | ||
![]() |
5367f945e7 | ||
![]() |
9a797cdd53 | ||
![]() |
2af952b542 | ||
![]() |
86649f829b | ||
![]() |
b2b26e4a65 | ||
![]() |
ab0ac566fe | ||
![]() |
1c26250da7 | ||
![]() |
c230a38f9d | ||
![]() |
89a9e91a56 | ||
![]() |
773eb89f9b | ||
![]() |
7ae3a23f5f | ||
![]() |
127d12fd9c | ||
![]() |
d206d1d80b | ||
![]() |
fa424931e7 | ||
![]() |
bff90274b1 | ||
![]() |
45d2dec67b | ||
![]() |
f5c4bab1d0 | ||
![]() |
0b033dc738 | ||
![]() |
155679c36f | ||
![]() |
aaa04ac917 | ||
![]() |
260d00b24b | ||
![]() |
91f6f38ae4 | ||
![]() |
57850b9d84 | ||
![]() |
f3538a0136 | ||
![]() |
89282b3125 | ||
![]() |
10d0f30cfa | ||
![]() |
de7c322520 | ||
![]() |
fdabbe7687 | ||
![]() |
289e6514da | ||
![]() |
132f7e2342 | ||
![]() |
06b3adbbbf | ||
![]() |
77a8a74a05 | ||
![]() |
621adf6b35 | ||
![]() |
d89a788783 | ||
![]() |
2dade9e281 | ||
![]() |
3d81935a92 | ||
![]() |
9c1d4a954b | ||
![]() |
ff58af471c | ||
![]() |
9b25497fe9 | ||
![]() |
b01b1c3b1e | ||
![]() |
e3a16b2481 | ||
![]() |
457fa117d4 | ||
![]() |
9e55650c62 | ||
![]() |
50e4520d0f | ||
![]() |
1dbc6dcb57 | ||
![]() |
c870cc4f23 | ||
![]() |
7a0fcf1e9e | ||
![]() |
bf69d3d2aa | ||
![]() |
5526c78b4a | ||
![]() |
fe34dc73ad | ||
![]() |
c0859c9dd2 | ||
![]() |
ce17dc8180 | ||
![]() |
fcfada618a | ||
![]() |
fe17a6c8c6 | ||
![]() |
a8ace27847 | ||
![]() |
55052f1854 | ||
![]() |
8bb281930b | ||
![]() |
e31eb7859e | ||
![]() |
47b652e457 | ||
![]() |
44b8620adf | ||
![]() |
3db5f61f5e | ||
![]() |
ca3e52b05b | ||
![]() |
67fe0ffd7c | ||
![]() |
58f2c57c33 | ||
![]() |
0268b877e5 | ||
![]() |
620307a64c | ||
![]() |
e833b962b9 | ||
![]() |
f9953b7d87 | ||
![]() |
0d62e2c0c9 | ||
![]() |
cf456c5cfb | ||
![]() |
e9dee7ae87 | ||
![]() |
ec99cb429b | ||
![]() |
8093438d6b | ||
![]() |
a0135416b5 | ||
![]() |
27bcce39a8 | ||
![]() |
4e07d3a2ae | ||
![]() |
b8582e57c7 | ||
![]() |
5684cd97ad | ||
![]() |
56695b3f26 | ||
![]() |
352e8203cd | ||
![]() |
350db5250c | ||
![]() |
880690fc89 | ||
![]() |
a64dc83028 | ||
![]() |
d29fac8ba9 | ||
![]() |
6145ac2213 | ||
![]() |
32da3ae204 | ||
![]() |
cbadda8611 | ||
![]() |
451da3e26d | ||
![]() |
5130a7347f | ||
![]() |
2a83c75317 | ||
![]() |
8d1daf1787 | ||
![]() |
6c6aee893f | ||
![]() |
02f2243cc7 | ||
![]() |
8766cf00ca | ||
![]() |
ba039d5a8f | ||
![]() |
007ad51b2c | ||
![]() |
c3d12bb0c8 | ||
![]() |
d0de5e7ad0 | ||
![]() |
4a00a0bb4f | ||
![]() |
abb4a468fc | ||
![]() |
cc3807f964 | ||
![]() |
3bff6b7378 | ||
![]() |
fc6d80e2fb | ||
![]() |
425ae2e8ec | ||
![]() |
c2a29c83cd | ||
![]() |
799816c21f | ||
![]() |
e3dbdb4fcc | ||
![]() |
c28afba169 | ||
![]() |
d790f07d2c | ||
![]() |
edf252fb2f | ||
![]() |
6942dd6b4b | ||
![]() |
a75f32fc3c | ||
![]() |
e243a0e1ad | ||
![]() |
755e4e74c3 | ||
![]() |
a46b9dd3ba | ||
![]() |
ab987dc6fa | ||
![]() |
a6ac1d1892 | ||
![]() |
945cb91e38 | ||
![]() |
262f3569c7 | ||
![]() |
1b358fc88d | ||
![]() |
455faa1fe6 | ||
![]() |
01facd113b | ||
![]() |
67daa9e5e2 | ||
![]() |
03a5620947 | ||
![]() |
424521d40e | ||
![]() |
1eeb39f264 | ||
![]() |
7aa5c33589 | ||
![]() |
60236803e4 | ||
![]() |
a26cfff0ad | ||
![]() |
f0c34a715d | ||
![]() |
4cb1282b70 | ||
![]() |
f34424ca20 | ||
![]() |
bc6851ea6f | ||
![]() |
3b10a1763f | ||
![]() |
329d20633a | ||
![]() |
5251065e6d | ||
![]() |
3d6f71ce00 | ||
![]() |
1a69430064 | ||
![]() |
050ad1f8fe | ||
![]() |
3965ce9221 | ||
![]() |
12003f27b5 | ||
![]() |
b0b1b5603e | ||
![]() |
2b8cc9d34c | ||
![]() |
4ddb89e053 | ||
![]() |
c9e782c881 | ||
![]() |
7d8f036490 | ||
![]() |
02bad3fbe3 | ||
![]() |
892364776e | ||
![]() |
8996332950 | ||
![]() |
eb214de775 | ||
![]() |
5ca77ced3c | ||
![]() |
91d2ebfc64 | ||
![]() |
ee7abc077a | ||
![]() |
087db42096 | ||
![]() |
028b54de90 | ||
![]() |
cb4f496b99 | ||
![]() |
573293bbfc | ||
![]() |
e6080c8366 | ||
![]() |
346c91205b | ||
![]() |
3828e969de | ||
![]() |
1a821bda9e | ||
![]() |
ddd782a950 | ||
![]() |
f2dffceb6d | ||
![]() |
8810ab6c57 | ||
![]() |
b21597d96d | ||
![]() |
c553401843 | ||
![]() |
ee8a705131 | ||
![]() |
665d46a59d | ||
![]() |
15d7883bb0 | ||
![]() |
6ed78e918e | ||
![]() |
5463d8ff1b | ||
![]() |
934209abb9 | ||
![]() |
a19338dd0a | ||
![]() |
ee6c5f5da3 | ||
![]() |
43d6d82b34 | ||
![]() |
7ad684b001 | ||
![]() |
25e4e5d18a | ||
![]() |
2719168ddc | ||
![]() |
31272b7cea | ||
![]() |
eb940183e3 | ||
![]() |
33356d5a30 | ||
![]() |
7ba05d4b43 | ||
![]() |
083de4d0c7 | ||
![]() |
cbe1f36d8a | ||
![]() |
efa434d7fa | ||
![]() |
7fc9018074 | ||
![]() |
a7fe838521 | ||
![]() |
a1e7108d76 | ||
![]() |
867883e525 | ||
![]() |
2f376b11e3 | ||
![]() |
07763938a3 | ||
![]() |
182aeaa7fa | ||
![]() |
75d59eb292 | ||
![]() |
18048c0a80 | ||
![]() |
fb262791a1 | ||
![]() |
b2d9bf88a1 | ||
![]() |
8e94cc5775 | ||
![]() |
e8c829df09 | ||
![]() |
b892974ffe | ||
![]() |
78022b0e38 | ||
![]() |
17c4db41dc | ||
![]() |
8070b6bba1 | ||
![]() |
c8ebba802e | ||
![]() |
5b24650755 | ||
![]() |
8b9a143376 | ||
![]() |
741a3daefe | ||
![]() |
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 |
62
CHANGES
Normal file
62
CHANGES
Normal file
@@ -0,0 +1,62 @@
|
||||
Version 0.9.2 (released 15-Jan-2001)
|
||||
|
||||
* fix redirects to Attic for diffs
|
||||
* fix diffs that have no changes (causing an infinite loop)
|
||||
|
||||
Version 0.9.1 (released 26-Dec-2001)
|
||||
|
||||
* fix a problem with some syntax in ndiff.py which isn't compatible
|
||||
with Python 1.5.2 (causing problems at install time)
|
||||
* remove a debug statement left in the code which continues to
|
||||
append lines to /tmp/log
|
||||
|
||||
Version 0.9 (released 23-Dec-2001)
|
||||
|
||||
* create templates for the rest of the pages: markup pages, graphs,
|
||||
annotation, and diff.
|
||||
* add multiple language support and dynamic selection based on the
|
||||
Accept-Language request header
|
||||
* add support for key/value files to provide a way for user-defined
|
||||
variables within templates
|
||||
* add optional regex searching for file contents
|
||||
* add new templates for the navigation header and the footer
|
||||
* EZT changes:
|
||||
- add formatting into print directives
|
||||
- add parameters to [include] directives
|
||||
- relax what can go in double quotes
|
||||
- [include] directives are now relative to the current template
|
||||
- throw an exception for unclosed blocks
|
||||
* changes to standalone.py: add flag for regex search
|
||||
* add more help pages
|
||||
* change installer to optionally show diffs
|
||||
* fix to log.ezt and log_table.ezt to select "Side by Side" properly
|
||||
* create dir_alternate.ezt for the flipped rev/name links
|
||||
* various UI tweaks for the directory pages
|
||||
|
||||
|
||||
Version 0.8 (released 10-Dec-2001)
|
||||
|
||||
* add EZT templating mechanism for generating output pages
|
||||
* big update of cvs commit database
|
||||
- updated MySQL support
|
||||
- new CGI
|
||||
- better database caching
|
||||
- switch from old templates to new EZT templates (and integration
|
||||
of look-and-feel)
|
||||
* optional usage of CVSGraph is now builtin
|
||||
* standalone server (for testing) is now provided
|
||||
* shifted some options from viewcvs.conf to the templates
|
||||
* the help at the top of the pages has been shifted to separate help
|
||||
pages, so experienced users don't have to keep seeing it
|
||||
* paths in viewcvs.conf don't require trailing slashes any more
|
||||
* tweak the colorizing for Pascal and Fortran files
|
||||
* fix file readability problem where the user had access via the
|
||||
group, but the process' group did not match that group
|
||||
* some Daylight Savings Time fixes in the CVS commit database
|
||||
* fix tarball generation (the file name) for the root dir
|
||||
* changed default human-readable-diff colors to "stoplight" metaphor
|
||||
* web site and doc revamps
|
||||
* fix the mime types on the download, view, etc links
|
||||
* improved error response when the cvs root is missing
|
||||
* don't try to process vhosts if the config section is not present
|
||||
* various bug fixes and UI tweaks
|
296
INSTALL
296
INSTALL
@@ -1,34 +1,284 @@
|
||||
CONTENTS
|
||||
--------
|
||||
TO THE IMPATIENT
|
||||
INSTALLING VIEWCVS
|
||||
UPGRADING VIEWCVS
|
||||
SQL CHECKIN DATABASE
|
||||
ENSCRIPT CONFIGURATION
|
||||
CVSGRAPH CONFIGURATION
|
||||
IF YOU HAVE PROBLEMS...
|
||||
|
||||
|
||||
TO THE IMPATIENT
|
||||
----------------
|
||||
Congratulations on getting this far. :-)
|
||||
|
||||
Prerequisites: Python 1.5 or later
|
||||
(http://www.python.org/)
|
||||
RCS, Revision Control System
|
||||
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
||||
read-only, physical access to a CVS repository
|
||||
(See http://www.cvshome.org/ for more information)
|
||||
|
||||
Optional: a web server capable of running CGI programs
|
||||
(for example, Apache at http://httpd.apache.org/)
|
||||
GNU-diff to replace broken diff implementations
|
||||
(http://www.gnu.org/software/diffutils/diffutils.html)
|
||||
MySQL to create and query a commit database
|
||||
(http://www.mysql.com/)
|
||||
(http://sourceforge.net/projects/mysql-python)
|
||||
(and Python 1.5.2 or later)
|
||||
Enscript to colorize code displayed from the CVS repository
|
||||
(http://people.ssh.com/mtr/genscript/)
|
||||
CvsGraph for a graphical representation of the CVS revisions
|
||||
(http://www.akhphd.au.dk/~bertho/cvsgraph/)
|
||||
|
||||
GUI Operation:
|
||||
|
||||
If you just want to see what your CVS repository looks like with
|
||||
ViewCVS, type "./standalone.py -g -r /PATH/TO/CVS/ROOT". This
|
||||
will start a tiny webserver serving at http://localhost:7467/.
|
||||
|
||||
Standard operation:
|
||||
|
||||
To start installing right away (on UNIX): type "./viewcvs-install"
|
||||
in the current directory and answer the prompts. When it
|
||||
finishes, edit the file viewcvs.conf in the installation directory
|
||||
to tell viewcvs the paths to your CVS repositories. Next,
|
||||
configure your web server to run <INSTALL>/cgi/viewcvs.cgi, as
|
||||
appropriate for your web server. The section `INSTALLING VIEWCVS'
|
||||
below is still recommended reading.
|
||||
|
||||
|
||||
INSTALLING VIEWCVS
|
||||
------------------
|
||||
|
||||
1) To get viewcvs.cgi to work, make sure that you have Python 1.5.2
|
||||
installed and a webserver which is capable to execute cgi-scripts
|
||||
(either based on the .cgi extension, or by placing the script
|
||||
within a specific directory).
|
||||
1) To get viewcvs.cgi to work, make sure that you have Python 1.5 or
|
||||
greater installed and a webserver which is capable of executing
|
||||
CGI scripts (either based on the .cgi extension, or by placing the
|
||||
script within a specific directory).
|
||||
|
||||
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
|
||||
and "co". This script was tested against RedHat's rcs-5.7-10.rpm
|
||||
|
||||
Note, that the viewcvs.cgi-script needs to have READ-ONLY, physical
|
||||
access to the repository (or a copy of it). Therefore, rsh/ssh or
|
||||
pserver access doesn't work yet.
|
||||
Note, that the viewcvs.cgi script needs to have READ-ONLY, physical
|
||||
access to the CVS repository (or a copy of it). Therefore, rsh/ssh or
|
||||
pserver access to the repository will not work.
|
||||
|
||||
2) Copy viewcvs.cgi to the cgi-script location of your web
|
||||
server. Edit the file for your specific configuration. In
|
||||
particular, examine the following configuration variables:
|
||||
For the more human readable diff formats you need a modern diff utility.
|
||||
If you are using Linux, this is no problem. But on commercial unices
|
||||
you might want to install GNU-diff to be able to use unified or
|
||||
side-by-side diffs.
|
||||
|
||||
If you want to use cvsgraph, you have to obtain and install this
|
||||
separately. See below. This was tested with cvsgraph-1.1.2.
|
||||
|
||||
For the checkin database to work, you will need MySQL >= 3.22,
|
||||
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
|
||||
MySQLdb 0.9.1.
|
||||
|
||||
2) Installation is handled by the ./viewcvs-install script. Run this
|
||||
script and you will be prompted for a installation root path.
|
||||
The default is /usr/local/viewcvs-VERSION, where VERSION is
|
||||
the version of this ViewCVS release. The installer sets the install
|
||||
path in some of the files, and ViewCVS cannot be moved to a
|
||||
different path after the install.
|
||||
|
||||
Note: while 'root' is usually required to create /usr/local/viewcvs,
|
||||
ViewCVS does not have to be installed as root, nor does it run as root.
|
||||
It is just as valid to place ViewCVS in a home directory, too.
|
||||
|
||||
Note: viewcvs-install will create directories if needed. It will
|
||||
prompt before overwriting files that may have been modified (such
|
||||
as viewcvs.conf), thus making it safe to install over the top of
|
||||
a previous installation. It will always overwrite program files,
|
||||
however.
|
||||
|
||||
3) Edit <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf for your specific
|
||||
configuration. In particular, examine the following configuration options:
|
||||
|
||||
cvs_roots
|
||||
default_root
|
||||
rcs_path
|
||||
mime_types_file
|
||||
|
||||
If Python is not located in /usr/local/bin, then you'll need to
|
||||
edit the first line of the file.
|
||||
There are some other options that are usually nice to change. See
|
||||
viewcvs.conf for more information. ViewCVS provides a working,
|
||||
default look. However, if you want to customize the look of ViewCVS
|
||||
then edit the files in <VIEWCVS_INSTALLATION_DIRECTORY>/templates.
|
||||
You need knowledge about HTML to edit the templates.
|
||||
|
||||
ViewCVS has not been tested on the Win32 platform.
|
||||
4) The CGI programs are in <VIEWCVS_INSTALLATION_DIRECTORY>/cgi/. You can
|
||||
symlink to this directory from somewhere in your published HTTP server
|
||||
path if your webserver is configured to follow symbolic links. You can
|
||||
also copy the installed <VIEWCVS_INSTALLATION_DIRECTORY>/cgi/*.cgi scripts
|
||||
after the install (unlike the other files in ViewCVS, the CGI scripts can
|
||||
be moved).
|
||||
|
||||
3) That's it. Try it out.
|
||||
If you are using Apache, then the ScriptAlias directive is very
|
||||
useful for pointing directly to the viwecvs.cgi script.
|
||||
|
||||
NOTE: for security reasons, it is not advisable to install ViewCVS
|
||||
directly into your published HTTP directory tree (due to the MySQL
|
||||
passwords in viewcvs.conf).
|
||||
|
||||
5) That's it for repository browsing. Instructions for getting the
|
||||
SQL checkin database working are below.
|
||||
|
||||
|
||||
WARNING: ViewCVS has not been tested on web servers operating on the
|
||||
Win32 platform.
|
||||
|
||||
|
||||
UPGRADING VIEWCVS
|
||||
-----------------
|
||||
|
||||
Please read the file upgrading.html in the website subdirectory or
|
||||
at <http://viewcvs.sourceforge.net/upgrading.html>.
|
||||
|
||||
|
||||
SQL CHECKIN DATABASE
|
||||
--------------------
|
||||
|
||||
This feature is a clone of the Mozilla Project's Bonsai database. It
|
||||
catalogs every commit in the CVS repository into a SQL database. In fact,
|
||||
the databases are 100% compatible.
|
||||
|
||||
Various queries can be performed on the database. After installing ViewCVS,
|
||||
there are some additional steps required to get the database working.
|
||||
|
||||
1) You need MySQL >= 3.22, and the Python module MySQLdb 0.9.0 installed.
|
||||
Python 1.5.2 is REQUIRED by MySQLdb, therefore to use this part of
|
||||
ViewCVS you must be using Python 1.5.2. Additionally you will need the
|
||||
mxDateTime extension. I've tested with version 1.3.0
|
||||
|
||||
2) You need to create a MySQL user who has permission to create databases.
|
||||
Optionally, you can create a second user with read-only access to the
|
||||
database.
|
||||
|
||||
3) Run the <VIEWCVS_INSTALLATION_DIRECTORY>/make-database script. It will
|
||||
prompt you for your MySQL user, password, and the name of database you
|
||||
want to create. The database name defaults to "ViewCVS". This script
|
||||
creates the database and sets up the empty tables. If you run this on a
|
||||
existing ViewCVS database, you will lose all your data!
|
||||
|
||||
4) Edit your <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf file.
|
||||
There is a [cvsdb] section. You will need to set:
|
||||
|
||||
|
||||
host = # MySQL database server host
|
||||
database_name = # the name of the database you created with
|
||||
# make-database
|
||||
user = # the read/write database user
|
||||
passwd = # password for read/write database user
|
||||
readonly_user = # the readonly database user -- it's pretty
|
||||
# safe to use the read/write user here
|
||||
readonly_passwd = # password for the readonly user
|
||||
|
||||
5) Two programs are provided for updating the checkin database,
|
||||
cvsdbadmin and loginfo-handler. They serve two different purposes.
|
||||
The cvsdbadmin program walks through your CVS repository and adds
|
||||
every commit in every file. This is commonly used for initializing
|
||||
the database from a repository which has been in use. The
|
||||
loginfo-handler script is executed by the CVS server's CVSROOT/loginfo
|
||||
system upon each commit. It makes real-time updates to the checkin
|
||||
database as commits are made to the repository.
|
||||
|
||||
To build a database of all the commits in the CVS repository /home/cvs,
|
||||
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
|
||||
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
|
||||
update mode checks to see if a commit is already in the database,
|
||||
and only adds it if it is absent.
|
||||
|
||||
To get real-time updates, you'll want to checkout the CVSROOT module
|
||||
from your CVS repository and edit CVSROOT/loginfo. Add the line:
|
||||
|
||||
ALL (echo %{sVv}; cat) | <VIEWCVS_INSTALLATION_DIRECTORY>/loginfo-handler
|
||||
|
||||
If you have other scripts invoked by CVSROOT/loginfo, you will want
|
||||
to make sure to change any running under the "DEFAULT" keyword to
|
||||
"ALL" like the loginfo handler, and probably carefully read the
|
||||
execution rules for CVSROOT/loginfo from the CVS manual.
|
||||
|
||||
6) You may want to modify the HTML template file:
|
||||
|
||||
<VIEWCVS_INSTALLATION_DIRECTORY>/templates/query.ezt
|
||||
|
||||
This is used by the query.cgi script to generate part of its HTML output.
|
||||
At some point the currently hardcoded table output will also vanish.
|
||||
|
||||
7) You should be ready to go. Load up the query.cgi script and give
|
||||
it a try.
|
||||
|
||||
|
||||
ENSCRIPT CONFIGURATION
|
||||
----------------------
|
||||
|
||||
Enscript is program that can colorize sourcecode of a lot of languages.
|
||||
Linux distributions like for example SuSE Linux from at least 7.0
|
||||
up to the recently released 7.3 already contain a precompiled and
|
||||
configured enscript 1.6.2 package.
|
||||
|
||||
1) Download genscript from http://people.ssh.com/mtr/genscript/
|
||||
|
||||
2) Configure and compile per instructions with enscript.
|
||||
(I 've not done this, since I'm using the precompiled package
|
||||
delivered with SuSE Linux)
|
||||
|
||||
3) Set the 'use_enscript' option in viewcvs.conf to 1.
|
||||
|
||||
4) That's it!
|
||||
|
||||
5) If you want to colorize exotic languages, you might have to
|
||||
patch 'lib/viewcvs.py' and add a new highlighting file to enscript.
|
||||
I've done this for Modula-2 and submitted the file to the
|
||||
enscript maintainer long ago. If interested in this patch for
|
||||
enscript mailto:pefu@sourceforge.net
|
||||
|
||||
|
||||
CVSGRAPH CONFIGURATION
|
||||
----------------------
|
||||
|
||||
CvsGraph is a program that can display a clickable, graphical tree
|
||||
of files in a CVS repository.
|
||||
|
||||
WARNING: Under certain circumstances (many revisions of a file
|
||||
or many branches or both) cvsgraph can generate very huge images.
|
||||
Especially on thin clients these images may crash the Web-Browser.
|
||||
Currently there is no known way to avoid this behavior of cvsgraph.
|
||||
So you have been warned!
|
||||
|
||||
Nevertheless cvsgraph can be quite helpful on repositories with
|
||||
a reasonable number of revisions and branches.
|
||||
|
||||
1) Install viewcvs according to instructions in 'INSTALLING
|
||||
VIEWCVS' section above. The installation directory is where
|
||||
the 'viewcvs-install' script copied and configured the viewcvs
|
||||
programs.
|
||||
|
||||
2) Download CvsGraph from http://www.akhphd.au.dk/~bertho/cvsgraph/
|
||||
|
||||
3) Configure and compile per instructions with CvsGraph. I had
|
||||
problems with 'configure' finding the gd library. Had to create
|
||||
a link from libgd.so to libgd.do.4.0.0. On Solaris you might
|
||||
want to edit the link command line and add the option -R if
|
||||
you have you libraries at non-standard location.
|
||||
|
||||
4) Place the 'cvsgraph' executable into a directory readable by the
|
||||
userid running the web server. (default is '/usr/local/bin' if
|
||||
you simply type 'make install' in the cvsgraph directory).
|
||||
|
||||
5) Check the setting of the 'cvsgraph_path' option in viewcvs.conf:
|
||||
/usr/local/bin/ is most often NOT contained in $PATH of the
|
||||
webserver process (e.g. Apache), so you will have to edit this.
|
||||
Set the 'use_cvsgraph' option in viewcvs.conf to 1.
|
||||
|
||||
6) That's it!
|
||||
|
||||
7) There is a file <VIEWCVS_INSTALLATION_DIRECTORY>/cvsgraph.conf that
|
||||
you may want to edit if desired to set color and font characteristics.
|
||||
See the cvsgraph.conf documentation. No edits are required in
|
||||
cvsgraph.conf for operation with viewcvs.
|
||||
|
||||
|
||||
IF YOU HAVE PROBLEMS ...
|
||||
@@ -61,4 +311,20 @@ If you've trouble to make viewcvs.cgi work:
|
||||
CVS-Repository. The CGI-script often runs as the user 'nobody'
|
||||
or 'httpd' ..
|
||||
|
||||
o does viewcvs find your RCS utililties? (edit rcs_path)
|
||||
o does viewcvs find your RCS utilities? (edit rcs_path)
|
||||
|
||||
=== If something else happens or you can't get it to work:
|
||||
|
||||
o check the ViewCVS home page:
|
||||
|
||||
http://viewcvs.sourceforge.net/
|
||||
|
||||
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.
|
||||
|
3
README
Normal file
3
README
Normal file
@@ -0,0 +1,3 @@
|
||||
ViewCVS -- Viewing the content of CVS repositories with a Webbrowser.
|
||||
|
||||
Please read the file INSTALL for more information.
|
53
TODO
Normal file
53
TODO
Normal file
@@ -0,0 +1,53 @@
|
||||
PREFACE
|
||||
-------
|
||||
This file will go away soon after release 0.8. Please use the SourceForge
|
||||
tracker to resubmit any of the items listed below, if you think, it is
|
||||
still an issue:
|
||||
http://sourceforge.net/tracker/?group_id=18760
|
||||
Before reporting please check, whether someone else has already done this.
|
||||
Working patches increase the chance to be included into the next release.
|
||||
-- PeFu / October 2001
|
||||
|
||||
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 capability similar to cvs2cl.pl:
|
||||
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
||||
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
||||
|
||||
*) add a tree view of the directory structure (and files?)
|
||||
|
||||
*) include a ConfigParser.py to help older Python installations
|
||||
|
||||
*) add a check for the rcs programs/paths to viewcvs-install. clarify the
|
||||
dependency on RCS in the docs.
|
||||
|
||||
*) have a "check" mode that verifies binaries are available on rcs_path
|
||||
|
||||
-> alternately (probably?): use rcsparse rather than external tools
|
||||
|
||||
KNOWN BUGS
|
||||
----------
|
||||
*) time.timezone seems to not be available on some 1.5.2 installs.
|
||||
I was unable to verify this. On RedHat and SuSE Linux this bug
|
||||
is non existant.
|
||||
|
||||
*) With old repositories containing many branches, tags or thousands
|
||||
or revisions, the cvsgraph feature becomes unusable (see INSTALL).
|
||||
ViewCVS can't do much about this, but it might be possible to
|
||||
investigate the number of branches, tags and revision in advance
|
||||
and disable the cvsgraph links, if the numbers exceed a certain
|
||||
treshold.
|
192
cgi/cvsgraph.conf.dist
Normal file
192
cgi/cvsgraph.conf.dist
Normal file
@@ -0,0 +1,192 @@
|
||||
# CvsGraph configuration
|
||||
#
|
||||
# - Empty lines and whitespace are ignored.
|
||||
#
|
||||
# - Comments start with '#' and everything until
|
||||
# end of line is ignored.
|
||||
#
|
||||
# - Strings are C-style strings in which characters
|
||||
# may be escaped with '\' and written in octal
|
||||
# and hex escapes. Note that '\' must be escaped
|
||||
# if it is to be entered as a character.
|
||||
#
|
||||
# - Some strings are expanded with printf like
|
||||
# conversions which start with '%'. Not all
|
||||
# are applicable at all times, in which case they
|
||||
# will expand to noting.
|
||||
# %c = cvsroot (with trailing '/')
|
||||
# %C = cvsroot (*without* trailing '/')
|
||||
# %m = module (with trailing '/')
|
||||
# %M = module (*without* trailing '/')
|
||||
# %f = filename without path
|
||||
# %F = filename without path and with ",v" stripped
|
||||
# %p = path part of filename (with trailing '/')
|
||||
# %r = number of revisions
|
||||
# %b = number of branches
|
||||
# %% = '%'
|
||||
# %R = the revision number (e.g. '1.2.4.4')
|
||||
# %P = previous revision number
|
||||
# %B = the branch number (e.g. '1.2.4')
|
||||
# %d = date of revision
|
||||
# %a = author of revision
|
||||
# %s = state of revision
|
||||
# %t = current tag of branch or revision
|
||||
# %0..%9 = command-line argument -0 .. -9
|
||||
# ViewCVS currently uses the following two command-line arguments to
|
||||
# pass URL information to cvsgraph:
|
||||
# -6 request.amp_query (the query preceeded with '&')
|
||||
# -7 request.qmark_query (the query preceed with '?')
|
||||
#
|
||||
# - Numbers may be entered as octal, decimal or
|
||||
# hex as in 0117, 79 and 0x4f respectively.
|
||||
#
|
||||
# - Fonts are numbered 0..4 (defined as in libgd)
|
||||
# 0 = tiny
|
||||
# 1 = small
|
||||
# 2 = medium (bold)
|
||||
# 3 = large
|
||||
# 4 = giant
|
||||
#
|
||||
# - Colors are a string like html-type colors in
|
||||
# the form "#rrggbb" with parts written in hex
|
||||
# rr = red (00..ff)
|
||||
# gg = green (00-ff)
|
||||
# bb = blue (00-ff)
|
||||
#
|
||||
# - There are several reserved words besides of the
|
||||
# feature-keywords. These additional reserved words
|
||||
# expand to numerical values:
|
||||
# * false = 0
|
||||
# * true = 1
|
||||
# * left = 0
|
||||
# * center = 1
|
||||
# * right = 2
|
||||
# * gif = 0
|
||||
# * png = 1
|
||||
# * jpeg = 2
|
||||
# * tiny = 0
|
||||
# * small = 1
|
||||
# * medium = 2
|
||||
# * large = 3
|
||||
# * giant = 4
|
||||
|
||||
# cvsroot <string>
|
||||
# The *absolute* base directory where the
|
||||
# CSV/RCS repository can be found
|
||||
# cvsmodule <string>
|
||||
#
|
||||
cvsroot = "--unused--"; # unused with ViewCVS, will be overridden
|
||||
cvsmodule = ""; # unused with ViewCVS -- please leave it blank
|
||||
|
||||
# color_bg <color>
|
||||
# The background color of the image
|
||||
color_bg = "#ffffff";
|
||||
|
||||
# date_format <string>
|
||||
# The strftime(3) format string for date and time
|
||||
date_format = "%d-%b-%Y %H:%M:%S";
|
||||
|
||||
box_shadow = true;
|
||||
|
||||
tag_font = medium;
|
||||
tag_color = "#007000";
|
||||
|
||||
rev_font = giant;
|
||||
rev_color = "#000000";
|
||||
rev_bgcolor = "#f0f0f0";
|
||||
rev_separator = 1;
|
||||
rev_minline = 15;
|
||||
rev_maxline = 30;
|
||||
rev_lspace = 5;
|
||||
rev_rspace = 5;
|
||||
rev_tspace = 3;
|
||||
rev_bspace = 3;
|
||||
rev_text = "%d"; # or "%d\n%a, %s" for author and state too
|
||||
rev_text_font = tiny;
|
||||
rev_text_color = "#500020";
|
||||
|
||||
# branch_font <number>
|
||||
# The font of the number and tags
|
||||
# branch_color <color>
|
||||
# All branch element's color
|
||||
# branch_[lrtb]space <number>
|
||||
# Interior spacing (margin)
|
||||
# branch_margin <number>
|
||||
# Exterior spacing
|
||||
# branch_connect <number>
|
||||
# Length of the vertical connector
|
||||
branch_font = medium;
|
||||
branch_color = "#0000c0";
|
||||
branch_bgcolor = "#ffffc0";
|
||||
branch_lspace = 5;
|
||||
branch_rspace = 5;
|
||||
branch_tspace = 3;
|
||||
branch_bspace = 3;
|
||||
branch_margin = 15;
|
||||
branch_connect = 8;
|
||||
|
||||
# title <string>
|
||||
# The title string is expanded (see above for details)
|
||||
# title_[xy] <number>
|
||||
# Postion of title
|
||||
# title_font <number>
|
||||
# The font
|
||||
# title_align <number>
|
||||
# 0 = left
|
||||
# 1 = center
|
||||
# 2 = right
|
||||
# title_color <color>
|
||||
title = "%C: %p%F\nRevisions: %r, Branches: %b";
|
||||
title_x = 10;
|
||||
title_y = 5;
|
||||
title_font = small;
|
||||
title_align = left;
|
||||
title_color = "#800000";
|
||||
|
||||
# Margins of the image
|
||||
# Note: the title is outside the margin
|
||||
margin_top = 35;
|
||||
margin_bottom = 10;
|
||||
margin_left = 10;
|
||||
margin_right = 10;
|
||||
|
||||
# Image format(s)
|
||||
# image_type <number|{gif,jpeg,png}>
|
||||
# gif (0) = Create gif image
|
||||
# png (1) = Create png image
|
||||
# jpeg (2) = Create jpeg image
|
||||
# Image types are available if they can be found in
|
||||
# the gd library. Newer versions of gd do not have
|
||||
# gif anymore. CvsGraph will automatically generate
|
||||
# png images instead.
|
||||
# image_quality <number>
|
||||
# The quality of a jpeg image (1..100)
|
||||
image_type = png;
|
||||
image_quality = 75;
|
||||
|
||||
# HTML ImageMap generation
|
||||
# map_name <string>
|
||||
# The name= attribute in <map name="mapname">...</map>
|
||||
# map_branch_href <string>
|
||||
# map_branch_alt <string>
|
||||
# map_rev_href <string>
|
||||
# map_rev_alt <string>
|
||||
# map_diff_href <string>
|
||||
# map_diff_alt <string>
|
||||
# These are the href= and alt= attributes in the <area>
|
||||
# tags of html. The strings are expanded (see above).
|
||||
map_name = "MyMapName";
|
||||
map_branch_href = "href=\"%m%F?only_with_tag=%t%8%6\"";
|
||||
map_branch_alt = "alt=\"%0 %t (%B)\"";
|
||||
# You might want to experiment with the following setting:
|
||||
# 1. The default setting will take you to a ViewCVS generated page displaying
|
||||
# that revision of the file, if you click into a revision box:
|
||||
map_rev_href = "href=\"%m%F?rev=%R&content-type=text/vnd.viewcvs-markup%6\"";
|
||||
# 2. This alternative setting will take you to the anchor representing this
|
||||
# revision on a ViewCVS generated Log page for that file:
|
||||
# map_rev_href = "href=\"%m%F%7#rev%R\"";
|
||||
#
|
||||
map_rev_alt = "alt=\"%1 %t (%R)\"";
|
||||
map_diff_href = "href=\"%m%F.diff?r1=%P&r2=%R%8%6\"";
|
||||
map_diff_alt = "alt=\"%2 %P <-> %R\"";
|
||||
|
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()
|
||||
|
51
cgi/query.cgi
Executable file
51
cgi/query.cgi
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# query.cgi: View CVS commit database by web browser
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This is a teeny stub to launch the main ViewCVS app. It checks the load
|
||||
# average, then loads the (precompiled) viewcvs.py file and runs it.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# These values will be set during the installation process. During
|
||||
# development, they will remain None.
|
||||
#
|
||||
|
||||
LIBRARY_DIR = None
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# Adjust sys.path to include our library directory
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path[:0] = ['../lib'] # any other places to look?
|
||||
|
||||
#########################################################################
|
||||
|
||||
import query
|
||||
query.run_cgi()
|
2438
cgi/viewcvs.cgi
2438
cgi/viewcvs.cgi
File diff suppressed because it is too large
Load Diff
460
cgi/viewcvs.conf.dist
Normal file
460
cgi/viewcvs.conf.dist
Normal file
@@ -0,0 +1,460 @@
|
||||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# Configuration file for ViewCVS
|
||||
#
|
||||
# Information on ViewCVS is located at the following web site:
|
||||
# http://viewcvs.sourceforge.net/
|
||||
#
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# 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
|
||||
# forbidden
|
||||
#
|
||||
# use_enscript
|
||||
# use_cvsgraph
|
||||
#
|
||||
# For Python source colorization:
|
||||
#
|
||||
# py2html_path
|
||||
#
|
||||
# If your icons are in a special location:
|
||||
#
|
||||
# icons
|
||||
#
|
||||
# Also, review the .ezt templates in the templates/ directory to adjust them
|
||||
# for your particular site.
|
||||
#
|
||||
|
||||
#
|
||||
# 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
|
||||
#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 of the generated pages.
|
||||
# It must be replaced with the address of the local CVS maintainer.
|
||||
address = <a href="mailto:cvs-admin@insert.your.domain.here">No CVS admin address has been configured</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).
|
||||
#
|
||||
# This configuration can be a simple list of modules, or it can get quite
|
||||
# complex:
|
||||
#
|
||||
# *) The "!" can be used before a module to explicitly state that it
|
||||
# is NOT forbidden. Whenever this form is seen, then all modules will
|
||||
# be forbidden unless one of the "!" modules match.
|
||||
#
|
||||
# *) Shell-style "glob" expressions may be used. "*" will match any
|
||||
# sequence of zero or more characters, "?" will match any single
|
||||
# character, "[seq]" will match any character in seq, and "[!seq]"
|
||||
# will match any character not in seq.
|
||||
#
|
||||
# *) Tests are performed in sequence. The first match will terminate the
|
||||
# testing. This allows for more complex allow/deny patterns.
|
||||
#
|
||||
# Tests are case-sensitive.
|
||||
#
|
||||
forbidden =
|
||||
|
||||
# Some examples:
|
||||
#
|
||||
# Disallow "example" but allow all others:
|
||||
# forbidden = example
|
||||
#
|
||||
# Disallow "example1" and "example2" but allow all others:
|
||||
# forbidden = example1, example2
|
||||
#
|
||||
# Allow *only* "example1" and "example2":
|
||||
# forbidden = !example1, !example2
|
||||
#
|
||||
# Forbid modules starting with "x":
|
||||
# forbidden = x*
|
||||
#
|
||||
# Allow modules starting with "x" but no others:
|
||||
# forbidden = !x*
|
||||
#
|
||||
# Allow "xml", forbid other modules starting with "x", and allow the rest:
|
||||
# forbidden = !xml, x*, !*
|
||||
#
|
||||
|
||||
#
|
||||
# This option provides a mechanism for custom key/value pairs to be
|
||||
# available to templates. These are stored in key/value files (KV files).
|
||||
#
|
||||
# Pathnames to the KV files are listed here, specified as absolute paths
|
||||
# or relative to this configuration file. The kV files follow the same
|
||||
# format as this configuration file. It may have multiple, user-defined
|
||||
# sections, and user-defined options in those sections. These are all
|
||||
# placed into a structure available to the templates as:
|
||||
#
|
||||
# kv.SECTION.OPTION
|
||||
#
|
||||
# Note that an option name can be dotted. For example:
|
||||
#
|
||||
# [my_images]
|
||||
# logos.small = /images/small-logo.png
|
||||
# logos.big = /images/big-logo.png
|
||||
#
|
||||
# Templates can use these with a directive like: [kv.my_images.logos.small]
|
||||
#
|
||||
# Note that sections across multiple files will be merged. If two files
|
||||
# have a [my_images] section, then the options will be merged together.
|
||||
# If two files have the same option name in a section, then one will
|
||||
# overwrite the other (it is unspecified regarding which "wins").
|
||||
#
|
||||
# To further categorize the KV files, and how the values are provided to
|
||||
# the templates, a KV file name may be annotated with an additional level
|
||||
# of dotted naming. For example:
|
||||
#
|
||||
# kv_files = [asf]kv/images.conf
|
||||
#
|
||||
# Assuming the same section as above, the template would refer to an image
|
||||
# using [kv.asf.my_images.logos.small]
|
||||
#
|
||||
# Lastly, it is possible to use %lang% in the filenames to specify a
|
||||
# substitution of the selected language-tag.
|
||||
#
|
||||
kv_files =
|
||||
|
||||
# example:
|
||||
# kv_files = kv/file1.conf, kv/file2.conf, [i18n]kv/%lang%_data.conf
|
||||
#
|
||||
|
||||
#
|
||||
# The languages available to ViewCVS. There are several i18n mechanisms
|
||||
# available:
|
||||
#
|
||||
# 1) using key/value extension system and reading KV files based on
|
||||
# the selected language
|
||||
# 2) GNU gettext to substitute text in the templates
|
||||
# 3) using different templates, based on the selected language
|
||||
#
|
||||
# ### NOTE: at the moment, the GNU gettext style is not implemented
|
||||
#
|
||||
# This option is a comma-separated list of language-tag values. The first
|
||||
# language-tag listed is the default language, and will be used if an
|
||||
# Accept-Language header is not present in the request, or none of the
|
||||
# user's requested languages are available. If there are ties on the
|
||||
# selection of a language, then the first to appear in the list is chosen.
|
||||
#
|
||||
languages = en-us
|
||||
|
||||
# other examples:
|
||||
#
|
||||
# languages = en-us, de
|
||||
# languages = en-us, en-gb, de
|
||||
# languages = de, fr, en-us
|
||||
#
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[templates]
|
||||
|
||||
#
|
||||
# The templates are specified relative to the configuration file. Absolute
|
||||
# paths may be used, if you want to keep these elsewhere.
|
||||
#
|
||||
# If %lang% occurs in the pathname, then the selected language will be
|
||||
# substituted.
|
||||
#
|
||||
# Note: the selected language is defined by the "languages" item in the
|
||||
# [general] section, and based on the request's Accept-Language
|
||||
# header.
|
||||
#
|
||||
|
||||
query = templates/query.ezt
|
||||
footer = templates/footer.ezt
|
||||
diff = templates/diff.ezt
|
||||
graph = templates/graph.ezt
|
||||
annotate = templates/annotate.ezt
|
||||
markup = templates/markup.ezt
|
||||
|
||||
directory = templates/directory.ezt
|
||||
# For an alternate form, where the first column displays a revision number
|
||||
# and brings you to the log view (and the filename displays the HEAD), then
|
||||
# you may use this template:
|
||||
# directory = templates/dir_alternate.ezt
|
||||
|
||||
log = templates/log.ezt
|
||||
# For a log view where the revisions are displayed in a table, you may
|
||||
# want to try this template:
|
||||
# log = templates/log_table.ezt
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[cvsdb]
|
||||
|
||||
#host = localhost
|
||||
#database_name = ViewCVS
|
||||
#user =
|
||||
#passwd =
|
||||
#readonly_user =
|
||||
#readonly_passwd =
|
||||
#row_limit = 1000
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[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 the contents of the Attic subdirectory
|
||||
# 1 Hide dead files inside Attic subdir
|
||||
# 0 Show the files which are inside the Attic subdir
|
||||
hide_attic = 1
|
||||
|
||||
# log_sort: Sort order for CVS logs
|
||||
# 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
|
||||
# l Long human readable (more context)
|
||||
diff_format = h
|
||||
|
||||
# hide_cvsroot: Don't show the CVSROOT directory
|
||||
# 1 Hide CVSROOT directory
|
||||
# 0 Show CVSROOT directory
|
||||
hide_cvsroot = 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.
|
||||
allow_annotate = 1
|
||||
|
||||
# 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
|
||||
|
||||
# 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..
|
||||
|
||||
#
|
||||
# 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
|
||||
|
||||
diff_font_face = Helvetica,Arial
|
||||
diff_font_size = -1
|
||||
|
||||
# should we use 'enscript' for syntax coloring?
|
||||
use_enscript = 0
|
||||
|
||||
#
|
||||
# if the enscript program is not on the path, set this value
|
||||
#
|
||||
enscript_path =
|
||||
# enscript_path = /usr/bin/
|
||||
|
||||
#
|
||||
# ViewCVS has its own set of mappings from filename extensions and filenames
|
||||
# to languages. If the language is not supported by enscript, then it can
|
||||
# be listed here to disable the use of enscript.
|
||||
#
|
||||
disable_enscript_lang =
|
||||
# disable_enscript_lang = perl, cpp
|
||||
|
||||
#
|
||||
# ViewCVS can generate tarball from a repository on the fly.
|
||||
#
|
||||
allow_tar = 0
|
||||
# allow_tar = 1
|
||||
|
||||
#
|
||||
# Use CvsGraph. See http://www.akhphd.au.dk/~bertho/cvsgraph/ for
|
||||
# documentation and download.
|
||||
#
|
||||
use_cvsgraph = 0
|
||||
# use_cvsgraph = 1
|
||||
|
||||
#
|
||||
# if the cvsgraph program is not on the path, set this value
|
||||
#
|
||||
cvsgraph_path =
|
||||
# cvsgraph_path = /usr/local/bin/
|
||||
|
||||
#
|
||||
# Location of the customized cvsgraph configuration file.
|
||||
# You will need an absolute pathname here:
|
||||
#
|
||||
cvsgraph_conf = <VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf
|
||||
|
||||
#
|
||||
# Set to enable regular expression search of all files in a directory
|
||||
#
|
||||
# WARNING:
|
||||
#
|
||||
# Enabling this option can consume HUGE amounts of server time. A
|
||||
# "checkout" must be performed on *each* file in a directory, and
|
||||
# the result needs to be searched for a match against the regular
|
||||
# expression.
|
||||
#
|
||||
#
|
||||
# SECURITY WARNING: Denial Of Service
|
||||
#
|
||||
# Since a user can enter the regular expression, it is possible for
|
||||
# them to enter an expression with many alternatives and a lot of
|
||||
# backtracking. Executing that search over thousands of lines over
|
||||
# dozens of files can easily tie up a server for a long period of
|
||||
# time.
|
||||
#
|
||||
# This option should only be used on sites with trusted users. It is
|
||||
# highly inadvisable to use this on a public site.
|
||||
#
|
||||
use_re_search = 0
|
||||
# use_re_search = 1
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
[vhosts]
|
||||
### DOC
|
||||
|
||||
# vhost1 = glob1, glob2
|
||||
# vhost2 = glob3, glob4
|
||||
|
||||
# [vhost1-section]
|
||||
# option = value
|
||||
# [vhost1-othersection]
|
||||
# option = value
|
||||
# [vhost2-section]
|
||||
# option = value
|
||||
|
||||
#
|
||||
# Here is an example:
|
||||
#
|
||||
# [vhosts]
|
||||
# lyra = *lyra.org
|
||||
#
|
||||
# [lyra-general]
|
||||
# forbidden = hideme
|
||||
#
|
||||
# [lyra-options]
|
||||
# show_logs = 0
|
||||
#
|
||||
# Note that "lyra" is the "canonical" name for all hosts in the lyra.org
|
||||
# domain. This canonical name is then used within the additional, vhost-
|
||||
# specific sections to override specific values in the common sections.
|
||||
#
|
||||
|
||||
#---------------------------------------------------------------------------
|
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]`
|
190
lib/accept.py
Normal file
190
lib/accept.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# accept.py: parse/handle the various Accept headers from the client
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def language(hdr):
|
||||
"Parse an Accept-Language header."
|
||||
|
||||
# parse the header, storing results in a _LanguageSelector object
|
||||
return _parse(hdr, _LanguageSelector())
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
_re_token = re.compile(r'\s*([^\s;,"]+|"[^"]*")+\s*')
|
||||
_re_param = re.compile(r';\s*([^;,"]+|"[^"]*")+\s*')
|
||||
_re_split_param = re.compile(r'([^\s=])\s*=\s*(.*)')
|
||||
|
||||
def _parse(hdr, result):
|
||||
# quick exit for empty or not-supplied header
|
||||
if not hdr:
|
||||
return result
|
||||
|
||||
pos = 0
|
||||
while pos < len(hdr):
|
||||
name = _re_token.match(hdr, pos)
|
||||
if not name:
|
||||
raise AcceptParseError()
|
||||
a = result.item_class(string.lower(name.group(1)))
|
||||
pos = name.end()
|
||||
while 1:
|
||||
# are we looking at a parameter?
|
||||
match = _re_param.match(hdr, pos)
|
||||
if not match:
|
||||
break
|
||||
param = match.group(1)
|
||||
pos = match.end()
|
||||
|
||||
# split up the pieces of the parameter
|
||||
match = _re_split_param.match(param)
|
||||
if not match:
|
||||
# the "=" was probably missing
|
||||
continue
|
||||
|
||||
pname = string.lower(match.group(1))
|
||||
if pname == 'q' or pname == 'qs':
|
||||
try:
|
||||
a.quality = float(match.group(2))
|
||||
except ValueError:
|
||||
# bad float literal
|
||||
pass
|
||||
elif pname == 'level':
|
||||
try:
|
||||
a.level = float(match.group(2))
|
||||
except ValueError:
|
||||
# bad float literal
|
||||
pass
|
||||
elif pname == 'charset':
|
||||
a.charset = string.lower(match.group(2))
|
||||
|
||||
result.append(a)
|
||||
if hdr[pos:pos+1] == ',':
|
||||
pos = pos + 1
|
||||
|
||||
return result
|
||||
|
||||
class _AcceptItem:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.quality = 1.0
|
||||
self.level = 0.0
|
||||
self.charset = ''
|
||||
|
||||
def __str__(self):
|
||||
s = self.name
|
||||
if self.quality != 1.0:
|
||||
s = '%s;q=%.3f' % (s, self.quality)
|
||||
if self.level != 0.0:
|
||||
s = '%s;level=%.3f' % (s, self.level)
|
||||
if self.charset:
|
||||
s = '%s;charset=%s' % (s, self.charset)
|
||||
return s
|
||||
|
||||
class _LanguageRange(_AcceptItem):
|
||||
def matches(self, tag):
|
||||
"Match the tag against self. Returns the qvalue, or None if non-matching."
|
||||
if tag == self.name:
|
||||
return self.quality
|
||||
|
||||
# are we a prefix of the available language-tag
|
||||
name = self.name + '-'
|
||||
if tag[:len(name)] == name:
|
||||
return self.quality
|
||||
return None
|
||||
|
||||
class _LanguageSelector:
|
||||
item_class = _LanguageRange
|
||||
|
||||
def __init__(self):
|
||||
self.requested = [ ]
|
||||
|
||||
def select_from(self, avail):
|
||||
"""Select one of the available choices based on the request.
|
||||
|
||||
Note: if there isn't a match, then the first available choice is
|
||||
considered the default.
|
||||
|
||||
avail is a list of language-tag strings of available languages
|
||||
"""
|
||||
|
||||
# tuples of (qvalue, language-tag)
|
||||
matches = [ ]
|
||||
|
||||
# try matching all pairs of desired vs available, recording the
|
||||
# resulting qvalues. we also need to record the longest language-range
|
||||
# that matches since the most specific range "wins"
|
||||
for tag in avail:
|
||||
longest = 0
|
||||
final = 0.0
|
||||
|
||||
# check this tag against the requests from the user
|
||||
for want in self.requested:
|
||||
qvalue = want.matches(tag)
|
||||
#print 'have %s. want %s. qvalue=%s' % (tag, want.name, qvalue)
|
||||
if qvalue is not None and len(want.name) > longest:
|
||||
# we have a match and it is longer than any we may have had
|
||||
final = qvalue
|
||||
longest = len(want.name)
|
||||
|
||||
# a non-zero qvalue is a potential match
|
||||
if final:
|
||||
matches.append((final, tag))
|
||||
|
||||
# if we have any matches, then look at the highest qvalue
|
||||
if matches:
|
||||
matches.sort()
|
||||
qvalue, tag = matches[-1]
|
||||
|
||||
if len(matches) >= 2 and matches[-2][0] == qvalue:
|
||||
#print "non-deterministic choice", avail
|
||||
pass
|
||||
|
||||
# if the qvalue is non-zero, then we have a valid match
|
||||
if qvalue:
|
||||
return tag
|
||||
# the qvalue is zero (non-match). drop thru to return the default
|
||||
|
||||
# return the default language tag
|
||||
return avail[0]
|
||||
|
||||
def append(self, item):
|
||||
self.requested.append(item)
|
||||
|
||||
class AcceptParseError(Exception):
|
||||
pass
|
||||
|
||||
def _test():
|
||||
s = language('en')
|
||||
assert s.select_from(['en']) == 'en'
|
||||
assert s.select_from(['en', 'de']) == 'en'
|
||||
assert s.select_from(['de', 'en']) == 'en'
|
||||
|
||||
s = language('fr, de;q=0.9, en-gb;q=0.7, en;q=0.6, en-gb-foo;q=0.8')
|
||||
assert s.select_from(['en']) == 'en'
|
||||
assert s.select_from(['en-gb-foo']) == 'en-gb-foo'
|
||||
assert s.select_from(['de', 'fr']) == 'fr'
|
||||
assert s.select_from(['de', 'en-gb']) == 'de'
|
||||
assert s.select_from(['en-gb', 'en-gb-foo']) == 'en-gb-foo'
|
||||
assert s.select_from(['en-bar']) == 'en-bar'
|
||||
assert s.select_from(['en-gb-bar', 'en-gb-foo']) == 'en-gb-foo'
|
||||
|
||||
# non-deterministic. en-gb;q=0.7 matches both avail tags.
|
||||
#assert s.select_from(['en-gb-bar', 'en-gb']) == 'en-gb'
|
199
lib/apache_icons.py
Normal file
199
lib/apache_icons.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#! /usr/bin/env python
|
||||
# This file was automatically generated! DO NOT EDIT!
|
||||
# Howto regenerate: see ../tools/bin2inline.py
|
||||
# $Id$
|
||||
# You have been warned. But if you want to edit, go ahead using your
|
||||
# favorite editor :-)
|
||||
## vim:ts=4:et:nowrap
|
||||
# [Emacs: -*- python -*-]
|
||||
|
||||
_ap_icons = {
|
||||
"/icons/apache_pb.gif" :
|
||||
'GIF89a\003\001 \000\367\000\000\377\377\377'
|
||||
'\316\316\316\245\245\245\204\204\204ssskkkZ'
|
||||
'ZZ!\030\030\377B\030\3771\000\275R\020\336\255'
|
||||
'\204\357\234B\377\204\000\377\316\030\377\316\000\316\316\306'
|
||||
'\275\275\3061\000\377c\000\377\234\000\377\357\000\377\347'
|
||||
'J\357\336{\336\326\245\316\377\000\234\357J\214\377\000'
|
||||
'c\347\204\234\357Rc\377\000\030\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
|
||||
'\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004'
|
||||
'\001\000\000\001\000,\000\000\000\000\003\001 \000G\010'
|
||||
'\377\000\003\010\034H\260\240\301\203\010\023*\\\310\260'
|
||||
'\241\303\207\020#J\234H\261\242E\206\021\016\034\030'
|
||||
'0\260\300\200\001\002\002l0\260!\303\006\017(Q'
|
||||
'&X\331\300A\204\201\007\376\3753\020\363_\004\231'
|
||||
'\007h\376;\020 \246\001\0032\011\370\024\2603\302'
|
||||
'\200\231=e\006%03\002P\002\001p\352\374W'
|
||||
'\020\350L\253\004\254\336\264y\261\253\327\257`\303\026'
|
||||
'|Iv\203\331\012\033*P\2400a\302?\011J'
|
||||
'\343\276\225@w\002\333\265\0252P\3100!\203I'
|
||||
'\277\005P\376;\231\022\001\002\001._\212]\314\270'
|
||||
'\261\343\307b#Dh0\000A\203\225\011R\012N'
|
||||
'\371\317\203\001\017f\375\206\366+\332d^\275\245\323'
|
||||
"f\030\360\240u\353\006\017\032\314\374'\200\243\300\013"
|
||||
"u'Hh\333\226\356\356\335\272%\\0xa\355"
|
||||
'\332\341\022/\220^\316\274y\006\015\0065{X\330'
|
||||
'A3\206\202\0300\257d\270@{\202\203\014\032\210'
|
||||
'\377o\000\371\253\200\363\347\313/\214\220\236 \373\220'
|
||||
'\352\343\313\237\357\360\345y\271o\347\266]\233!o'
|
||||
'\337\275\031L\225\222\001\010(PSQ\002\035\305S'
|
||||
'T\264\011$\300\201P\305\264\240@L)\025\222S'
|
||||
'rU\025\327F\016\312\244\030} \2068_\004\010'
|
||||
'\030\260Rg\240\225d\332r\024T`\327ZnY'
|
||||
'\205\237\\p\375\366\033[\025\2505\300^\033\000\230'
|
||||
'\300\000-\271\324P\004\272\265\025\200\005\277M\360\341'
|
||||
'A\305\031G\301BN"\207\335r\303Y\260\334u'
|
||||
'\011I\267\220\006\326a\347\035w_\032\024\336x"'
|
||||
'\226i\346\231h\246\251\346\232l\266\351\246A\017.'
|
||||
'9\320y\005(\346\200\000\015\340\331PM\002X\305'
|
||||
'\221Q\006\014`\337O/\031\000\225S\006\220E\200'
|
||||
'\240\024\032 \200S\360\011@\000|\002E@@V'
|
||||
'2i\370\317\000\216\006\320g\242\236\032\372\346\250e'
|
||||
'\032U\023P \355\367_\217*J\247RM\037n'
|
||||
'\377\005U\205\222)\365\023N\001\034E\033zIM'
|
||||
'\010\324\001\350=*S\242Vi\352\351\260\001\310('
|
||||
"'\251\314\252\367\350O\004\024\020\000_\301\3218\227"
|
||||
'\001\022\030p\227\213\250M0\022a\011\240\310\222x'
|
||||
'\206!\260\254|[Q\332\354\272"\032\225\222Y\241'
|
||||
'\251\325\237\213l\3015c~\276\365\266\037\275x\241'
|
||||
'6\322_&\201\326\300\271\023\345;\201\005\0155\351'
|
||||
'\344\302\014\033\207%B\0324\367pB\030\270j\261'
|
||||
'\253\034\030\224\035f\035\200\251\035x\343\205,\362\310'
|
||||
'\013<\206\247\211\232\015\206\222\212\244\261\314\234qj'
|
||||
'\3355\301On\331;#P\015\030\020\333k\2559'
|
||||
" \244\306ENP\020\222ENL\220\302\015'\275"
|
||||
'\226\321\312\221&\345m\313e|\220\226\025m\354\335'
|
||||
'\325Xc\006\362\310"\227<\252d\001HfT\003'
|
||||
'\033!p\000\002)\036\340\301\000\033\014\000\332\333\360'
|
||||
'\232\205\322\001q\033\345\300\000w\252{d\276F\037'
|
||||
'\377\204Ap\022 <\020\322O7\304\\\337~\037'
|
||||
'>\020\007\0277\216R\307^~\274Pw\222\0274'
|
||||
'&\327\230\213\007\001\273\234w\356\371\347\240\207.\372'
|
||||
'\350\244\227\3169\301a\203M\321\201\310\212X,A'
|
||||
'\257\233.;D\246\352-)\243hk\226\200e\003'
|
||||
'#T\323\261\377L*\227\260\233\022e\323\257\311*'
|
||||
'\005lRV\361t\340\001/U\030\227\246\312oe'
|
||||
'\000\363\263go\320\000\032\011\012@\001\005@\205Z'
|
||||
'\005\004\2304RJ,\355~\030\353\300\032?\253L'
|
||||
'\222\342$!\374\025j\224)\237\317\356d?\2602'
|
||||
'q\024\273@\257\363\311V|\242\275\002\036DF\267'
|
||||
'\252\213\223\374\223\232\317x@)\0360\314\300jB'
|
||||
'A\000\302OW\327\263\236\244\234\267\2239\015\353S'
|
||||
'I\311\020\354\360\023\022\343]\317\200(\014[\001\200'
|
||||
'b\000\217\330L)\276\221\000\214\214\343\257\275\020\000'
|
||||
'\\\036X\211\002,\203\030\325%\204=\014yTW'
|
||||
'\377X\230B\024\212\215=/\351Vo^\010\303|'
|
||||
'\311\320.}\351V\3344\343\300\0346\240\\z\203'
|
||||
'\314\005rR\304.V*l\271\353\221_\3622\303'
|
||||
'\266\334k.7z"\031\333\242\032\322\334\020\\\231'
|
||||
')\014\352(R\227\302%Dix\274\000\342\012r'
|
||||
'\001\013\370Qp\015\201\000\007\030\347\270\224p\000q'
|
||||
'\030\350\200";\3405\205Pn%\215$\010\003&'
|
||||
'I\311JZ\262\222\221\264\310d\334\366.\227\235\246'
|
||||
'E0b"~jd\243\375\340H-1\243@\001'
|
||||
'\344V\030\004\260\315\\\022\311\315o\000\231\020\244Q'
|
||||
'\340\002\270\314\245.\011\227\020\347\370e\217\003\221\316'
|
||||
' \207IL\016h\240:)\331\034A\254\346\001\310'
|
||||
'92L\226\013\331\002\246I\315jZ\2231w\273'
|
||||
'L\034W\026\232\264\250\34649B\245]\012\000\027'
|
||||
'\031\235Q&\300\341\015o\332&\236\330\300\346g\010'
|
||||
'!\022p\006B\264\335\240\316\226\273\314g\2244F'
|
||||
'\363\245\301])K\232Y\010\006\016\211\310\254\031\364'
|
||||
"j[\023\317%'\231I\365\214\315D+\331\014J"
|
||||
'H\302\315n.\3474\375\361\313^\310\330\242\276\304'
|
||||
'\314\0003\273U\\~b\000\361\300S \177K\222'
|
||||
':y\223$\304\331\022\217\306\321#\037}IS\251'
|
||||
'\025\204j\011\341\200"\017\2511\357\024\223\003\326|'
|
||||
'\244\326\304$\315\240.@\231i\222Lm\\\351\235'
|
||||
'\034\272\252G\025e\245\001\376B\030\222\250\250\000\355'
|
||||
'\204\215k|\366!\334\000G\217\030\010k\000\302J'
|
||||
'\326\013,\261p\274\214H\323\374\002V\262\2725\254'
|
||||
'\034X\016t\010"\314\237\016\363\230\232A*J\241'
|
||||
'\231\020\241~\207\250\343\241\244Q\257\351E\332\351r'
|
||||
'\216\3569\354@0\240K`\372M\227\020\031\3500'
|
||||
"'&Y\273ZV\257\003\201@5\035+\020\315V"
|
||||
'\363 \203\015-5\013K\332\322\232\366\264\250=\010'
|
||||
'@\002\004\004\000;',
|
||||
|
||||
"/icons/small/back.gif" :
|
||||
'GIF89a\020\000\020\000\242\377\000!!!'
|
||||
'111ZZZ\204\204\204\214\214\214\300\300\300\000'
|
||||
'\000\000\000\000\000!\371\004\001\000\000\005\000,\000\000'
|
||||
'\000\000\020\000\020\000\000\003FX\272\334\015\300I`'
|
||||
'L\224\213V\213\213\320\025 t\304f\020]Qn'
|
||||
'(i\266\303\240\310\312Z\215D,\010\255\022\230\236'
|
||||
'\20000Z\010L\203\241\320a"*\035 \321\260'
|
||||
'\267\260\235\204E\307q3\310J\010`p#\001\000'
|
||||
';',
|
||||
|
||||
"/icons/small/dir.gif" :
|
||||
'GIF89a\020\000\020\000\242\377\000\377\336\255'
|
||||
'\377\334\256\300\300\300\270\240}WK;+%\035\000'
|
||||
'\000\000\000\000\000!\371\004\001\000\000\002\000,\000\000'
|
||||
'\000\000\020\000\020\000\000\003I(b\246\376\316\2241'
|
||||
'\230\262\320\2002\253\031\001v\001$GQA\025\221'
|
||||
'\254y6#\333v\360"\337_\255\335dE\350\274'
|
||||
'\236\341\267\012\372t\204\001\357\370`$e\314\314P'
|
||||
'\0118\026 \227\251\365\212]\014\277\234n\204\021\026'
|
||||
'$\000\000;',
|
||||
|
||||
"/icons/small/text.gif" :
|
||||
'GIF89a\020\000\020\000\242\377\000\377\377\377'
|
||||
'999kkk\214\214\214\300\300\300\316\316\316\347'
|
||||
'\347\347\000\000\000!\371\004\001\000\000\004\000,\000\000'
|
||||
'\000\000\020\000\020\000\000\003EH\272\334\276\343\025@'
|
||||
"k1\261\315J'\326\\W\024D\246LA\232\002"
|
||||
'j\300xFlP\212\360\262me\330\013,\307\024'
|
||||
'\336\011\327Z\011\011\276\337\354\210*\032oJ\031\200'
|
||||
'I,No!\316\261$\350z\275\256E\002\000;',
|
||||
|
||||
}
|
||||
|
||||
|
||||
def serve_icon(pathname, fp):
|
||||
if _ap_icons.has_key(pathname):
|
||||
fp.write(_ap_icons[pathname])
|
||||
else:
|
||||
raise OSError # icon not found
|
||||
|
576
lib/blame.py
Normal file
576
lib/blame.py
Normal file
@@ -0,0 +1,576 @@
|
||||
#!/usr/local/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# blame.py: Annotate each line of a CVS file with its author,
|
||||
# revision #, date, etc.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This software is being maintained as part of the ViewCVS project.
|
||||
# Information is available at:
|
||||
# http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
|
||||
# developed by Steve Lamm for Netscape Communications Corporation. More
|
||||
# information about Bonsai can be found at
|
||||
# http://www.mozilla.org/bonsai.html
|
||||
#
|
||||
# cvsblame.pl, in turn, was based on Scott Furman's cvsblame script
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import string
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
import cgi
|
||||
import rcsparse
|
||||
|
||||
class CVSParser(rcsparse.Sink):
|
||||
# Precompiled regular expressions
|
||||
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
|
||||
last_branch = re.compile('(.*)\\.[0-9]+')
|
||||
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
|
||||
SECONDS_PER_DAY = 86400
|
||||
|
||||
def __init__(self):
|
||||
self.Reset()
|
||||
|
||||
def Reset(self):
|
||||
self.last_revision = {}
|
||||
self.prev_revision = {}
|
||||
self.revision_date = {}
|
||||
self.revision_author = {}
|
||||
self.revision_branches = {}
|
||||
self.next_delta = {}
|
||||
self.prev_delta = {}
|
||||
self.tag_revision = {}
|
||||
self.revision_symbolic_name = {}
|
||||
self.timestamp = {}
|
||||
self.revision_ctime = {}
|
||||
self.revision_age = {}
|
||||
self.revision_log = {}
|
||||
self.revision_deltatext = {}
|
||||
self.revision_map = []
|
||||
self.lines_added = {}
|
||||
self.lines_removed = {}
|
||||
|
||||
# Map a tag to a numerical revision number. The tag can be a symbolic
|
||||
# branch tag, a symbolic revision tag, or an ordinary numerical
|
||||
# revision number.
|
||||
def map_tag_to_revision(self, tag_or_revision):
|
||||
try:
|
||||
revision = self.tag_revision[tag_or_revision]
|
||||
match = self.is_branch.match(revision)
|
||||
if match:
|
||||
branch = match.group(1) + '.' + match.group(2)
|
||||
if self.last_revision.get(branch):
|
||||
return self.last_revision[branch]
|
||||
else:
|
||||
return match.group(1)
|
||||
else:
|
||||
return revision
|
||||
except:
|
||||
return ''
|
||||
|
||||
# Construct an ordered list of ancestor revisions to the given
|
||||
# revision, starting with the immediate ancestor and going back
|
||||
# to the primordial revision (1.1).
|
||||
#
|
||||
# Note: The generated path does not traverse the tree the same way
|
||||
# that the individual revision deltas do. In particular,
|
||||
# the path traverses the tree "backwards" on branches.
|
||||
def ancestor_revisions(self, revision):
|
||||
ancestors = []
|
||||
revision = self.prev_revision.get(revision)
|
||||
while revision:
|
||||
ancestors.append(revision)
|
||||
revision = self.prev_revision.get(revision)
|
||||
|
||||
return ancestors
|
||||
|
||||
# Split deltatext specified by rev to each line.
|
||||
def deltatext_split(self, rev):
|
||||
lines = string.split(self.revision_deltatext[rev], '\n')
|
||||
if lines[-1] == '':
|
||||
del lines[-1]
|
||||
return lines
|
||||
|
||||
# Extract the given revision from the digested RCS file.
|
||||
# (Essentially the equivalent of cvs up -rXXX)
|
||||
def extract_revision(self, revision):
|
||||
path = []
|
||||
add_lines_remaining = 0
|
||||
start_line = 0
|
||||
count = 0
|
||||
while revision:
|
||||
path.append(revision)
|
||||
revision = self.prev_delta.get(revision)
|
||||
path.reverse()
|
||||
path = path[1:] # Get rid of head revision
|
||||
|
||||
text = self.deltatext_split(self.head_revision)
|
||||
|
||||
# Iterate, applying deltas to previous revision
|
||||
for revision in path:
|
||||
adjust = 0
|
||||
diffs = self.deltatext_split(revision)
|
||||
self.lines_added[revision] = 0
|
||||
self.lines_removed[revision] = 0
|
||||
lines_added_now = 0
|
||||
lines_removed_now = 0
|
||||
|
||||
for command in diffs:
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if add_lines_remaining > 0:
|
||||
# Insertion lines from a prior "a" command
|
||||
text.insert(start_line + adjust, command)
|
||||
add_lines_remaining = add_lines_remaining - 1
|
||||
adjust = adjust + 1
|
||||
elif dmatch:
|
||||
# "d" - Delete command
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
begin = start_line + adjust - 1
|
||||
del text[begin:begin + count]
|
||||
adjust = adjust - count
|
||||
lines_removed_now = lines_removed_now + count
|
||||
elif amatch:
|
||||
# "a" - Add command
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
add_lines_remaining = count
|
||||
lines_added_now = lines_added_now + count
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
self.lines_added[revision] = self.lines_added[revision] + lines_added_now
|
||||
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
|
||||
return text
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head_revision = revision
|
||||
|
||||
def set_principal_branch(self, branch_name):
|
||||
self.principal_branch = branch_name
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
# Create an associate array that maps from tag name to
|
||||
# revision number and vice-versa.
|
||||
self.tag_revision[name] = revision
|
||||
|
||||
### actually, this is a bit bogus... a rev can have multiple names
|
||||
self.revision_symbolic_name[revision] = name
|
||||
|
||||
def set_comment(self, comment):
|
||||
self.file_description = comment
|
||||
|
||||
def set_description(self, description):
|
||||
self.rcs_file_description = description
|
||||
|
||||
# Construct dicts that represent the topology of the RCS tree
|
||||
# and other arrays that contain info about individual revisions.
|
||||
#
|
||||
# The following dicts are created, keyed by revision number:
|
||||
# self.revision_date -- e.g. "96.02.23.00.21.52"
|
||||
# self.timestamp -- seconds since 12:00 AM, Jan 1, 1970 GMT
|
||||
# self.revision_author -- e.g. "tom"
|
||||
# self.revision_branches -- descendant branch revisions, separated by spaces,
|
||||
# e.g. "1.21.4.1 1.21.2.6.1"
|
||||
# self.prev_revision -- revision number of previous *ancestor* in RCS tree.
|
||||
# Traversal of this array occurs in the direction
|
||||
# of the primordial (1.1) revision.
|
||||
# self.prev_delta -- revision number of previous revision which forms
|
||||
# the basis for the edit commands in this revision.
|
||||
# This causes the tree to be traversed towards the
|
||||
# trunk when on a branch, and towards the latest trunk
|
||||
# revision when on the trunk.
|
||||
# self.next_delta -- revision number of next "delta". Inverts prev_delta.
|
||||
#
|
||||
# Also creates self.last_revision, keyed by a branch revision number, which
|
||||
# indicates the latest revision on a given branch,
|
||||
# e.g. self.last_revision{"1.2.8"} == 1.2.8.5
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
self.tag_revision[revision] = revision
|
||||
branch = self.last_branch.match(revision).group(1)
|
||||
self.last_revision[branch] = revision
|
||||
|
||||
#self.revision_date[revision] = date
|
||||
self.timestamp[revision] = timestamp
|
||||
|
||||
# Pretty print the date string
|
||||
ltime = time.localtime(self.timestamp[revision])
|
||||
formatted_date = time.strftime("%d %b %Y %H:%M", ltime)
|
||||
self.revision_ctime[revision] = formatted_date
|
||||
|
||||
# Save age
|
||||
self.revision_age[revision] = ((time.time() - self.timestamp[revision])
|
||||
/ self.SECONDS_PER_DAY)
|
||||
|
||||
# save author
|
||||
self.revision_author[revision] = author
|
||||
|
||||
# ignore the state
|
||||
|
||||
# process the branch information
|
||||
branch_text = ''
|
||||
for branch in branches:
|
||||
self.prev_revision[branch] = revision
|
||||
self.next_delta[revision] = branch
|
||||
self.prev_delta[branch] = revision
|
||||
branch_text = branch_text + branch + ''
|
||||
self.revision_branches[revision] = branch_text
|
||||
|
||||
# process the "next revision" information
|
||||
if next:
|
||||
self.next_delta[revision] = next
|
||||
self.prev_delta[next] = revision
|
||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
||||
if is_trunk_revision:
|
||||
self.prev_revision[revision] = next
|
||||
else:
|
||||
self.prev_revision[next] = revision
|
||||
|
||||
# Construct associative arrays containing info about individual revisions.
|
||||
#
|
||||
# The following associative arrays are created, keyed by revision number:
|
||||
# revision_log -- log message
|
||||
# revision_deltatext -- Either the complete text of the revision,
|
||||
# in the case of the head revision, or the
|
||||
# encoded delta between this revision and another.
|
||||
# The delta is either with respect to the successor
|
||||
# revision if this revision is on the trunk or
|
||||
# relative to its immediate predecessor if this
|
||||
# revision is on a branch.
|
||||
def set_revision_info(self, revision, log, text):
|
||||
self.revision_log[revision] = log
|
||||
self.revision_deltatext[revision] = text
|
||||
|
||||
def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
|
||||
# Args in: opt_rev - requested revision
|
||||
# opt_m - time since modified
|
||||
# Args out: revision_map
|
||||
# timestamp
|
||||
# revision_deltatext
|
||||
|
||||
# CheckHidden(rcs_pathname);
|
||||
try:
|
||||
rcsfile = open(rcs_pathname, 'r')
|
||||
except:
|
||||
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
|
||||
'but the RCS file is inaccessible.') % rcs_pathname
|
||||
|
||||
rcsparse.Parser().parse(rcsfile, self)
|
||||
rcsfile.close()
|
||||
|
||||
if opt_rev in [None, '', 'HEAD']:
|
||||
# Explicitly specified topmost revision in tree
|
||||
revision = self.head_revision
|
||||
else:
|
||||
# Symbolic tag or specific revision number specified.
|
||||
revision = self.map_tag_to_revision(opt_rev)
|
||||
if revision == '':
|
||||
raise RuntimeError, 'error: -r: No such revision: ' + opt_rev
|
||||
|
||||
# The primordial revision is not always 1.1! Go find it.
|
||||
primordial = revision
|
||||
while self.prev_revision.get(primordial):
|
||||
primordial = self.prev_revision[primordial]
|
||||
|
||||
# Don't display file at all, if -m option is specified and no
|
||||
# changes have been made in the specified file.
|
||||
if opt_m_timestamp and self.timestamp[revision] < opt_m_timestamp:
|
||||
return ''
|
||||
|
||||
# Figure out how many lines were in the primordial, i.e. version 1.1,
|
||||
# check-in by moving backward in time from the head revision to the
|
||||
# first revision.
|
||||
line_count = 0
|
||||
if self.revision_deltatext.get(self.head_revision):
|
||||
tmp_array = self.deltatext_split(self.head_revision)
|
||||
line_count = len(tmp_array)
|
||||
|
||||
skip = 0
|
||||
|
||||
rev = self.prev_revision.get(self.head_revision)
|
||||
while rev:
|
||||
diffs = self.deltatext_split(rev)
|
||||
for command in diffs:
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if skip > 0:
|
||||
# Skip insertion lines from a prior "a" command
|
||||
skip = skip - 1
|
||||
elif dmatch:
|
||||
# "d" - Delete command
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
line_count = line_count - count
|
||||
elif amatch:
|
||||
# "a" - Add command
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
skip = count;
|
||||
line_count = line_count + count
|
||||
else:
|
||||
raise RuntimeError, 'error: illegal RCS file'
|
||||
|
||||
rev = self.prev_revision.get(rev)
|
||||
|
||||
# Now, play the delta edit commands *backwards* from the primordial
|
||||
# revision forward, but rather than applying the deltas to the text of
|
||||
# each revision, apply the changes to an array of revision numbers.
|
||||
# This creates a "revision map" -- an array where each element
|
||||
# represents a line of text in the given revision but contains only
|
||||
# the revision number in which the line was introduced rather than
|
||||
# the line text itself.
|
||||
#
|
||||
# Note: These are backward deltas for revisions on the trunk and
|
||||
# forward deltas for branch revisions.
|
||||
|
||||
# Create initial revision map for primordial version.
|
||||
self.revision_map = [primordial] * line_count
|
||||
|
||||
ancestors = [revision, ] + self.ancestor_revisions(revision)
|
||||
ancestors = ancestors[:-1] # Remove "1.1"
|
||||
last_revision = primordial
|
||||
ancestors.reverse()
|
||||
for revision in ancestors:
|
||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
||||
|
||||
if is_trunk_revision:
|
||||
diffs = self.deltatext_split(last_revision)
|
||||
|
||||
# Revisions on the trunk specify deltas that transform a
|
||||
# revision into an earlier revision, so invert the translation
|
||||
# of the 'diff' commands.
|
||||
for command in diffs:
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if dmatch:
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
temp = []
|
||||
while count > 0:
|
||||
temp.append(revision)
|
||||
count = count - 1
|
||||
self.revision_map = (self.revision_map[:start_line - 1] +
|
||||
temp + self.revision_map[start_line - 1:])
|
||||
elif amatch:
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
del self.revision_map[start_line:start_line + count]
|
||||
skip = count
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
else:
|
||||
# Revisions on a branch are arranged backwards from those on
|
||||
# the trunk. They specify deltas that transform a revision
|
||||
# into a later revision.
|
||||
adjust = 0
|
||||
diffs = self.deltatext_split(revision)
|
||||
for command in diffs:
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
else:
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if dmatch:
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
del self.revision_map[start_line + adjust - 1:start_line + adjust - 1 + count]
|
||||
adjust = adjust - count
|
||||
elif amatch:
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
skip = count
|
||||
temp = []
|
||||
while count > 0:
|
||||
temp.append(revision)
|
||||
count = count - 1
|
||||
self.revision_map = (self.revision_map[:start_line + adjust] +
|
||||
temp + self.revision_map[start_line + adjust:])
|
||||
adjust = adjust + skip
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
last_revision = revision
|
||||
|
||||
return revision
|
||||
|
||||
|
||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
||||
re_filename = re.compile('(.*[\\\\/])?(.+)')
|
||||
|
||||
def link_includes(text, root, rcs_path, sticky = None):
|
||||
match = re_includes.match(text)
|
||||
if match:
|
||||
incfile = match.group(3)
|
||||
for rel_path in ('', 'Attic', '..'):
|
||||
trial_root = os.path.join(rcs_path, rel_path)
|
||||
file = os.path.join(root, trial_root)
|
||||
file = os.path.normpath(os.path.join(file, incfile + ',v'))
|
||||
if os.access(file, os.F_OK):
|
||||
url = os.path.join(rel_path, incfile)
|
||||
if sticky:
|
||||
url = url + '?' + sticky
|
||||
return '#%sinclude%s"<a href="%s">%s</a>"' % \
|
||||
(match.group(1), match.group(2), url, incfile)
|
||||
return text
|
||||
|
||||
def make_html(root, rcs_path, opt_rev = None, sticky = None):
|
||||
filename = os.path.join(root, rcs_path)
|
||||
parser = CVSParser()
|
||||
revision = parser.parse_cvs_file(filename, opt_rev)
|
||||
count = len(parser.revision_map)
|
||||
text = parser.extract_revision(revision)
|
||||
if len(text) != count:
|
||||
raise RuntimeError, 'Internal consistency error'
|
||||
|
||||
match = re_filename.match(rcs_path)
|
||||
if not match:
|
||||
raise RuntimeError, 'Unable to parse filename'
|
||||
file_head = match.group(1)
|
||||
file_tail = match.group(2)
|
||||
|
||||
open_table_tag = '<table border=0 cellpadding=0 cellspacing=0 width="100%">'
|
||||
startOfRow = '<tr><td colspan=3%s><pre>'
|
||||
endOfRow = '</td></tr>'
|
||||
|
||||
print open_table_tag + (startOfRow % '')
|
||||
|
||||
if count == 0:
|
||||
count = 1
|
||||
|
||||
line_num_width = int(math.log10(count)) + 1
|
||||
revision_width = 3
|
||||
author_width = 5
|
||||
line = 0
|
||||
usedlog = {}
|
||||
usedlog[revision] = 1
|
||||
old_revision = 0
|
||||
row_color = ''
|
||||
lines_in_table = 0
|
||||
inMark = 0
|
||||
rev_count = 0
|
||||
|
||||
for revision in parser.revision_map:
|
||||
thisline = text[line]
|
||||
line = line + 1
|
||||
usedlog[revision] = 1
|
||||
line_in_table = lines_in_table + 1
|
||||
|
||||
# Escape HTML meta-characters
|
||||
thisline = cgi.escape(thisline)
|
||||
|
||||
# Add a link to traverse to included files
|
||||
if 1: # opt_includes
|
||||
thisline = link_includes(thisline, root, file_head, sticky)
|
||||
|
||||
output = ''
|
||||
|
||||
# Highlight lines
|
||||
#mark_cmd;
|
||||
#if (defined($mark_cmd = $mark_line{$line}) and mark_cmd != 'end':
|
||||
# output = output + endOfRow + '<tr><td bgcolor=LIGHTGREEN width="100%"><pre>'
|
||||
# inMark = 1
|
||||
|
||||
if old_revision != revision and line != 1:
|
||||
if row_color == '':
|
||||
row_color = ' bgcolor="#e7e7e7"'
|
||||
else:
|
||||
row_color = ''
|
||||
|
||||
if not inMark:
|
||||
if lines_in_table > 100:
|
||||
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
|
||||
lines_in_table = 0
|
||||
else:
|
||||
output = output + endOfRow + (startOfRow % row_color)
|
||||
|
||||
elif lines_in_table > 200 and not inMark:
|
||||
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
|
||||
lines_in_table = 0
|
||||
|
||||
output = output + "<a name=%d></a>" % (line, )
|
||||
if 1: # opt_line_nums
|
||||
output = output + ('%%%dd' % (line_num_width, )) % (line, )
|
||||
|
||||
if old_revision != revision or rev_count > 20:
|
||||
revision_width = max(revision_width, len(revision))
|
||||
|
||||
if parser.prev_revision.get(revision):
|
||||
fname = file_tail[:-2] # strip the ",v"
|
||||
url = '%s.diff?r1=%s&r2=%s' % \
|
||||
(fname, parser.prev_revision[revision], revision)
|
||||
if sticky:
|
||||
url = url + '&' + sticky
|
||||
output = output + ' <a href="%s"' % (url, )
|
||||
if 0: # use_layers
|
||||
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
|
||||
parser.prev_revision[revision], revision)
|
||||
output = output + ">"
|
||||
else:
|
||||
output = output + " "
|
||||
parser.prev_revision[revision] = ''
|
||||
|
||||
author = parser.revision_author[revision]
|
||||
# $author =~ s/%.*$//;
|
||||
author_width = max(author_width, len(author))
|
||||
output = output + ('%%-%ds ' % (author_width, )) % (author, )
|
||||
output = output + revision
|
||||
if parser.prev_revision.get(revision):
|
||||
output = output + '</a>'
|
||||
output = output + (' ' * (revision_width - len(revision) + 1))
|
||||
|
||||
old_revision = revision
|
||||
rev_count = 0
|
||||
else:
|
||||
output = output + ' ' + (' ' * (author_width + revision_width))
|
||||
rev_count = rev_count + 1
|
||||
|
||||
output = output + thisline
|
||||
|
||||
# Close the highlighted section
|
||||
#if (defined $mark_cmd and mark_cmd != 'begin'):
|
||||
# chop($output)
|
||||
# output = output + endOfRow + (startOfRow % row_color)
|
||||
# inMark = 0
|
||||
|
||||
print output
|
||||
print endOfRow + '</table>'
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print 'USAGE: %s cvsroot rcs-file' % sys.argv[0]
|
||||
sys.exit(1)
|
||||
make_html(sys.argv[1], sys.argv[2])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
68
lib/compat.py
Normal file
68
lib/compat.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# compat.py: compatibility functions for operation across Python 1.5.x
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import urllib
|
||||
import string
|
||||
import time
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
#
|
||||
# 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):
|
||||
'Parse a CVS-style date/time value.'
|
||||
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):
|
||||
'Parse a CVS-style date/time value.'
|
||||
matches = _re_rev_date.match(timestr).groups()
|
||||
return tuple(map(int, matches)) + (0, 1, 0)
|
||||
|
||||
#
|
||||
# os.makedirs() is new to Python 1.5.2
|
||||
#
|
||||
try:
|
||||
makedirs = os.makedirs
|
||||
except AttributeError:
|
||||
def makedirs(path, mode=0777):
|
||||
head, tail = os.path.split(path)
|
||||
if head and tail and not os.path.exists(head):
|
||||
makedirs(head, mode)
|
||||
os.mkdir(path, mode)
|
226
lib/config.py
Normal file
226
lib/config.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# config.py: configuration utilities
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import ConfigParser
|
||||
import fnmatch
|
||||
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# CONFIGURATION
|
||||
#
|
||||
# There are three forms of configuration:
|
||||
#
|
||||
# 1) edit the viewcvs.conf created by the viewcvs-install(er)
|
||||
# 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', 'options', 'cvsdb', 'templates')
|
||||
_force_multi_value = ('cvs_roots', 'forbidden', 'disable_enscript_lang',
|
||||
'languages', 'kv_files')
|
||||
|
||||
def __init__(self):
|
||||
for section in self._sections:
|
||||
setattr(self, section, _sub_config())
|
||||
|
||||
def load_config(self, fname, vhost=None):
|
||||
this_dir = os.path.dirname(sys.argv[0])
|
||||
pathname = os.path.join(this_dir, fname)
|
||||
self.base = os.path.dirname(pathname)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read(pathname)
|
||||
|
||||
for section in self._sections:
|
||||
if parser.has_section(section):
|
||||
self._process_section(parser, section, section)
|
||||
|
||||
if vhost and parser.has_section('vhosts'):
|
||||
self._process_vhost(parser, vhost)
|
||||
|
||||
def load_kv_files(self, language):
|
||||
kv = _sub_config()
|
||||
|
||||
for fname in self.general.kv_files:
|
||||
if fname[0] == '[':
|
||||
idx = string.index(fname, ']')
|
||||
parts = string.split(fname[1:idx], '.')
|
||||
fname = string.strip(fname[idx+1:])
|
||||
else:
|
||||
parts = [ ]
|
||||
fname = string.replace(fname, '%lang%', language)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read(os.path.join(self.base, fname))
|
||||
for section in parser.sections():
|
||||
for option in parser.options(section):
|
||||
full_name = parts + [section]
|
||||
ob = kv
|
||||
for name in full_name:
|
||||
try:
|
||||
ob = getattr(ob, name)
|
||||
except AttributeError:
|
||||
c = _sub_config()
|
||||
setattr(ob, name, c)
|
||||
ob = c
|
||||
setattr(ob, option, parser.get(section, option))
|
||||
|
||||
return kv
|
||||
|
||||
def _process_section(self, parser, section, subcfg_name):
|
||||
sc = getattr(self, subcfg_name)
|
||||
|
||||
for opt in parser.options(section):
|
||||
value = parser.get(section, opt)
|
||||
if opt in self._force_multi_value:
|
||||
value = map(string.strip, filter(None, 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 _process_vhost(self, parser, vhost):
|
||||
canon_vhost = self._find_canon_vhost(parser, vhost)
|
||||
if not canon_vhost:
|
||||
# none of the vhost sections matched
|
||||
return
|
||||
|
||||
cv = canon_vhost + '-'
|
||||
lcv = len(cv)
|
||||
for section in parser.sections():
|
||||
if section[:lcv] == cv:
|
||||
self._process_section(parser, section, section[lcv:])
|
||||
|
||||
def _find_canon_vhost(self, parser, vhost):
|
||||
vhost = string.lower(vhost)
|
||||
|
||||
for canon_vhost in parser.options('vhosts'):
|
||||
value = parser.get('vhosts', canon_vhost)
|
||||
patterns = map(string.lower, map(string.strip,
|
||||
filter(None, string.split(value, ','))))
|
||||
for pat in patterns:
|
||||
if fnmatch.fnmatchcase(vhost, pat):
|
||||
return canon_vhost
|
||||
|
||||
return None
|
||||
|
||||
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:user@insert.your.domain.here">No CVS admin address has been configured</a>'
|
||||
self.general.main_title = 'CVS Repository'
|
||||
self.general.forbidden = ()
|
||||
self.general.kv_files = [ ]
|
||||
self.general.languages = ['en-us']
|
||||
|
||||
self.templates.directory = 'templates/directory.ezt'
|
||||
self.templates.log = 'templates/log.ezt'
|
||||
self.templates.query = 'templates/query.ezt'
|
||||
self.templates.footer = 'templates/footer.ezt'
|
||||
self.templates.diff = 'templates/diff.ezt'
|
||||
self.templates.graph = 'templates/graph.ezt'
|
||||
self.templates.annotate = 'templates/annotate.ezt'
|
||||
self.templates.markup = 'templates/markup.ezt'
|
||||
|
||||
self.cvsdb.enabled = 0
|
||||
self.cvsdb.host = ''
|
||||
self.cvsdb.database_name = ''
|
||||
self.cvsdb.user = ''
|
||||
self.cvsdb.passwd = ''
|
||||
self.cvsdb.readonly_user = ''
|
||||
self.cvsdb.readonly_passwd = ''
|
||||
self.cvsdb.row_limit = 1000
|
||||
|
||||
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.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 = 1
|
||||
self.options.allow_markup = 1
|
||||
self.options.allow_compress = 1
|
||||
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.py2html_path = '.'
|
||||
self.options.short_log_len = 80
|
||||
self.options.diff_font_face = 'Helvetica,Arial'
|
||||
self.options.diff_font_size = -1
|
||||
self.options.use_enscript = 0
|
||||
self.options.enscript_path = ''
|
||||
self.options.disable_enscript_lang = ()
|
||||
self.options.allow_tar = 0
|
||||
self.options.use_cvsgraph = 0
|
||||
self.options.cvsgraph_path = ''
|
||||
self.options.cvsgraph_conf = "<VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf"
|
||||
self.options.use_re_search = 0
|
||||
|
||||
def is_forbidden(self, module):
|
||||
if not module:
|
||||
return 0
|
||||
default = 0
|
||||
for pat in self.general.forbidden:
|
||||
if pat[0] == '!':
|
||||
default = 1
|
||||
if fnmatch.fnmatchcase(module, pat[1:]):
|
||||
return 0
|
||||
elif fnmatch.fnmatchcase(module, pat):
|
||||
return 1
|
||||
return default
|
||||
|
||||
class _sub_config:
|
||||
pass
|
||||
|
||||
if not hasattr(sys, 'hexversion'):
|
||||
# Python 1.5 or 1.5.1. fix the syntax for ConfigParser options.
|
||||
import regex
|
||||
ConfigParser.option_cre = regex.compile('^\([-A-Za-z0-9._]+\)\(:\|['
|
||||
+ string.whitespace
|
||||
+ ']*=\)\(.*\)$')
|
753
lib/cvsdb.py
Normal file
753
lib/cvsdb.py
Normal file
@@ -0,0 +1,753 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# These values will be set during the installation process. During
|
||||
# development, they will remain None.
|
||||
#
|
||||
|
||||
CONF_PATHNAME = None
|
||||
|
||||
#########################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import time
|
||||
|
||||
import config
|
||||
import dbi
|
||||
import rlog
|
||||
|
||||
|
||||
## load configuration file, the data is used globally here
|
||||
if CONF_PATHNAME:
|
||||
_cfg_pathname = CONF_PATHNAME
|
||||
else:
|
||||
# developer assistance: running from a CVS working copy
|
||||
_cfg_pathname = os.path.join(os.path.dirname(__file__), os.pardir, 'cgi',
|
||||
'viewcvs.conf')
|
||||
cfg = config.Config()
|
||||
cfg.set_defaults()
|
||||
cfg.load_config(_cfg_pathname)
|
||||
|
||||
## error
|
||||
error = "cvsdb error"
|
||||
|
||||
## cached (active) database connections
|
||||
gCheckinDatabase = None
|
||||
gCheckinDatabaseReadOnly = None
|
||||
|
||||
|
||||
## 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._host = host
|
||||
self._user = user
|
||||
self._passwd = passwd
|
||||
self._database = database
|
||||
|
||||
## database lookup caches
|
||||
self._get_cache = {}
|
||||
self._get_id_cache = {}
|
||||
self._desc_id_cache = {}
|
||||
|
||||
def Connect(self):
|
||||
self.db = dbi.connect(
|
||||
self._host, self._user, self._passwd, self._database)
|
||||
|
||||
def sql_get_id(self, table, column, value, auto_set):
|
||||
sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
|
||||
sql_args = (value, )
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
(id, ) = cursor.fetchone()
|
||||
except TypeError:
|
||||
if not auto_set:
|
||||
return None
|
||||
else:
|
||||
return str(int(id))
|
||||
|
||||
## insert the new identifier
|
||||
sql = "INSERT INTO %s(%s) VALUES(%%s)" % (table, column)
|
||||
sql_args = (value, )
|
||||
cursor.execute(sql, sql_args)
|
||||
|
||||
return self.sql_get_id(table, column, value, 0)
|
||||
|
||||
def get_id(self, table, column, value, auto_set):
|
||||
## attempt to retrieve from cache
|
||||
try:
|
||||
return self._get_id_cache[table][column][value]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
id = self.sql_get_id(table, column, value, auto_set)
|
||||
if id == None:
|
||||
return None
|
||||
|
||||
## add to cache
|
||||
try:
|
||||
temp = self._get_id_cache[table]
|
||||
except KeyError:
|
||||
temp = self._get_id_cache[table] = {}
|
||||
|
||||
try:
|
||||
temp2 = temp[column]
|
||||
except KeyError:
|
||||
temp2 = temp[column] = {}
|
||||
|
||||
temp2[value] = id
|
||||
return id
|
||||
|
||||
def sql_get(self, table, column, id):
|
||||
sql = "SELECT %s FROM %s WHERE id=%%s" % (column, table)
|
||||
sql_args = (id, )
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
(value, ) = cursor.fetchone()
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
return value
|
||||
|
||||
def get(self, table, column, id):
|
||||
## attempt to retrieve from cache
|
||||
try:
|
||||
return self._get_cache[table][column][id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
value = self.sql_get(table, column, id)
|
||||
if value == None:
|
||||
return None
|
||||
|
||||
## add to cache
|
||||
try:
|
||||
temp = self._get_cache[table]
|
||||
except KeyError:
|
||||
temp = self._get_cache[table] = {}
|
||||
|
||||
try:
|
||||
temp2 = temp[column]
|
||||
except KeyError:
|
||||
temp2 = temp[column] = {}
|
||||
|
||||
temp2[id] = value
|
||||
return value
|
||||
|
||||
def get_list(self, table, field_index):
|
||||
sql = "SELECT * FROM %s" % (table)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql)
|
||||
|
||||
list = []
|
||||
while 1:
|
||||
row = cursor.fetchone()
|
||||
if row == None:
|
||||
break
|
||||
list.append(row[field_index])
|
||||
|
||||
return list
|
||||
|
||||
def GetBranchID(self, branch, auto_set = 1):
|
||||
return self.get_id("branches", "branch", branch, auto_set)
|
||||
|
||||
def GetBranch(self, id):
|
||||
return self.get("branches", "branch", id)
|
||||
|
||||
def GetDirectoryID(self, dir, auto_set = 1):
|
||||
return self.get_id("dirs", "dir", dir, auto_set)
|
||||
|
||||
def GetDirectory(self, id):
|
||||
return self.get("dirs", "dir", id)
|
||||
|
||||
def GetFileID(self, file, auto_set = 1):
|
||||
return self.get_id("files", "file", file, auto_set)
|
||||
|
||||
def GetFile(self, id):
|
||||
return self.get("files", "file", id)
|
||||
|
||||
def GetAuthorID(self, author, auto_set = 1):
|
||||
return self.get_id("people", "who", author, auto_set)
|
||||
|
||||
def GetAuthor(self, id):
|
||||
return self.get("people", "who", id)
|
||||
|
||||
def GetRepositoryID(self, repository, auto_set = 1):
|
||||
return self.get_id("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)
|
||||
|
||||
sql = "SELECT id FROM descs WHERE hash=%s AND description=%s"
|
||||
sql_args = (hash, description)
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
(id, ) = cursor.fetchone()
|
||||
except TypeError:
|
||||
if not auto_set:
|
||||
return None
|
||||
else:
|
||||
return str(int(id))
|
||||
|
||||
sql = "INSERT INTO descs (hash,description) values (%s,%s)"
|
||||
sql_args = (hash, description)
|
||||
cursor.execute(sql, sql_args)
|
||||
|
||||
return self.GetDescriptionID(description, 0)
|
||||
|
||||
def GetDescriptionID(self, description, auto_set = 1):
|
||||
## attempt to retrieve from cache
|
||||
hash = len(description)
|
||||
try:
|
||||
return self._desc_id_cache[hash][description]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
id = self.SQLGetDescriptionID(description, auto_set)
|
||||
if id == None:
|
||||
return None
|
||||
|
||||
## add to cache
|
||||
try:
|
||||
temp = self._desc_id_cache[hash]
|
||||
except KeyError:
|
||||
temp = self._desc_id_cache[hash] = {}
|
||||
|
||||
temp[description] = id
|
||||
return id
|
||||
|
||||
def GetDescription(self, id):
|
||||
return self.get("descs", "description", id)
|
||||
|
||||
def GetRepositoryList(self):
|
||||
return self.get_list("repositories", 1)
|
||||
|
||||
def GetBranchList(self):
|
||||
return self.get_list("branches", 1)
|
||||
|
||||
def GetAuthorList(self):
|
||||
return self.get_list("people", 1)
|
||||
|
||||
def AddCommitList(self, commit_list):
|
||||
for commit in commit_list:
|
||||
self.AddCommit(commit)
|
||||
|
||||
def AddCommit(self, commit):
|
||||
## 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())
|
||||
ci_when = dbi.Timestamp(
|
||||
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
|
||||
|
||||
ci_type = commit.GetTypeString()
|
||||
who_id = self.GetAuthorID(commit.GetAuthor())
|
||||
repository_id = self.GetRepositoryID(commit.GetRepository())
|
||||
directory_id = self.GetDirectoryID(commit.GetDirectory())
|
||||
file_id = self.GetFileID(commit.GetFile())
|
||||
revision = commit.GetRevision()
|
||||
sticky_tag = "NULL"
|
||||
branch_id = self.GetBranchID(commit.GetBranch())
|
||||
plus_count = commit.GetPlusCount()
|
||||
minus_count = commit.GetMinusCount()
|
||||
description_id = 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)"
|
||||
sql_args = (ci_type, ci_when, who_id, repository_id,
|
||||
directory_id, file_id, revision, sticky_tag, branch_id,
|
||||
plus_count, minus_count, description_id)
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
|
||||
def SQLQueryListString(self, sqlString, query_entry_list):
|
||||
sqlList = []
|
||||
|
||||
for query_entry in query_entry_list:
|
||||
## figure out the correct match type
|
||||
if query_entry.match == "exact":
|
||||
match = "="
|
||||
elif query_entry.match == "like":
|
||||
match = " LIKE "
|
||||
elif query_entry.match == "regex":
|
||||
match = " REGEXP "
|
||||
|
||||
sqlList.append(sqlString % (match, query_entry.data))
|
||||
|
||||
return "(%s)" % (string.join(sqlList, " OR "))
|
||||
|
||||
def CreateSQLQueryString(self, query):
|
||||
tableList = ["checkins"]
|
||||
condList = []
|
||||
|
||||
## XXX: this is to exclude .ver files -- RN specific hack --JMP
|
||||
tableList.append("files")
|
||||
temp = "(checkins.fileid=files.id AND files.file NOT LIKE \"%.ver\")"
|
||||
condList.append(temp)
|
||||
## XXX
|
||||
|
||||
if len(query.repository_list):
|
||||
tableList.append("repositories")
|
||||
|
||||
sql = "(checkins.repositoryid=repositories.id AND "\
|
||||
"repositories.repository%s\"%s\")"
|
||||
temp = self.SQLQueryListString(sql, query.repository_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.branch_list):
|
||||
tableList.append("branches")
|
||||
|
||||
sql = "(checkins.branchid=branches.id AND "\
|
||||
"branches.branch%s\"%s\")"
|
||||
temp = self.SQLQueryListString(sql, query.branch_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.directory_list):
|
||||
tableList.append("dirs")
|
||||
|
||||
sql = "(checkins.dirid=dirs.id AND dirs.dir%s\"%s\")"
|
||||
temp = self.SQLQueryListString(sql, query.directory_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.file_list):
|
||||
tableList.append("files")
|
||||
|
||||
sql = "(checkins.fileid=files.id AND files.file%s\"%s\")"
|
||||
temp = self.SQLQueryListString(sql, query.file_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.author_list):
|
||||
tableList.append("people")
|
||||
|
||||
sql = "(checkins.whoid=people.id AND people.who%s\"%s\")"
|
||||
temp = self.SQLQueryListString(sql, query.author_list)
|
||||
condList.append(temp)
|
||||
|
||||
if query.from_date:
|
||||
temp = "(checkins.ci_when>=\"%s\")" % (str(query.from_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.to_date:
|
||||
temp = "(checkins.ci_when<=\"%s\")" % (str(query.to_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.sort == "date":
|
||||
order_by = "ORDER BY checkins.ci_when DESC"
|
||||
elif query.sort == "author":
|
||||
order_by = "ORDER BY checkins.whoid"
|
||||
elif query.sort == "file":
|
||||
order_by = "ORDER BY checkins.fileid"
|
||||
|
||||
## exclude duplicates from the table list
|
||||
for table in tableList[:]:
|
||||
while tableList.count(table) > 1:
|
||||
tableList.remove(table)
|
||||
|
||||
tables = string.join(tableList, ",")
|
||||
conditions = string.join(condList, " AND ")
|
||||
|
||||
## limit the number of rows requested or we could really slam
|
||||
## a server with a large database
|
||||
limit = ""
|
||||
if cfg.cvsdb.row_limit:
|
||||
limit = "LIMIT %s" % (str(cfg.cvsdb.row_limit))
|
||||
|
||||
sql = "SELECT checkins.* FROM %s WHERE %s %s %s" % (
|
||||
tables, conditions, order_by, limit)
|
||||
|
||||
return sql
|
||||
|
||||
def RunQuery(self, query):
|
||||
sql = self.CreateSQLQueryString(query)
|
||||
cursor = self.db.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):
|
||||
repository_id = self.GetRepositoryID(commit.GetRepository(), 0)
|
||||
if repository_id == None:
|
||||
return None
|
||||
|
||||
dir_id = self.GetDirectoryID(commit.GetDirectory(), 0)
|
||||
if dir_id == None:
|
||||
return None
|
||||
|
||||
file_id = self.GetFileID(commit.GetFile(), 0)
|
||||
if file_id == None:
|
||||
return None
|
||||
|
||||
sql = "SELECT * FROM checkins WHERE "\
|
||||
" repositoryid=%s AND dirid=%s AND fileid=%s AND revision=%s"
|
||||
sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
(ci_type, ci_when, who_id, repository_id,
|
||||
dir_id, file_id, revision, sticky_tag, branch_id,
|
||||
plus_count, minus_count, description_id) = cursor.fetchone()
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
return commit
|
||||
|
||||
## 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 len(repository) and 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 len(dir) and dir[0] == os.sep:
|
||||
dir = dir[1:]
|
||||
while len(dir) and 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 len(file) and file[0] == os.sep:
|
||||
file = file[1:]
|
||||
while len(file) and 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'
|
||||
|
||||
## QueryEntry holds data on one match-type in the SQL database
|
||||
## match is: "exact", "like", or "regex"
|
||||
class QueryEntry:
|
||||
def __init__(self, data, match):
|
||||
self.data = data
|
||||
self.match = match
|
||||
|
||||
## CheckinDatabaseQueryData is a object which contains the search parameters
|
||||
## for a query to the CheckinDatabase
|
||||
class CheckinDatabaseQuery:
|
||||
def __init__(self):
|
||||
## sorting
|
||||
self.sort = "date"
|
||||
|
||||
## repository to query
|
||||
self.repository_list = []
|
||||
self.branch_list = []
|
||||
self.directory_list = []
|
||||
self.file_list = []
|
||||
self.author_list = []
|
||||
|
||||
## 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, match = "exact"):
|
||||
self.repository_list.append(QueryEntry(repository, match))
|
||||
|
||||
def SetBranch(self, branch, match = "exact"):
|
||||
self.branch_list.append(QueryEntry(branch, match))
|
||||
|
||||
def SetDirectory(self, directory, match = "exact"):
|
||||
self.directory_list.append(QueryEntry(directory, match))
|
||||
|
||||
def SetFile(self, file, match = "exact"):
|
||||
self.file_list.append(QueryEntry(file, match))
|
||||
|
||||
def SetAuthor(self, author, match = "exact"):
|
||||
self.author_list.append(QueryEntry(author, match))
|
||||
|
||||
def SetSortMethod(self, sort):
|
||||
self.sort = sort
|
||||
|
||||
def SetFromDateObject(self, ticks):
|
||||
self.from_date = dbi.TimestampFromTicks(ticks)
|
||||
|
||||
def SetToDateObject(self, ticks):
|
||||
self.to_date = dbi.TimestampFromTicks(ticks)
|
||||
|
||||
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 CheckinDatabase(host, user, passwd, database)
|
||||
|
||||
def CreateCommit():
|
||||
return Commit()
|
||||
|
||||
def CreateCheckinQuery():
|
||||
return CheckinDatabaseQuery()
|
||||
|
||||
def ConnectDatabaseReadOnly():
|
||||
global gCheckinDatabaseReadOnly
|
||||
|
||||
if gCheckinDatabaseReadOnly:
|
||||
return gCheckinDatabaseReadOnly
|
||||
|
||||
gCheckinDatabaseReadOnly = 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 = 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 = rlog.GetRLogData(cfg, 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
|
71
lib/dbi.py
Normal file
71
lib/dbi.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import sys
|
||||
import MySQLdb
|
||||
|
||||
|
||||
dbi_error = "dbi error"
|
||||
|
||||
|
||||
## make some checks in MySQLdb
|
||||
_no_datetime = """\
|
||||
ERROR: Your version of MySQLdb requires the mxDateTime module
|
||||
for the Timestamp() and TimestampFromTicks() methods.
|
||||
You will need to install mxDateTime to use the ViewCVS
|
||||
database.
|
||||
"""
|
||||
|
||||
if not hasattr(MySQLdb, "Timestamp") or \
|
||||
not hasattr(MySQLdb, "TimestampFromTicks"):
|
||||
sys.stderr.write(_no_datetime)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Cursor:
|
||||
def __init__(self, mysql_cursor):
|
||||
self.__cursor = mysql_cursor
|
||||
|
||||
def execute(self, *args):
|
||||
apply(self.__cursor.execute, args)
|
||||
|
||||
def fetchone(self):
|
||||
try:
|
||||
row = self.__cursor.fetchone()
|
||||
except IndexError:
|
||||
row = None
|
||||
|
||||
return row
|
||||
|
||||
|
||||
class Connection:
|
||||
def __init__(self, host, user, passwd, db):
|
||||
self.__mysql = MySQLdb.connect(
|
||||
host=host, user=user, passwd=passwd, db=db)
|
||||
|
||||
def cursor(self):
|
||||
return Cursor(self.__mysql.cursor())
|
||||
|
||||
|
||||
def Timestamp(year, month, date, hour, minute, second):
|
||||
return MySQLdb.Timestamp(year, month, date, hour, minute, second)
|
||||
|
||||
|
||||
def TimestampFromTicks(ticks):
|
||||
return MySQLdb.TimestampFromTicks(ticks)
|
||||
|
||||
|
||||
def connect(host, user, passwd, db):
|
||||
return Connection(host, user, passwd, db)
|
42
lib/debug.py
Normal file
42
lib/debug.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# Note: a t_start/t_end pair consumes about 0.00005 seconds on a P3/700.
|
||||
# the lambda form (when debugging is disabled) should be even faster.
|
||||
#
|
||||
|
||||
if 0:
|
||||
|
||||
import time
|
||||
|
||||
_timers = { }
|
||||
_times = { }
|
||||
|
||||
def t_start(which):
|
||||
_timers[which] = time.time()
|
||||
|
||||
def t_end(which):
|
||||
t = time.time() - _timers[which]
|
||||
if _times.has_key(which):
|
||||
_times[which] = _times[which] + t
|
||||
else:
|
||||
_times[which] = t
|
||||
|
||||
def dump():
|
||||
for name, value in _times.items():
|
||||
print '%s: %.6f<br>' % (name, value)
|
||||
|
||||
else:
|
||||
|
||||
t_start = t_end = dump = lambda *args: None
|
786
lib/difflib.py
Executable file
786
lib/difflib.py
Executable file
@@ -0,0 +1,786 @@
|
||||
#! /usr/bin/env python
|
||||
# Backported to Python 1.5.2 for the ViewCVS project by pf@artcom-gmbh.de
|
||||
# 24-Dec-2001, original version "stolen" from Python-2.1.1
|
||||
"""
|
||||
Module difflib -- helpers for computing deltas between objects.
|
||||
|
||||
Function get_close_matches(word, possibilities, n=3, cutoff=0.6):
|
||||
|
||||
Use SequenceMatcher to return list of the best "good enough" matches.
|
||||
|
||||
word is a sequence for which close matches are desired (typically a
|
||||
string).
|
||||
|
||||
possibilities is a list of sequences against which to match word
|
||||
(typically a list of strings).
|
||||
|
||||
Optional arg n (default 3) is the maximum number of close matches to
|
||||
return. n must be > 0.
|
||||
|
||||
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
|
||||
that don't score at least that similar to word are ignored.
|
||||
|
||||
The best (no more than n) matches among the possibilities are returned
|
||||
in a list, sorted by similarity score, most similar first.
|
||||
|
||||
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
|
||||
['apple', 'ape']
|
||||
>>> import keyword
|
||||
>>> get_close_matches("wheel", keyword.kwlist)
|
||||
['while']
|
||||
>>> get_close_matches("apple", keyword.kwlist)
|
||||
[]
|
||||
>>> get_close_matches("accept", keyword.kwlist)
|
||||
['except']
|
||||
|
||||
Class SequenceMatcher
|
||||
|
||||
SequenceMatcher is a flexible class for comparing pairs of sequences of any
|
||||
type, so long as the sequence elements are hashable. The basic algorithm
|
||||
predates, and is a little fancier than, an algorithm published in the late
|
||||
1980's by Ratcliff and Obershelp under the hyperbolic name "gestalt pattern
|
||||
matching". The basic idea is to find the longest contiguous matching
|
||||
subsequence that contains no "junk" elements (R-O doesn't address junk).
|
||||
The same idea is then applied recursively to the pieces of the sequences to
|
||||
the left and to the right of the matching subsequence. This does not yield
|
||||
minimal edit sequences, but does tend to yield matches that "look right"
|
||||
to people.
|
||||
|
||||
Example, comparing two strings, and considering blanks to be "junk":
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x == " ",
|
||||
... "private Thread currentThread;",
|
||||
... "private volatile Thread currentThread;")
|
||||
>>>
|
||||
|
||||
.ratio() returns a float in [0, 1], measuring the "similarity" of the
|
||||
sequences. As a rule of thumb, a .ratio() value over 0.6 means the
|
||||
sequences are close matches:
|
||||
|
||||
>>> print round(s.ratio(), 3)
|
||||
0.866
|
||||
>>>
|
||||
|
||||
If you're only interested in where the sequences match,
|
||||
.get_matching_blocks() is handy:
|
||||
|
||||
>>> for block in s.get_matching_blocks():
|
||||
... print "a[%d] and b[%d] match for %d elements" % block
|
||||
a[0] and b[0] match for 8 elements
|
||||
a[8] and b[17] match for 6 elements
|
||||
a[14] and b[23] match for 15 elements
|
||||
a[29] and b[38] match for 0 elements
|
||||
|
||||
Note that the last tuple returned by .get_matching_blocks() is always a
|
||||
dummy, (len(a), len(b), 0), and this is the only case in which the last
|
||||
tuple element (number of elements matched) is 0.
|
||||
|
||||
If you want to know how to change the first sequence into the second, use
|
||||
.get_opcodes():
|
||||
|
||||
>>> for opcode in s.get_opcodes():
|
||||
... print "%6s a[%d:%d] b[%d:%d]" % opcode
|
||||
equal a[0:8] b[0:8]
|
||||
insert a[8:8] b[8:17]
|
||||
equal a[8:14] b[17:23]
|
||||
equal a[14:29] b[23:38]
|
||||
|
||||
See Tools/scripts/ndiff.py for a fancy human-friendly file differencer,
|
||||
which uses SequenceMatcher both to view files as sequences of lines, and
|
||||
lines as sequences of characters.
|
||||
|
||||
See also function get_close_matches() in this module, which shows how
|
||||
simple code building on SequenceMatcher can be used to do useful work.
|
||||
|
||||
Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||
case. SequenceMatcher is quadratic time for the worst case and has
|
||||
expected-case behavior dependent in a complicated way on how many
|
||||
elements the sequences have in common; best case time is linear.
|
||||
|
||||
SequenceMatcher methods:
|
||||
|
||||
__init__(isjunk=None, a='', b='')
|
||||
Construct a SequenceMatcher.
|
||||
|
||||
Optional arg isjunk is None (the default), or a one-argument function
|
||||
that takes a sequence element and returns true iff the element is junk.
|
||||
None is equivalent to passing "lambda x: 0", i.e. no elements are
|
||||
considered to be junk. For example, pass
|
||||
lambda x: x in " \\t"
|
||||
if you're comparing lines as sequences of characters, and don't want to
|
||||
synch up on blanks or hard tabs.
|
||||
|
||||
Optional arg a is the first of two sequences to be compared. By
|
||||
default, an empty string. The elements of a must be hashable.
|
||||
|
||||
Optional arg b is the second of two sequences to be compared. By
|
||||
default, an empty string. The elements of b must be hashable.
|
||||
|
||||
set_seqs(a, b)
|
||||
Set the two sequences to be compared.
|
||||
|
||||
>>> s = SequenceMatcher()
|
||||
>>> s.set_seqs("abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
|
||||
set_seq1(a)
|
||||
Set the first sequence to be compared.
|
||||
|
||||
The second sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq1("bcde")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against many
|
||||
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
|
||||
each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq2().
|
||||
|
||||
set_seq2(b)
|
||||
Set the second sequence to be compared.
|
||||
|
||||
The first sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq2("abcd")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against many
|
||||
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
|
||||
each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq1().
|
||||
|
||||
find_longest_match(alo, ahi, blo, bhi)
|
||||
Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
|
||||
If isjunk is not defined:
|
||||
|
||||
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
alo <= i <= i+k <= ahi
|
||||
blo <= j <= j+k <= bhi
|
||||
and for all (i',j',k') meeting those conditions,
|
||||
k >= k'
|
||||
i <= i'
|
||||
and if i == i', j <= j'
|
||||
|
||||
In other words, of all maximal matching blocks, return one that starts
|
||||
earliest in a, and of all those maximal matching blocks that start
|
||||
earliest in a, return the one that starts earliest in b.
|
||||
|
||||
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(0, 4, 5)
|
||||
|
||||
If isjunk is defined, first the longest matching block is determined as
|
||||
above, but with the additional restriction that no junk element appears
|
||||
in the block. Then that block is extended as far as possible by
|
||||
matching (only) junk elements on both sides. So the resulting block
|
||||
never matches on junk except as identical junk happens to be adjacent
|
||||
to an "interesting" match.
|
||||
|
||||
Here's the same example as before, but considering blanks to be junk.
|
||||
That prevents " abcd" from matching the " abcd" at the tail end of the
|
||||
second sequence directly. Instead only the "abcd" can match, and
|
||||
matches the leftmost "abcd" in the second sequence:
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(1, 0, 4)
|
||||
|
||||
If no blocks match, return (alo, blo, 0).
|
||||
|
||||
>>> s = SequenceMatcher(None, "ab", "c")
|
||||
>>> s.find_longest_match(0, 2, 0, 1)
|
||||
(0, 0, 0)
|
||||
|
||||
get_matching_blocks()
|
||||
Return list of triples describing matching subsequences.
|
||||
|
||||
Each triple is of the form (i, j, n), and means that
|
||||
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in i
|
||||
and in j.
|
||||
|
||||
The last triple is a dummy, (len(a), len(b), 0), and is the only triple
|
||||
with n==0.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abxcd", "abcd")
|
||||
>>> s.get_matching_blocks()
|
||||
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
|
||||
|
||||
get_opcodes()
|
||||
Return list of 5-tuples describing how to turn a into b.
|
||||
|
||||
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple has
|
||||
i1 == j1 == 0, and remaining tuples have i1 == the i2 from the tuple
|
||||
preceding it, and likewise for j1 == the previous j2.
|
||||
|
||||
The tags are strings, with these meanings:
|
||||
|
||||
'replace': a[i1:i2] should be replaced by b[j1:j2]
|
||||
'delete': a[i1:i2] should be deleted.
|
||||
Note that j1==j2 in this case.
|
||||
'insert': b[j1:j2] should be inserted at a[i1:i1].
|
||||
Note that i1==i2 in this case.
|
||||
'equal': a[i1:i2] == b[j1:j2]
|
||||
|
||||
>>> a = "qabxcd"
|
||||
>>> b = "abycdf"
|
||||
>>> s = SequenceMatcher(None, a, b)
|
||||
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
|
||||
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
|
||||
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
|
||||
delete a[0:1] (q) b[0:0] ()
|
||||
equal a[1:3] (ab) b[0:2] (ab)
|
||||
replace a[3:4] (x) b[2:3] (y)
|
||||
equal a[4:6] (cd) b[3:5] (cd)
|
||||
insert a[6:6] () b[5:6] (f)
|
||||
|
||||
ratio()
|
||||
Return a measure of the sequences' similarity (float in [0,1]).
|
||||
|
||||
Where T is the total number of elements in both sequences, and M is the
|
||||
number of matches, this is 2,0*M / T. Note that this is 1 if the
|
||||
sequences are identical, and 0 if they have nothing in common.
|
||||
|
||||
.ratio() is expensive to compute if you haven't already computed
|
||||
.get_matching_blocks() or .get_opcodes(), in which case you may want to
|
||||
try .quick_ratio() or .real_quick_ratio() first to get an upper bound.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.quick_ratio()
|
||||
0.75
|
||||
>>> s.real_quick_ratio()
|
||||
1.0
|
||||
|
||||
quick_ratio()
|
||||
Return an upper bound on .ratio() relatively quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute.
|
||||
|
||||
real_quick_ratio():
|
||||
Return an upper bound on ratio() very quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute than either .ratio() or .quick_ratio().
|
||||
"""
|
||||
|
||||
TRACE = 0
|
||||
|
||||
class SequenceMatcher:
|
||||
def __init__(self, isjunk=None, a='', b=''):
|
||||
"""Construct a SequenceMatcher.
|
||||
|
||||
Optional arg isjunk is None (the default), or a one-argument
|
||||
function that takes a sequence element and returns true iff the
|
||||
element is junk. None is equivalent to passing "lambda x: 0", i.e.
|
||||
no elements are considered to be junk. For example, pass
|
||||
lambda x: x in " \\t"
|
||||
if you're comparing lines as sequences of characters, and don't
|
||||
want to synch up on blanks or hard tabs.
|
||||
|
||||
Optional arg a is the first of two sequences to be compared. By
|
||||
default, an empty string. The elements of a must be hashable. See
|
||||
also .set_seqs() and .set_seq1().
|
||||
|
||||
Optional arg b is the second of two sequences to be compared. By
|
||||
default, an empty string. The elements of b must be hashable. See
|
||||
also .set_seqs() and .set_seq2().
|
||||
"""
|
||||
|
||||
# Members:
|
||||
# a
|
||||
# first sequence
|
||||
# b
|
||||
# second sequence; differences are computed as "what do
|
||||
# we need to do to 'a' to change it into 'b'?"
|
||||
# b2j
|
||||
# for x in b, b2j[x] is a list of the indices (into b)
|
||||
# at which x appears; junk elements do not appear
|
||||
# b2jhas
|
||||
# b2j.has_key
|
||||
# fullbcount
|
||||
# for x in b, fullbcount[x] == the number of times x
|
||||
# appears in b; only materialized if really needed (used
|
||||
# only for computing quick_ratio())
|
||||
# matching_blocks
|
||||
# a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
|
||||
# ascending & non-overlapping in i and in j; terminated by
|
||||
# a dummy (len(a), len(b), 0) sentinel
|
||||
# opcodes
|
||||
# a list of (tag, i1, i2, j1, j2) tuples, where tag is
|
||||
# one of
|
||||
# 'replace' a[i1:i2] should be replaced by b[j1:j2]
|
||||
# 'delete' a[i1:i2] should be deleted
|
||||
# 'insert' b[j1:j2] should be inserted
|
||||
# 'equal' a[i1:i2] == b[j1:j2]
|
||||
# isjunk
|
||||
# a user-supplied function taking a sequence element and
|
||||
# returning true iff the element is "junk" -- this has
|
||||
# subtle but helpful effects on the algorithm, which I'll
|
||||
# get around to writing up someday <0.9 wink>.
|
||||
# DON'T USE! Only __chain_b uses this. Use isbjunk.
|
||||
# isbjunk
|
||||
# for x in b, isbjunk(x) == isjunk(x) but much faster;
|
||||
# it's really the has_key method of a hidden dict.
|
||||
# DOES NOT WORK for x in a!
|
||||
|
||||
self.isjunk = isjunk
|
||||
self.a = self.b = None
|
||||
self.set_seqs(a, b)
|
||||
|
||||
def set_seqs(self, a, b):
|
||||
"""Set the two sequences to be compared.
|
||||
|
||||
>>> s = SequenceMatcher()
|
||||
>>> s.set_seqs("abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
"""
|
||||
|
||||
self.set_seq1(a)
|
||||
self.set_seq2(b)
|
||||
|
||||
def set_seq1(self, a):
|
||||
"""Set the first sequence to be compared.
|
||||
|
||||
The second sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq1("bcde")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against
|
||||
many sequences, use .set_seq2(S) once and call .set_seq1(x)
|
||||
repeatedly for each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq2().
|
||||
"""
|
||||
|
||||
if a is self.a:
|
||||
return
|
||||
self.a = a
|
||||
self.matching_blocks = self.opcodes = None
|
||||
|
||||
def set_seq2(self, b):
|
||||
"""Set the second sequence to be compared.
|
||||
|
||||
The first sequence to be compared is not changed.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.set_seq2("abcd")
|
||||
>>> s.ratio()
|
||||
1.0
|
||||
>>>
|
||||
|
||||
SequenceMatcher computes and caches detailed information about the
|
||||
second sequence, so if you want to compare one sequence S against
|
||||
many sequences, use .set_seq2(S) once and call .set_seq1(x)
|
||||
repeatedly for each of the other sequences.
|
||||
|
||||
See also set_seqs() and set_seq1().
|
||||
"""
|
||||
|
||||
if b is self.b:
|
||||
return
|
||||
self.b = b
|
||||
self.matching_blocks = self.opcodes = None
|
||||
self.fullbcount = None
|
||||
self.__chain_b()
|
||||
|
||||
# For each element x in b, set b2j[x] to a list of the indices in
|
||||
# b where x appears; the indices are in increasing order; note that
|
||||
# the number of times x appears in b is len(b2j[x]) ...
|
||||
# when self.isjunk is defined, junk elements don't show up in this
|
||||
# map at all, which stops the central find_longest_match method
|
||||
# from starting any matching block at a junk element ...
|
||||
# also creates the fast isbjunk function ...
|
||||
# note that this is only called when b changes; so for cross-product
|
||||
# kinds of matches, it's best to call set_seq2 once, then set_seq1
|
||||
# repeatedly
|
||||
|
||||
def __chain_b(self):
|
||||
# Because isjunk is a user-defined (not C) function, and we test
|
||||
# for junk a LOT, it's important to minimize the number of calls.
|
||||
# Before the tricks described here, __chain_b was by far the most
|
||||
# time-consuming routine in the whole module! If anyone sees
|
||||
# Jim Roskind, thank him again for profile.py -- I never would
|
||||
# have guessed that.
|
||||
# The first trick is to build b2j ignoring the possibility
|
||||
# of junk. I.e., we don't call isjunk at all yet. Throwing
|
||||
# out the junk later is much cheaper than building b2j "right"
|
||||
# from the start.
|
||||
b = self.b
|
||||
self.b2j = b2j = {}
|
||||
self.b2jhas = b2jhas = b2j.has_key
|
||||
for i in xrange(len(b)):
|
||||
elt = b[i]
|
||||
if b2jhas(elt):
|
||||
b2j[elt].append(i)
|
||||
else:
|
||||
b2j[elt] = [i]
|
||||
|
||||
# Now b2j.keys() contains elements uniquely, and especially when
|
||||
# the sequence is a string, that's usually a good deal smaller
|
||||
# than len(string). The difference is the number of isjunk calls
|
||||
# saved.
|
||||
isjunk, junkdict = self.isjunk, {}
|
||||
if isjunk:
|
||||
for elt in b2j.keys():
|
||||
if isjunk(elt):
|
||||
junkdict[elt] = 1 # value irrelevant; it's a set
|
||||
del b2j[elt]
|
||||
|
||||
# Now for x in b, isjunk(x) == junkdict.has_key(x), but the
|
||||
# latter is much faster. Note too that while there may be a
|
||||
# lot of junk in the sequence, the number of *unique* junk
|
||||
# elements is probably small. So the memory burden of keeping
|
||||
# this dict alive is likely trivial compared to the size of b2j.
|
||||
self.isbjunk = junkdict.has_key
|
||||
|
||||
def find_longest_match(self, alo, ahi, blo, bhi):
|
||||
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
|
||||
If isjunk is not defined:
|
||||
|
||||
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
alo <= i <= i+k <= ahi
|
||||
blo <= j <= j+k <= bhi
|
||||
and for all (i',j',k') meeting those conditions,
|
||||
k >= k'
|
||||
i <= i'
|
||||
and if i == i', j <= j'
|
||||
|
||||
In other words, of all maximal matching blocks, return one that
|
||||
starts earliest in a, and of all those maximal matching blocks that
|
||||
start earliest in a, return the one that starts earliest in b.
|
||||
|
||||
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(0, 4, 5)
|
||||
|
||||
If isjunk is defined, first the longest matching block is
|
||||
determined as above, but with the additional restriction that no
|
||||
junk element appears in the block. Then that block is extended as
|
||||
far as possible by matching (only) junk elements on both sides. So
|
||||
the resulting block never matches on junk except as identical junk
|
||||
happens to be adjacent to an "interesting" match.
|
||||
|
||||
Here's the same example as before, but considering blanks to be
|
||||
junk. That prevents " abcd" from matching the " abcd" at the tail
|
||||
end of the second sequence directly. Instead only the "abcd" can
|
||||
match, and matches the leftmost "abcd" in the second sequence:
|
||||
|
||||
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
|
||||
>>> s.find_longest_match(0, 5, 0, 9)
|
||||
(1, 0, 4)
|
||||
|
||||
If no blocks match, return (alo, blo, 0).
|
||||
|
||||
>>> s = SequenceMatcher(None, "ab", "c")
|
||||
>>> s.find_longest_match(0, 2, 0, 1)
|
||||
(0, 0, 0)
|
||||
"""
|
||||
|
||||
# CAUTION: stripping common prefix or suffix would be incorrect.
|
||||
# E.g.,
|
||||
# ab
|
||||
# acab
|
||||
# Longest matching block is "ab", but if common prefix is
|
||||
# stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||
# strip, so ends up claiming that ab is changed to acab by
|
||||
# inserting "ca" in the middle. That's minimal but unintuitive:
|
||||
# "it's obvious" that someone inserted "ac" at the front.
|
||||
# Windiff ends up at the same place as diff, but by pairing up
|
||||
# the unique 'b's and then matching the first two 'a's.
|
||||
|
||||
a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
|
||||
besti, bestj, bestsize = alo, blo, 0
|
||||
# find longest junk-free match
|
||||
# during an iteration of the loop, j2len[j] = length of longest
|
||||
# junk-free match ending with a[i-1] and b[j]
|
||||
j2len = {}
|
||||
nothing = []
|
||||
for i in xrange(alo, ahi):
|
||||
# look at all instances of a[i] in b; note that because
|
||||
# b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||
j2lenget = j2len.get
|
||||
newj2len = {}
|
||||
for j in b2j.get(a[i], nothing):
|
||||
# a[i] matches b[j]
|
||||
if j < blo:
|
||||
continue
|
||||
if j >= bhi:
|
||||
break
|
||||
k = newj2len[j] = j2lenget(j-1, 0) + 1
|
||||
if k > bestsize:
|
||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||
j2len = newj2len
|
||||
|
||||
# Now that we have a wholly interesting match (albeit possibly
|
||||
# empty!), we may as well suck up the matching junk on each
|
||||
# side of it too. Can't think of a good reason not to, and it
|
||||
# saves post-processing the (possibly considerable) expense of
|
||||
# figuring out what to do with it. In the case of an empty
|
||||
# interesting match, this is clearly the right thing to do,
|
||||
# because no other kind of match is possible in the regions.
|
||||
while besti > alo and bestj > blo and \
|
||||
isbjunk(b[bestj-1]) and \
|
||||
a[besti-1] == b[bestj-1]:
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
while besti+bestsize < ahi and bestj+bestsize < bhi and \
|
||||
isbjunk(b[bestj+bestsize]) and \
|
||||
a[besti+bestsize] == b[bestj+bestsize]:
|
||||
bestsize = bestsize + 1
|
||||
|
||||
if TRACE:
|
||||
print "get_matching_blocks", alo, ahi, blo, bhi
|
||||
print " returns", besti, bestj, bestsize
|
||||
return besti, bestj, bestsize
|
||||
|
||||
def get_matching_blocks(self):
|
||||
"""Return list of triples describing matching subsequences.
|
||||
|
||||
Each triple is of the form (i, j, n), and means that
|
||||
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||
i and in j.
|
||||
|
||||
The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||
triple with n==0.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abxcd", "abcd")
|
||||
>>> s.get_matching_blocks()
|
||||
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
|
||||
"""
|
||||
|
||||
if self.matching_blocks is not None:
|
||||
return self.matching_blocks
|
||||
self.matching_blocks = []
|
||||
la, lb = len(self.a), len(self.b)
|
||||
self.__helper(0, la, 0, lb, self.matching_blocks)
|
||||
self.matching_blocks.append( (la, lb, 0) )
|
||||
if TRACE:
|
||||
print '*** matching blocks', self.matching_blocks
|
||||
return self.matching_blocks
|
||||
|
||||
# builds list of matching blocks covering a[alo:ahi] and
|
||||
# b[blo:bhi], appending them in increasing order to answer
|
||||
|
||||
def __helper(self, alo, ahi, blo, bhi, answer):
|
||||
i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
|
||||
# a[alo:i] vs b[blo:j] unknown
|
||||
# a[i:i+k] same as b[j:j+k]
|
||||
# a[i+k:ahi] vs b[j+k:bhi] unknown
|
||||
if k:
|
||||
if alo < i and blo < j:
|
||||
self.__helper(alo, i, blo, j, answer)
|
||||
answer.append(x)
|
||||
if i+k < ahi and j+k < bhi:
|
||||
self.__helper(i+k, ahi, j+k, bhi, answer)
|
||||
|
||||
def get_opcodes(self):
|
||||
"""Return list of 5-tuples describing how to turn a into b.
|
||||
|
||||
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||
has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||
tuple preceding it, and likewise for j1 == the previous j2.
|
||||
|
||||
The tags are strings, with these meanings:
|
||||
|
||||
'replace': a[i1:i2] should be replaced by b[j1:j2]
|
||||
'delete': a[i1:i2] should be deleted.
|
||||
Note that j1==j2 in this case.
|
||||
'insert': b[j1:j2] should be inserted at a[i1:i1].
|
||||
Note that i1==i2 in this case.
|
||||
'equal': a[i1:i2] == b[j1:j2]
|
||||
|
||||
>>> a = "qabxcd"
|
||||
>>> b = "abycdf"
|
||||
>>> s = SequenceMatcher(None, a, b)
|
||||
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
|
||||
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
|
||||
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
|
||||
delete a[0:1] (q) b[0:0] ()
|
||||
equal a[1:3] (ab) b[0:2] (ab)
|
||||
replace a[3:4] (x) b[2:3] (y)
|
||||
equal a[4:6] (cd) b[3:5] (cd)
|
||||
insert a[6:6] () b[5:6] (f)
|
||||
"""
|
||||
|
||||
if self.opcodes is not None:
|
||||
return self.opcodes
|
||||
i = j = 0
|
||||
self.opcodes = answer = []
|
||||
for ai, bj, size in self.get_matching_blocks():
|
||||
# invariant: we've pumped out correct diffs to change
|
||||
# a[:i] into b[:j], and the next matching block is
|
||||
# a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||
# out a diff to change a[i:ai] into b[j:bj], pump out
|
||||
# the matching block, and move (i,j) beyond the match
|
||||
tag = ''
|
||||
if i < ai and j < bj:
|
||||
tag = 'replace'
|
||||
elif i < ai:
|
||||
tag = 'delete'
|
||||
elif j < bj:
|
||||
tag = 'insert'
|
||||
if tag:
|
||||
answer.append( (tag, i, ai, j, bj) )
|
||||
i, j = ai+size, bj+size
|
||||
# the list of matching blocks is terminated by a
|
||||
# sentinel with size 0
|
||||
if size:
|
||||
answer.append( ('equal', ai, i, bj, j) )
|
||||
return answer
|
||||
|
||||
def ratio(self):
|
||||
"""Return a measure of the sequences' similarity (float in [0,1]).
|
||||
|
||||
Where T is the total number of elements in both sequences, and
|
||||
M is the number of matches, this is 2,0*M / T.
|
||||
Note that this is 1 if the sequences are identical, and 0 if
|
||||
they have nothing in common.
|
||||
|
||||
.ratio() is expensive to compute if you haven't already computed
|
||||
.get_matching_blocks() or .get_opcodes(), in which case you may
|
||||
want to try .quick_ratio() or .real_quick_ratio() first to get an
|
||||
upper bound.
|
||||
|
||||
>>> s = SequenceMatcher(None, "abcd", "bcde")
|
||||
>>> s.ratio()
|
||||
0.75
|
||||
>>> s.quick_ratio()
|
||||
0.75
|
||||
>>> s.real_quick_ratio()
|
||||
1.0
|
||||
"""
|
||||
|
||||
matches = reduce(lambda sum, triple: sum + triple[-1],
|
||||
self.get_matching_blocks(), 0)
|
||||
return 2.0 * matches / (len(self.a) + len(self.b))
|
||||
|
||||
def quick_ratio(self):
|
||||
"""Return an upper bound on ratio() relatively quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute.
|
||||
"""
|
||||
|
||||
# viewing a and b as multisets, set matches to the cardinality
|
||||
# of their intersection; this counts the number of matches
|
||||
# without regard to order, so is clearly an upper bound
|
||||
if self.fullbcount is None:
|
||||
self.fullbcount = fullbcount = {}
|
||||
for elt in self.b:
|
||||
fullbcount[elt] = fullbcount.get(elt, 0) + 1
|
||||
fullbcount = self.fullbcount
|
||||
# avail[x] is the number of times x appears in 'b' less the
|
||||
# number of times we've seen it in 'a' so far ... kinda
|
||||
avail = {}
|
||||
availhas, matches = avail.has_key, 0
|
||||
for elt in self.a:
|
||||
if availhas(elt):
|
||||
numb = avail[elt]
|
||||
else:
|
||||
numb = fullbcount.get(elt, 0)
|
||||
avail[elt] = numb - 1
|
||||
if numb > 0:
|
||||
matches = matches + 1
|
||||
return 2.0 * matches / (len(self.a) + len(self.b))
|
||||
|
||||
def real_quick_ratio(self):
|
||||
"""Return an upper bound on ratio() very quickly.
|
||||
|
||||
This isn't defined beyond that it is an upper bound on .ratio(), and
|
||||
is faster to compute than either .ratio() or .quick_ratio().
|
||||
"""
|
||||
|
||||
la, lb = len(self.a), len(self.b)
|
||||
# can't have more matches than the number of elements in the
|
||||
# shorter sequence
|
||||
return 2.0 * min(la, lb) / (la + lb)
|
||||
|
||||
def get_close_matches(word, possibilities, n=3, cutoff=0.6):
|
||||
"""Use SequenceMatcher to return list of the best "good enough" matches.
|
||||
|
||||
word is a sequence for which close matches are desired (typically a
|
||||
string).
|
||||
|
||||
possibilities is a list of sequences against which to match word
|
||||
(typically a list of strings).
|
||||
|
||||
Optional arg n (default 3) is the maximum number of close matches to
|
||||
return. n must be > 0.
|
||||
|
||||
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
|
||||
that don't score at least that similar to word are ignored.
|
||||
|
||||
The best (no more than n) matches among the possibilities are returned
|
||||
in a list, sorted by similarity score, most similar first.
|
||||
|
||||
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
|
||||
['apple', 'ape']
|
||||
>>> import keyword
|
||||
>>> get_close_matches("wheel", keyword.kwlist)
|
||||
['while']
|
||||
>>> get_close_matches("apple", keyword.kwlist)
|
||||
[]
|
||||
>>> get_close_matches("accept", keyword.kwlist)
|
||||
['except']
|
||||
"""
|
||||
|
||||
if not n > 0:
|
||||
raise ValueError("n must be > 0: " + `n`)
|
||||
if not 0.0 <= cutoff <= 1.0:
|
||||
raise ValueError("cutoff must be in [0.0, 1.0]: " + `cutoff`)
|
||||
result = []
|
||||
s = SequenceMatcher()
|
||||
s.set_seq2(word)
|
||||
for x in possibilities:
|
||||
s.set_seq1(x)
|
||||
if s.real_quick_ratio() >= cutoff and \
|
||||
s.quick_ratio() >= cutoff and \
|
||||
s.ratio() >= cutoff:
|
||||
result.append((s.ratio(), x))
|
||||
# Sort by score.
|
||||
result.sort()
|
||||
# Retain only the best n.
|
||||
result = result[-n:]
|
||||
# Move best-scorer to head of list.
|
||||
result.reverse()
|
||||
# Strip scores.
|
||||
# Python 2.x list comprehensions: return [x for score, x in result]
|
||||
return_result = []
|
||||
for score, x in result:
|
||||
return_result.append(x)
|
||||
return return_result
|
||||
|
||||
def _test():
|
||||
import doctest, difflib
|
||||
return doctest.testmod(difflib)
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
523
lib/ezt.py
Normal file
523
lib/ezt.py
Normal file
@@ -0,0 +1,523 @@
|
||||
#!/usr/bin/env python
|
||||
"""ezt.py -- easy templating
|
||||
|
||||
ezt templates are very similar to standard HTML files. But additionaly
|
||||
they contain directives sprinkled in between. With these directives
|
||||
it possible to generate the dynamic content from the ezt templates.
|
||||
|
||||
These directives are enclosed in square brackets. If you are a
|
||||
C-programmer, you might be familar with the #ifdef directives of the
|
||||
C preprocessor 'cpp'. ezt provides a similar concept for HTML. Additionally
|
||||
EZT has a 'for' directive, which allows to iterate (repeat) certain
|
||||
subsections of the template according to sequence of data items
|
||||
provided by the application.
|
||||
|
||||
The HTML rendering is performed by the method generate() of the Template
|
||||
class. Building template instances can either be done using external
|
||||
EZT files (convention: use the suffix .ezt for such files):
|
||||
|
||||
>>> template = Template("../templates/log.ezt")
|
||||
|
||||
or by calling the parse() method of a template instance directly with
|
||||
a EZT template string:
|
||||
|
||||
>>> template = Template()
|
||||
>>> template.parse('''<html><head>
|
||||
... <title>[title_string]</title></head>
|
||||
... <body><h1>[title_string]</h1>
|
||||
... [for a_sequence] <p>[a_sequence]</p>
|
||||
... [end] <hr>
|
||||
... The [person] is [if-any state]in[else]out[end].
|
||||
... </body>
|
||||
... </html>
|
||||
... ''')
|
||||
|
||||
The application should build a dictionary 'data' and pass it together
|
||||
with the output fileobject to the templates generate method:
|
||||
|
||||
>>> data = {'title_string' : "A Dummy Page",
|
||||
... 'a_sequence' : ['list item 1', 'list item 2', 'another element'],
|
||||
... 'person': "doctor",
|
||||
... 'state' : None }
|
||||
>>> import sys
|
||||
>>> template.generate(sys.stdout, data)
|
||||
<html><head>
|
||||
<title>A Dummy Page</title></head>
|
||||
<body><h1>A Dummy Page</h1>
|
||||
<p>list item 1</p>
|
||||
<p>list item 2</p>
|
||||
<p>another element</p>
|
||||
<hr>
|
||||
The doctor is out.
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Directives
|
||||
==========
|
||||
|
||||
Several directives allow the use of dotted qualified names refering to objects
|
||||
or attributes of objects contained in the data dictionary given to the
|
||||
.generate() method.
|
||||
|
||||
Simple directives
|
||||
-----------------
|
||||
|
||||
[QUAL_NAME]
|
||||
|
||||
This directive is simply replaced by the value of identifier from the data
|
||||
dictionary. QUAL_NAME might be a dotted qualified name refering to some
|
||||
instance attribute of objects contained in the dats dictionary.
|
||||
Numbers are converted to string though.
|
||||
|
||||
[include "filename"] or [include QUAL_NAME]
|
||||
|
||||
This directive is replaced by content of the named include file.
|
||||
|
||||
Block directives
|
||||
----------------
|
||||
|
||||
[for QUAL_NAME] ... [end]
|
||||
|
||||
The text within the [for ...] directive and the corresponding [end]
|
||||
is repeated for each element in the sequence referred to by the qualified
|
||||
name in the for directive. Within the for block this identifiers now
|
||||
refers to the actual item indexed by this loop iteration.
|
||||
|
||||
[if-any QUAL_NAME] ... [else] ... [end]
|
||||
|
||||
Test if the value QUAL_NAME is not None or an empty string or list.
|
||||
The [else] clause is optional. CAUTION: Numeric values are converted to string,
|
||||
so if QUAL_NAME refers to a numeric value 0, the then-clause is
|
||||
substituted!
|
||||
|
||||
[if-index odd] ... [else] ... [end]
|
||||
[if-index even] ... [else] ... [end]
|
||||
[if-index first] ... [else] ... [end]
|
||||
[if-index last] ... [else] ... [end]
|
||||
[if-index NUMBER] ... [else] ... [end]
|
||||
|
||||
These five directives work similar to [if-any], but are only useful
|
||||
within a [for ...]-block (see above). The odd/even directives are
|
||||
for example useful to choose different background colors for adjacent rows
|
||||
in a table. Similar the first/last directives might be used to
|
||||
remove certain parts (for example "Diff to previous" doesn't make sense,
|
||||
if there is no previous).
|
||||
|
||||
[is QUAL_NAME STRING] ... [else] ... [end]
|
||||
[is QUAL_NAME QUAL_NAME] ... [else] ... [end]
|
||||
|
||||
The [is ...] directive is similar to the other conditional directives
|
||||
above. But it allows to compare two value references or a value reference
|
||||
with some constant string.
|
||||
|
||||
"""
|
||||
#
|
||||
# Copyright (C) 2001 Greg Stein. All Rights Reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * 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 COPYRIGHT HOLDERS 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 REGENTS 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.
|
||||
#
|
||||
#
|
||||
# This software is maintained by Greg and is available at:
|
||||
# http://viewcvs.sourceforge.net/
|
||||
# it is also used by the following projects:
|
||||
# http://edna.sourceforge.net/
|
||||
#
|
||||
|
||||
import string
|
||||
import re
|
||||
from types import StringType, IntType, FloatType
|
||||
import os
|
||||
|
||||
#
|
||||
# This regular expression matches three alternatives:
|
||||
# expr: DIRECTIVE | BRACKET | COMMENT
|
||||
# DIRECTIVE: '[' ITEM (whitespace ITEM)* ']
|
||||
# ITEM: STRING | NAME
|
||||
# STRING: '"' (not-slash-or-dquote | '\' anychar)* '"'
|
||||
# NAME: (alphanum | '_' | '-' | '.')+
|
||||
# BRACKET: '[[]'
|
||||
# COMMENT: '[#' not-rbracket* ']'
|
||||
#
|
||||
# When used with the split() method, the return value will be composed of
|
||||
# non-matching text and the two paren groups (DIRECTIVE and BRACKET). Since
|
||||
# the COMMENT matches are not placed into a group, they are considered a
|
||||
# "splitting" value and simply dropped.
|
||||
#
|
||||
_item = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)'
|
||||
_re_parse = re.compile(r'\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % (_item, _item))
|
||||
|
||||
_re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+')
|
||||
|
||||
# block commands and their argument counts
|
||||
_block_cmd_specs = { 'if-any':1, 'if-index':2, 'for':1, 'is':2 }
|
||||
_block_cmds = _block_cmd_specs.keys()
|
||||
|
||||
# two regular expresssions for compressing whitespace. the first is used to
|
||||
# compress any whitespace including a newline into a single newline. the
|
||||
# second regex is used to compress runs of whitespace into a single space.
|
||||
_re_newline = re.compile('[ \t\r\f\v]*\n\\s*')
|
||||
_re_whitespace = re.compile(r'\s\s+')
|
||||
|
||||
# this regex is used to substitute arguments into a value. we split the value,
|
||||
# replace the relevant pieces, and then put it all back together. splitting
|
||||
# will produce a list of: TEXT ( splitter TEXT )*. splitter will be '%' or
|
||||
# an integer.
|
||||
_re_subst = re.compile('%(%|[0-9]+)')
|
||||
|
||||
class Template:
|
||||
|
||||
def __init__(self, fname=None):
|
||||
if fname:
|
||||
self.parse_file(fname)
|
||||
|
||||
def parse_file(self, fname):
|
||||
"""fname -> a string object with pathname of file containg an EZT template.
|
||||
"""
|
||||
self.program = self._parse_file(fname)
|
||||
|
||||
def parse(self, text):
|
||||
"""text -> a string object containing the HTML template.
|
||||
|
||||
parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT
|
||||
DIRECTIVE will be '[directive]' or None
|
||||
BRACKET will be '[[]' or None
|
||||
"""
|
||||
self.program = self._parse(text)
|
||||
|
||||
def generate(self, fp, data):
|
||||
ctx = _context()
|
||||
ctx.data = data
|
||||
ctx.for_index = { }
|
||||
self._execute(self.program, fp, ctx)
|
||||
|
||||
def _parse_file(self, fname, for_names=None, file_args=()):
|
||||
return self._parse(open(fname, "rt").read(), for_names, file_args,
|
||||
os.path.dirname(fname))
|
||||
|
||||
def _parse(self, text, for_names=None, file_args=(), base=None):
|
||||
"""text -> string object containing the HTML template.
|
||||
|
||||
This is a private helper function doing the real work for method parse.
|
||||
It returns the parsed template as a 'program'. This program is a sequence
|
||||
made out of strings or (function, argument) 2-tuples.
|
||||
|
||||
Note: comment directives [# ...] are automatically dropped by _re_parse.
|
||||
"""
|
||||
|
||||
parts = _re_parse.split(text)
|
||||
|
||||
program = [ ]
|
||||
stack = [ ]
|
||||
if not for_names:
|
||||
for_names = [ ]
|
||||
|
||||
for i in range(len(parts)):
|
||||
piece = parts[i]
|
||||
which = i % 3 # discriminate between: TEXT DIRECTIVE BRACKET
|
||||
if which == 0:
|
||||
# TEXT. append if non-empty.
|
||||
if piece:
|
||||
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece))
|
||||
program.append(piece)
|
||||
elif which == 2:
|
||||
# BRACKET directive. append '[' if present.
|
||||
if piece:
|
||||
program.append('[')
|
||||
elif piece:
|
||||
# DIRECTIVE is present.
|
||||
args = _re_args.findall(piece)
|
||||
cmd = args[0]
|
||||
if cmd == 'else':
|
||||
if len(args) > 1:
|
||||
raise ArgCountSyntaxError()
|
||||
### check: don't allow for 'for' cmd
|
||||
idx = stack[-1][1]
|
||||
true_section = program[idx:]
|
||||
del program[idx:]
|
||||
stack[-1][3] = true_section
|
||||
elif cmd == 'end':
|
||||
if len(args) > 1:
|
||||
raise ArgCountSyntaxError()
|
||||
# note: true-section may be None
|
||||
cmd, idx, args, true_section = stack.pop()
|
||||
else_section = program[idx:]
|
||||
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
|
||||
program[idx:] = [ (func, (args, true_section, else_section)) ]
|
||||
if cmd == 'for':
|
||||
for_names.pop()
|
||||
elif cmd in _block_cmds:
|
||||
if len(args) > _block_cmd_specs[cmd] + 1:
|
||||
raise ArgCountSyntaxError()
|
||||
### this assumes arg1 is always a ref
|
||||
args[1] = _prepare_ref(args[1], for_names, file_args)
|
||||
|
||||
# handle arg2 for the 'is' command
|
||||
if cmd == 'is':
|
||||
args[2] = _prepare_ref(args[2], for_names, file_args)
|
||||
elif cmd == 'for':
|
||||
for_names.append(args[1][0])
|
||||
|
||||
# remember the cmd, current pos, args, and a section placeholder
|
||||
stack.append([cmd, len(program), args[1:], None])
|
||||
elif cmd == 'include':
|
||||
if args[1][0] == '"':
|
||||
include_filename = args[1][1:-1]
|
||||
if base:
|
||||
include_filename = os.path.join(base, include_filename)
|
||||
f_args = [ ]
|
||||
for arg in args[2:]:
|
||||
f_args.append(_prepare_ref(arg, for_names, file_args))
|
||||
program.extend(self._parse_file(include_filename, for_names,
|
||||
f_args))
|
||||
else:
|
||||
if len(args) != 2:
|
||||
raise ArgCountSyntaxError()
|
||||
program.append((self._cmd_include,
|
||||
(_prepare_ref(args[1], for_names, file_args),
|
||||
base)))
|
||||
else:
|
||||
# implied PRINT command
|
||||
if len(args) > 1:
|
||||
f_args = [ ]
|
||||
for arg in args:
|
||||
f_args.append(_prepare_ref(arg, for_names, file_args))
|
||||
program.append((self._cmd_format, (f_args[0], f_args[1:])))
|
||||
else:
|
||||
program.append((self._cmd_print,
|
||||
_prepare_ref(args[0], for_names, file_args)))
|
||||
|
||||
if stack:
|
||||
### would be nice to say which blocks...
|
||||
raise UnclosedBlocksError()
|
||||
return program
|
||||
|
||||
def _execute(self, program, fp, ctx):
|
||||
"""This private helper function takes a 'program' sequence as created
|
||||
by the method '_parse' and executes it step by step. strings are written
|
||||
to the file object 'fp' and functions are called.
|
||||
"""
|
||||
for step in program:
|
||||
if isinstance(step, StringType):
|
||||
fp.write(step)
|
||||
else:
|
||||
step[0](step[1], fp, ctx)
|
||||
|
||||
def _cmd_print(self, valref, fp, ctx):
|
||||
value = _get_value(valref, ctx)
|
||||
|
||||
# if the value has a 'read' attribute, then it is a stream: copy it
|
||||
if hasattr(value, 'read'):
|
||||
while 1:
|
||||
chunk = value.read(16384)
|
||||
if not chunk:
|
||||
break
|
||||
fp.write(chunk)
|
||||
else:
|
||||
fp.write(value)
|
||||
|
||||
def _cmd_format(self, (valref, args), fp, ctx):
|
||||
fmt = _get_value(valref, ctx)
|
||||
parts = _re_subst.split(fmt)
|
||||
for i in range(len(parts)):
|
||||
piece = parts[i]
|
||||
if i%2 == 1 and piece != '%':
|
||||
idx = int(piece)
|
||||
if idx < len(args):
|
||||
piece = _get_value(args[idx], ctx)
|
||||
else:
|
||||
piece = '<undef>'
|
||||
fp.write(piece)
|
||||
|
||||
def _cmd_include(self, (valref, base), fp, ctx):
|
||||
fname = _get_value(valref, ctx)
|
||||
if base:
|
||||
fname = os.path.join(base, fname)
|
||||
### note: we don't have the set of for_names to pass into this parse.
|
||||
### I don't think there is anything to do but document it.
|
||||
self._execute(self._parse_file(fname), fp, ctx)
|
||||
|
||||
def _cmd_if_any(self, args, fp, ctx):
|
||||
"If the value is a non-empty string or non-empty list, then T else F."
|
||||
((valref,), t_section, f_section) = args
|
||||
value = _get_value(valref, ctx)
|
||||
self._do_if(value, t_section, f_section, fp, ctx)
|
||||
|
||||
def _cmd_if_index(self, args, fp, ctx):
|
||||
((valref, value), t_section, f_section) = args
|
||||
list, idx = ctx.for_index[valref[0]]
|
||||
if value == 'even':
|
||||
value = idx % 2 == 0
|
||||
elif value == 'odd':
|
||||
value = idx % 2 == 1
|
||||
elif value == 'first':
|
||||
value = idx == 0
|
||||
elif value == 'last':
|
||||
value = idx == len(list)-1
|
||||
else:
|
||||
value = idx == int(value)
|
||||
self._do_if(value, t_section, f_section, fp, ctx)
|
||||
|
||||
def _cmd_is(self, args, fp, ctx):
|
||||
((left_ref, right_ref), t_section, f_section) = args
|
||||
value = _get_value(right_ref, ctx)
|
||||
value = string.lower(_get_value(left_ref, ctx)) == string.lower(value)
|
||||
self._do_if(value, t_section, f_section, fp, ctx)
|
||||
|
||||
def _do_if(self, value, t_section, f_section, fp, ctx):
|
||||
if t_section is None:
|
||||
t_section = f_section
|
||||
f_section = None
|
||||
if value:
|
||||
section = t_section
|
||||
else:
|
||||
section = f_section
|
||||
if section is not None:
|
||||
self._execute(section, fp, ctx)
|
||||
|
||||
def _cmd_for(self, args, fp, ctx):
|
||||
((valref,), unused, section) = args
|
||||
list = _get_value(valref, ctx)
|
||||
if isinstance(list, StringType):
|
||||
raise NeedSequenceError()
|
||||
refname = valref[0]
|
||||
ctx.for_index[refname] = idx = [ list, 0 ]
|
||||
for item in list:
|
||||
self._execute(section, fp, ctx)
|
||||
idx[1] = idx[1] + 1
|
||||
del ctx.for_index[refname]
|
||||
|
||||
def boolean(value):
|
||||
"Return a value suitable for [if-any bool_var] usage in a template."
|
||||
if value:
|
||||
return 'yes'
|
||||
return None
|
||||
|
||||
|
||||
def _prepare_ref(refname, for_names, file_args):
|
||||
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
|
||||
for_names -> a list of active for sequences.
|
||||
|
||||
Returns a `value reference', a 3-Tupel made out of (refname, start, rest),
|
||||
for fast access later.
|
||||
"""
|
||||
# is the reference a string constant?
|
||||
if refname[0] == '"':
|
||||
return None, refname[1:-1], None
|
||||
|
||||
# if this is an include-argument, then just return the prepared ref
|
||||
if refname[:3] == 'arg':
|
||||
try:
|
||||
idx = int(refname[3:])
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if idx < len(file_args):
|
||||
return file_args[idx]
|
||||
|
||||
parts = string.split(refname, '.')
|
||||
start = parts[0]
|
||||
rest = parts[1:]
|
||||
while rest and (start in for_names):
|
||||
# check if the next part is also a "for name"
|
||||
name = start + '.' + rest[0]
|
||||
if name in for_names:
|
||||
start = name
|
||||
del rest[0]
|
||||
else:
|
||||
break
|
||||
return refname, start, rest
|
||||
|
||||
def _get_value((refname, start, rest), ctx):
|
||||
"""(refname, start, rest) -> a prepared `value reference' (see above).
|
||||
ctx -> an execution context instance.
|
||||
|
||||
Does a name space lookup within the template name space. Active
|
||||
for blocks take precedence over data dictionary members with the
|
||||
same name.
|
||||
"""
|
||||
if rest is None:
|
||||
# it was a string constant
|
||||
return start
|
||||
if ctx.for_index.has_key(start):
|
||||
list, idx = ctx.for_index[start]
|
||||
ob = list[idx]
|
||||
elif ctx.data.has_key(start):
|
||||
ob = ctx.data[start]
|
||||
else:
|
||||
raise UnknownReference(refname)
|
||||
|
||||
# walk the rest of the dotted reference
|
||||
for attr in rest:
|
||||
try:
|
||||
ob = getattr(ob, attr)
|
||||
except AttributeError:
|
||||
raise UnknownReference(refname)
|
||||
|
||||
# make sure we return a string instead of some various Python types
|
||||
if isinstance(ob, IntType) or isinstance(ob, FloatType):
|
||||
return str(ob)
|
||||
if ob is None:
|
||||
return ''
|
||||
|
||||
# string or a sequence
|
||||
return ob
|
||||
|
||||
class _context:
|
||||
"""A container for the execution context"""
|
||||
|
||||
class ArgCountSyntaxError(Exception):
|
||||
pass
|
||||
|
||||
class UnknownReference(Exception):
|
||||
pass
|
||||
|
||||
class NeedSequenceError(Exception):
|
||||
pass
|
||||
|
||||
class UnclosedBlocksError(Exception):
|
||||
pass
|
||||
|
||||
# --- standard test environment ---
|
||||
def test_parse():
|
||||
assert _re_parse.split('[a]') == ['', '[a]', None, '']
|
||||
assert _re_parse.split('[a] [b]') == \
|
||||
['', '[a]', None, ' ', '[b]', None, '']
|
||||
assert _re_parse.split('[a c] [b]') == \
|
||||
['', '[a c]', None, ' ', '[b]', None, '']
|
||||
assert _re_parse.split('x [a] y [b] z') == \
|
||||
['x ', '[a]', None, ' y ', '[b]', None, ' z']
|
||||
assert _re_parse.split('[a "b" c "d"]') == \
|
||||
['', '[a "b" c "d"]', None, '']
|
||||
assert _re_parse.split(r'["a \"b[foo]" c.d f]') == \
|
||||
['', '["a \\"b[foo]" c.d f]', None, '']
|
||||
|
||||
def _test(argv):
|
||||
import doctest, ezt
|
||||
verbose = "-v" in argv
|
||||
return doctest.testmod(ezt, verbose=verbose)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# invoke unit test for this module:
|
||||
import sys
|
||||
sys.exit(_test(sys.argv)[0])
|
347
lib/ndiff.py
Normal file
347
lib/ndiff.py
Normal file
@@ -0,0 +1,347 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Module ndiff version 1.6.0
|
||||
# Released to the public domain 08-Dec-2000,
|
||||
# by Tim Peters (tim.one@home.com).
|
||||
|
||||
# Backported to Python 1.5.2 for ViewCVS by pf@artcom-gmbh.de, 24-Dec-2001
|
||||
|
||||
# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
|
||||
|
||||
"""ndiff [-q] file1 file2
|
||||
or
|
||||
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
|
||||
|
||||
Print a human-friendly file difference report to stdout. Both inter-
|
||||
and intra-line differences are noted. In the second form, recreate file1
|
||||
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
|
||||
|
||||
In the first form, if -q ("quiet") is not specified, the first two lines
|
||||
of output are
|
||||
|
||||
-: file1
|
||||
+: file2
|
||||
|
||||
Each remaining line begins with a two-letter code:
|
||||
|
||||
"- " line unique to file1
|
||||
"+ " line unique to file2
|
||||
" " line common to both files
|
||||
"? " line not present in either input file
|
||||
|
||||
Lines beginning with "? " attempt to guide the eye to intraline
|
||||
differences, and were not present in either input file. These lines can be
|
||||
confusing if the source files contain tab characters.
|
||||
|
||||
The first file can be recovered by retaining only lines that begin with
|
||||
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
|
||||
|
||||
The second file can be recovered similarly, but by retaining only " " and
|
||||
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
|
||||
recovered by piping the output through
|
||||
|
||||
sed -n '/^[+ ] /s/^..//p'
|
||||
|
||||
See module comments for details and programmatic interface.
|
||||
"""
|
||||
|
||||
__version__ = 1, 6, 1
|
||||
|
||||
# SequenceMatcher tries to compute a "human-friendly diff" between
|
||||
# two sequences (chiefly picturing a file as a sequence of lines,
|
||||
# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm)
|
||||
# diff, the fundamental notion is the longest *contiguous* & junk-free
|
||||
# matching subsequence. That's what catches peoples' eyes. The
|
||||
# Windows(tm) windiff has another interesting notion, pairing up elements
|
||||
# that appear uniquely in each sequence. That, and the method here,
|
||||
# appear to yield more intuitive difference reports than does diff. This
|
||||
# method appears to be the least vulnerable to synching up on blocks
|
||||
# of "junk lines", though (like blank lines in ordinary text files,
|
||||
# or maybe "<P>" lines in HTML files). That may be because this is
|
||||
# the only method of the 3 that has a *concept* of "junk" <wink>.
|
||||
#
|
||||
# Note that ndiff makes no claim to produce a *minimal* diff. To the
|
||||
# contrary, minimal diffs are often counter-intuitive, because they
|
||||
# synch up anywhere possible, sometimes accidental matches 100 pages
|
||||
# apart. Restricting synch points to contiguous matches preserves some
|
||||
# notion of locality, at the occasional cost of producing a longer diff.
|
||||
#
|
||||
# With respect to junk, an earlier version of ndiff simply refused to
|
||||
# *start* a match with a junk element. The result was cases like this:
|
||||
# before: private Thread currentThread;
|
||||
# after: private volatile Thread currentThread;
|
||||
# If you consider whitespace to be junk, the longest contiguous match
|
||||
# not starting with junk is "e Thread currentThread". So ndiff reported
|
||||
# that "e volatil" was inserted between the 't' and the 'e' in "private".
|
||||
# While an accurate view, to people that's absurd. The current version
|
||||
# looks for matching blocks that are entirely junk-free, then extends the
|
||||
# longest one of those as far as possible but only with matching junk.
|
||||
# So now "currentThread" is matched, then extended to suck up the
|
||||
# preceding blank; then "private" is matched, and extended to suck up the
|
||||
# following blank; then "Thread" is matched; and finally ndiff reports
|
||||
# that "volatile " was inserted before "Thread". The only quibble
|
||||
# remaining is that perhaps it was really the case that " volatile"
|
||||
# was inserted after "private". I can live with that <wink>.
|
||||
#
|
||||
# NOTE on junk: the module-level names
|
||||
# IS_LINE_JUNK
|
||||
# IS_CHARACTER_JUNK
|
||||
# can be set to any functions you like. The first one should accept
|
||||
# a single string argument, and return true iff the string is junk.
|
||||
# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a
|
||||
# line without visible characters, except for at most one splat).
|
||||
# The second should accept a string of length 1 etc. The default is
|
||||
# whether the character is a blank or tab (note: bad idea to include
|
||||
# newline in this!).
|
||||
#
|
||||
# After setting those, you can call fcompare(f1name, f2name) with the
|
||||
# names of the files you want to compare. The difference report
|
||||
# is sent to stdout. Or you can call main(args), passing what would
|
||||
# have been in sys.argv[1:] had the cmd-line form been used.
|
||||
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
import string
|
||||
TRACE = 0
|
||||
|
||||
# define what "junk" means
|
||||
import re
|
||||
|
||||
def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
|
||||
return pat(line) is not None
|
||||
|
||||
def IS_CHARACTER_JUNK(ch, ws=" \t"):
|
||||
return ch in ws
|
||||
|
||||
del re
|
||||
|
||||
# meant for dumping lines
|
||||
def dump(tag, x, lo, hi):
|
||||
for i in xrange(lo, hi):
|
||||
print tag, x[i],
|
||||
|
||||
def plain_replace(a, alo, ahi, b, blo, bhi):
|
||||
assert alo < ahi and blo < bhi
|
||||
# dump the shorter block first -- reduces the burden on short-term
|
||||
# memory if the blocks are of very different sizes
|
||||
if bhi - blo < ahi - alo:
|
||||
dump('+', b, blo, bhi)
|
||||
dump('-', a, alo, ahi)
|
||||
else:
|
||||
dump('-', a, alo, ahi)
|
||||
dump('+', b, blo, bhi)
|
||||
|
||||
# When replacing one block of lines with another, this guy searches
|
||||
# the blocks for *similar* lines; the best-matching pair (if any) is
|
||||
# used as a synch point, and intraline difference marking is done on
|
||||
# the similar pair. Lots of work, but often worth it.
|
||||
|
||||
def fancy_replace(a, alo, ahi, b, blo, bhi):
|
||||
if TRACE:
|
||||
print '*** fancy_replace', alo, ahi, blo, bhi
|
||||
dump('>', a, alo, ahi)
|
||||
dump('<', b, blo, bhi)
|
||||
|
||||
# don't synch up unless the lines have a similarity score of at
|
||||
# least cutoff; best_ratio tracks the best score seen so far
|
||||
best_ratio, cutoff = 0.74, 0.75
|
||||
cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
|
||||
eqi, eqj = None, None # 1st indices of equal lines (if any)
|
||||
|
||||
# search for the pair that matches best without being identical
|
||||
# (identical lines must be junk lines, & we don't want to synch up
|
||||
# on junk -- unless we have to)
|
||||
for j in xrange(blo, bhi):
|
||||
bj = b[j]
|
||||
cruncher.set_seq2(bj)
|
||||
for i in xrange(alo, ahi):
|
||||
ai = a[i]
|
||||
if ai == bj:
|
||||
if eqi is None:
|
||||
eqi, eqj = i, j
|
||||
continue
|
||||
cruncher.set_seq1(ai)
|
||||
# computing similarity is expensive, so use the quick
|
||||
# upper bounds first -- have seen this speed up messy
|
||||
# compares by a factor of 3.
|
||||
# note that ratio() is only expensive to compute the first
|
||||
# time it's called on a sequence pair; the expensive part
|
||||
# of the computation is cached by cruncher
|
||||
if cruncher.real_quick_ratio() > best_ratio and \
|
||||
cruncher.quick_ratio() > best_ratio and \
|
||||
cruncher.ratio() > best_ratio:
|
||||
best_ratio, best_i, best_j = cruncher.ratio(), i, j
|
||||
if best_ratio < cutoff:
|
||||
# no non-identical "pretty close" pair
|
||||
if eqi is None:
|
||||
# no identical pair either -- treat it as a straight replace
|
||||
plain_replace(a, alo, ahi, b, blo, bhi)
|
||||
return
|
||||
# no close pair, but an identical pair -- synch up on that
|
||||
best_i, best_j, best_ratio = eqi, eqj, 1.0
|
||||
else:
|
||||
# there's a close pair, so forget the identical pair (if any)
|
||||
eqi = None
|
||||
|
||||
# a[best_i] very similar to b[best_j]; eqi is None iff they're not
|
||||
# identical
|
||||
if TRACE:
|
||||
print '*** best_ratio', best_ratio, best_i, best_j
|
||||
dump('>', a, best_i, best_i+1)
|
||||
dump('<', b, best_j, best_j+1)
|
||||
|
||||
# pump out diffs from before the synch point
|
||||
fancy_helper(a, alo, best_i, b, blo, best_j)
|
||||
|
||||
# do intraline marking on the synch pair
|
||||
aelt, belt = a[best_i], b[best_j]
|
||||
if eqi is None:
|
||||
# pump out a '-', '?', '+', '?' quad for the synched lines
|
||||
atags = btags = ""
|
||||
cruncher.set_seqs(aelt, belt)
|
||||
for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
|
||||
la, lb = ai2 - ai1, bj2 - bj1
|
||||
if tag == 'replace':
|
||||
atags = atags + '^' * la
|
||||
btags = btags + '^' * lb
|
||||
elif tag == 'delete':
|
||||
atags = atags + '-' * la
|
||||
elif tag == 'insert':
|
||||
btags = btags + '+' * lb
|
||||
elif tag == 'equal':
|
||||
atags = atags + ' ' * la
|
||||
btags = btags + ' ' * lb
|
||||
else:
|
||||
raise ValueError, 'unknown tag ' + `tag`
|
||||
printq(aelt, belt, atags, btags)
|
||||
else:
|
||||
# the synch pair is identical
|
||||
print ' ', aelt,
|
||||
|
||||
# pump out diffs from after the synch point
|
||||
fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
|
||||
|
||||
def fancy_helper(a, alo, ahi, b, blo, bhi):
|
||||
if alo < ahi:
|
||||
if blo < bhi:
|
||||
fancy_replace(a, alo, ahi, b, blo, bhi)
|
||||
else:
|
||||
dump('-', a, alo, ahi)
|
||||
elif blo < bhi:
|
||||
dump('+', b, blo, bhi)
|
||||
|
||||
# Crap to deal with leading tabs in "?" output. Can hurt, but will
|
||||
# probably help most of the time.
|
||||
|
||||
def printq(aline, bline, atags, btags):
|
||||
common = min(count_leading(aline, "\t"),
|
||||
count_leading(bline, "\t"))
|
||||
common = min(common, count_leading(atags[:common], " "))
|
||||
print "-", aline,
|
||||
if count_leading(atags, " ") < len(atags):
|
||||
print "?", "\t" * common + atags[common:]
|
||||
print "+", bline,
|
||||
if count_leading(btags, " ") < len(btags):
|
||||
print "?", "\t" * common + btags[common:]
|
||||
|
||||
def count_leading(line, ch):
|
||||
i, n = 0, len(line)
|
||||
while i < n and line[i] == ch:
|
||||
i = i+1
|
||||
return i
|
||||
|
||||
def fail(msg):
|
||||
import sys
|
||||
out = sys.stderr.write
|
||||
out(msg + "\n\n")
|
||||
out(__doc__)
|
||||
return 0
|
||||
|
||||
# open a file & return the file object; gripe and return 0 if it
|
||||
# couldn't be opened
|
||||
def fopen(fname):
|
||||
try:
|
||||
return open(fname, 'r')
|
||||
except IOError, detail:
|
||||
return fail("couldn't open " + fname + ": " + str(detail))
|
||||
|
||||
# open two files & spray the diff to stdout; return false iff a problem
|
||||
def fcompare(f1name, f2name):
|
||||
f1 = fopen(f1name)
|
||||
f2 = fopen(f2name)
|
||||
if not f1 or not f2:
|
||||
return 0
|
||||
|
||||
a = f1.readlines(); f1.close()
|
||||
b = f2.readlines(); f2.close()
|
||||
|
||||
cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
|
||||
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
|
||||
if tag == 'replace':
|
||||
fancy_replace(a, alo, ahi, b, blo, bhi)
|
||||
elif tag == 'delete':
|
||||
dump('-', a, alo, ahi)
|
||||
elif tag == 'insert':
|
||||
dump('+', b, blo, bhi)
|
||||
elif tag == 'equal':
|
||||
dump(' ', a, alo, ahi)
|
||||
else:
|
||||
raise ValueError, 'unknown tag ' + `tag`
|
||||
|
||||
return 1
|
||||
|
||||
# crack args (sys.argv[1:] is normal) & compare;
|
||||
# return false iff a problem
|
||||
|
||||
def main(args):
|
||||
import getopt
|
||||
try:
|
||||
opts, args = getopt.getopt(args, "qr:")
|
||||
except getopt.error, detail:
|
||||
return fail(str(detail))
|
||||
noisy = 1
|
||||
qseen = rseen = 0
|
||||
for opt, val in opts:
|
||||
if opt == "-q":
|
||||
qseen = 1
|
||||
noisy = 0
|
||||
elif opt == "-r":
|
||||
rseen = 1
|
||||
whichfile = val
|
||||
if qseen and rseen:
|
||||
return fail("can't specify both -q and -r")
|
||||
if rseen:
|
||||
if args:
|
||||
return fail("no args allowed with -r option")
|
||||
if whichfile in "12":
|
||||
restore(whichfile)
|
||||
return 1
|
||||
return fail("-r value must be 1 or 2")
|
||||
if len(args) != 2:
|
||||
return fail("need 2 filename args")
|
||||
f1name, f2name = args
|
||||
if noisy:
|
||||
print '-:', f1name
|
||||
print '+:', f2name
|
||||
return fcompare(f1name, f2name)
|
||||
|
||||
def restore(which):
|
||||
import sys
|
||||
tag = {"1": "- ", "2": "+ "}[which]
|
||||
prefixes = (" ", tag)
|
||||
for line in sys.stdin.readlines():
|
||||
if line[:2] in prefixes:
|
||||
print line[2:],
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if "-profile" in args:
|
||||
import profile, pstats
|
||||
args.remove("-profile")
|
||||
statf = "ndiff.pro"
|
||||
profile.run("main(args)", statf)
|
||||
stats = pstats.Stats(statf)
|
||||
stats.strip_dirs().sort_stats('time').print_stats()
|
||||
else:
|
||||
main(args)
|
190
lib/popen.py
Normal file
190
lib/popen.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# popen.py: a replacement for os.popen()
|
||||
#
|
||||
# This implementation of popen() provides a cmd + args calling sequence,
|
||||
# rather than a system() type of convention. The shell facilities are not
|
||||
# available, but that implies we can avoid worrying about shell hacks in
|
||||
# the arguments.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def popen(cmd, args, mode, capture_err=1):
|
||||
# flush the stdio buffers since we are about to change the FD under them
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
r, w = os.pipe()
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
# in the parent
|
||||
|
||||
# close the descriptor that we don't need and return the other one.
|
||||
if mode == 'r':
|
||||
os.close(w)
|
||||
return _pipe(os.fdopen(r, 'r'), pid)
|
||||
os.close(r)
|
||||
return _pipe(os.fdopen(w, 'w'), pid)
|
||||
|
||||
# in the child
|
||||
|
||||
# we'll need /dev/null for the discarded I/O
|
||||
null = os.open('/dev/null', os.O_RDWR)
|
||||
|
||||
if mode == 'r':
|
||||
# hook stdout/stderr to the "write" channel
|
||||
os.dup2(w, 1)
|
||||
# "close" stdin; the child shouldn't use it
|
||||
### this isn't quite right... we may want the child to read from stdin
|
||||
os.dup2(null, 0)
|
||||
# what to do with errors?
|
||||
if capture_err:
|
||||
os.dup2(w, 2)
|
||||
else:
|
||||
os.dup2(null, 2)
|
||||
else:
|
||||
# hook stdin to the "read" channel
|
||||
os.dup2(r, 0)
|
||||
# "close" stdout/stderr; the child shouldn't use them
|
||||
### this isn't quite right... we may want the child to write to these
|
||||
os.dup2(null, 1)
|
||||
os.dup2(null, 2)
|
||||
|
||||
# don't need these FDs any more
|
||||
os.close(null)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# the stdin/stdout/stderr are all set up. exec the target
|
||||
try:
|
||||
os.execvp(cmd, (cmd,) + tuple(args))
|
||||
except:
|
||||
# aid debugging, if the os.execvp above fails for some reason:
|
||||
import string
|
||||
print "<h2>exec failed:</h2><pre>", cmd, string.join(args), "</pre>"
|
||||
raise
|
||||
|
||||
# crap. shouldn't be here.
|
||||
sys.exit(127)
|
||||
|
||||
def pipe_cmds(cmds):
|
||||
# flush the stdio buffers since we are about to change the FD under them
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
prev_r, parent_w = os.pipe()
|
||||
|
||||
null = os.open('/dev/null', os.O_RDWR)
|
||||
|
||||
for cmd in cmds[:-1]:
|
||||
r, w = os.pipe()
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# hook up stdout to the output channel
|
||||
os.dup2(w, 1)
|
||||
|
||||
# toss errors
|
||||
os.dup2(null, 2)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
os.close(null)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# time to run the command
|
||||
try:
|
||||
os.execvp(cmd[0], cmd)
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
# in the parent
|
||||
|
||||
# we don't need these any more
|
||||
os.close(prev_r)
|
||||
os.close(w)
|
||||
|
||||
# the read channel of this pipe will feed into to the next command
|
||||
prev_r = r
|
||||
|
||||
# no longer needed
|
||||
os.close(null)
|
||||
|
||||
# done with most of the commands. set up the last command to write to stdout
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child (the last command)
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
|
||||
# run the last command
|
||||
try:
|
||||
os.execvp(cmds[-1][0], cmds[-1])
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
# not needed any more
|
||||
os.close(prev_r)
|
||||
|
||||
# write into the first pipe, wait on the final process
|
||||
return _pipe(os.fdopen(parent_w, 'w'), pid)
|
||||
|
||||
|
||||
class _pipe:
|
||||
"Wrapper for a file which can wait() on a child process at close time."
|
||||
|
||||
def __init__(self, file, child_pid):
|
||||
self.file = file
|
||||
self.child_pid = child_pid
|
||||
|
||||
def eof(self):
|
||||
pid, status = os.waitpid(self.child_pid, os.WNOHANG)
|
||||
if pid:
|
||||
self.file.close()
|
||||
self.file = None
|
||||
return status
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
if self.file:
|
||||
self.file.close()
|
||||
self.file = None
|
||||
return os.waitpid(self.child_pid, 0)[1]
|
||||
return None
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.file, name)
|
||||
|
||||
def __del__(self):
|
||||
if self.file:
|
||||
self.close()
|
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)
|
||||
|
||||
|
431
lib/query.py
Normal file
431
lib/query.py
Normal file
@@ -0,0 +1,431 @@
|
||||
#!/usr/bin/python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# CGI script to process and display queries to CVSdb
|
||||
#
|
||||
# This script is part of the ViewCVS package. More information can be
|
||||
# found at http://www.lyra.org/viewcvs/.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# These values will be set during the installation process. During
|
||||
# development, they will remain None.
|
||||
#
|
||||
|
||||
CONF_PATHNAME = None
|
||||
|
||||
#########################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import cgi
|
||||
import time
|
||||
|
||||
import cvsdb
|
||||
import viewcvs
|
||||
import ezt
|
||||
|
||||
class FormData:
|
||||
def __init__(self, form):
|
||||
self.valid = 0
|
||||
|
||||
self.repository = ""
|
||||
self.branch = ""
|
||||
self.directory = ""
|
||||
self.file = ""
|
||||
self.who = ""
|
||||
self.sortby = ""
|
||||
self.date = ""
|
||||
self.hours = 0
|
||||
|
||||
self.decode_thyself(form)
|
||||
|
||||
def decode_thyself(self, form):
|
||||
try:
|
||||
self.repository = string.strip(form["repository"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.branch = string.strip(form["branch"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.directory = string.strip(form["directory"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.file = string.strip(form["file"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.who = string.strip(form["who"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.sortby = string.strip(form["sortby"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.date = string.strip(form["date"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.hours = int(form["hours"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
## returns a tuple-list (mod-str, string)
|
||||
def listparse_string(str):
|
||||
return_list = []
|
||||
|
||||
cmd = ""
|
||||
temp = ""
|
||||
escaped = 0
|
||||
state = "eat leading whitespace"
|
||||
|
||||
for c in str:
|
||||
## handle escaped charactors
|
||||
if not escaped and c == "\\":
|
||||
escaped = 1
|
||||
continue
|
||||
|
||||
## strip leading white space
|
||||
if state == "eat leading whitespace":
|
||||
if c in string.whitespace:
|
||||
continue
|
||||
else:
|
||||
state = "get command or data"
|
||||
|
||||
## parse to '"' or ","
|
||||
if state == "get command or data":
|
||||
|
||||
## just add escaped charactors
|
||||
if escaped:
|
||||
escaped = 0
|
||||
temp = temp + c
|
||||
continue
|
||||
|
||||
## the data is in quotes after the command
|
||||
elif c == "\"":
|
||||
cmd = temp
|
||||
temp = ""
|
||||
state = "get quoted data"
|
||||
continue
|
||||
|
||||
## this tells us there was no quoted data, therefore no
|
||||
## command; add the command and start over
|
||||
elif c == ",":
|
||||
## strip ending whitespace on un-quoted data
|
||||
temp = string.rstrip(temp)
|
||||
return_list.append( ("", temp) )
|
||||
temp = ""
|
||||
state = "eat leading whitespace"
|
||||
continue
|
||||
|
||||
## record the data
|
||||
else:
|
||||
temp = temp + c
|
||||
continue
|
||||
|
||||
## parse until ending '"'
|
||||
if state == "get quoted data":
|
||||
|
||||
## just add escaped charactors
|
||||
if escaped:
|
||||
escaped = 0
|
||||
temp = temp + c
|
||||
continue
|
||||
|
||||
## look for ending '"'
|
||||
elif c == "\"":
|
||||
return_list.append( (cmd, temp) )
|
||||
cmd = ""
|
||||
temp = ""
|
||||
state = "eat comma after quotes"
|
||||
continue
|
||||
|
||||
## record the data
|
||||
else:
|
||||
temp = temp + c
|
||||
continue
|
||||
|
||||
## parse until ","
|
||||
if state == "eat comma after quotes":
|
||||
if c in string.whitespace:
|
||||
continue
|
||||
|
||||
elif c == ",":
|
||||
state = "eat leading whitespace"
|
||||
continue
|
||||
|
||||
else:
|
||||
print "format error"
|
||||
sys.exit(1)
|
||||
|
||||
if cmd or temp:
|
||||
return_list.append((cmd, temp))
|
||||
|
||||
return return_list
|
||||
|
||||
def decode_command(cmd):
|
||||
if cmd == "r":
|
||||
return "regex"
|
||||
elif cmd == "l":
|
||||
return "like"
|
||||
else:
|
||||
return "exact"
|
||||
|
||||
def form_to_cvsdb_query(form_data):
|
||||
query = cvsdb.CreateCheckinQuery()
|
||||
|
||||
if form_data.repository:
|
||||
for cmd, str in listparse_string(form_data.repository):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetRepository(str, cmd)
|
||||
|
||||
if form_data.branch:
|
||||
for cmd, str in listparse_string(form_data.branch):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetBranch(str, cmd)
|
||||
|
||||
if form_data.directory:
|
||||
for cmd, str in listparse_string(form_data.directory):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetDirectory(str, cmd)
|
||||
|
||||
if form_data.file:
|
||||
for cmd, str in listparse_string(form_data.file):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetFile(str, cmd)
|
||||
|
||||
if form_data.who:
|
||||
for cmd, str in listparse_string(form_data.who):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetAuthor(str, cmd)
|
||||
|
||||
if form_data.sortby == "author":
|
||||
query.SetSortMethod("author")
|
||||
elif form_data.sortby == "file":
|
||||
query.SetSortMethod("file")
|
||||
else:
|
||||
query.SetSortMethod("date")
|
||||
|
||||
if form_data.date:
|
||||
if form_data.date == "hours" and form_data.hours:
|
||||
query.SetFromDateHoursAgo(form_data.hours)
|
||||
elif form_data.date == "day":
|
||||
query.SetFromDateDaysAgo(1)
|
||||
elif form_data.date == "week":
|
||||
query.SetFromDateDaysAgo(7)
|
||||
elif form_data.date == "month":
|
||||
query.SetFromDateDaysAgo(31)
|
||||
|
||||
return query
|
||||
|
||||
def cvsroot_name_from_path(cvsroot):
|
||||
## we need to resolve the cvsroot path from the database
|
||||
## to the name given to it in the viewcvs.conf file
|
||||
for key, value in cfg.general.cvs_roots.items():
|
||||
if value == cvsroot:
|
||||
return key
|
||||
|
||||
return None
|
||||
|
||||
def build_commit(desc, files):
|
||||
ob = _item(num_files=len(files), files=[])
|
||||
|
||||
if desc:
|
||||
ob.desc = string.replace(cgi.escape(desc), '\n', '<br>')
|
||||
else:
|
||||
ob.desc = ' '
|
||||
|
||||
for commit in files:
|
||||
ctime = commit.GetTime()
|
||||
if not ctime:
|
||||
ctime = " ";
|
||||
else:
|
||||
ctime = time.strftime("%y/%m/%d %H:%M", time.localtime(ctime))
|
||||
|
||||
## make the file link
|
||||
file = os.path.join(commit.GetDirectory(), commit.GetFile())
|
||||
file_full_path = os.path.join(commit.GetRepository(), file)
|
||||
|
||||
## if we couldn't find the cvsroot path configured in the
|
||||
## viewcvs.conf file, then don't make the link
|
||||
cvsroot_name = cvsroot_name_from_path(commit.GetRepository())
|
||||
if cvsroot_name:
|
||||
flink = '<a href="viewcvs.cgi/%s?cvsroot=%s">%s</a>' \
|
||||
% (file, cvsroot_name, file_full_path)
|
||||
else:
|
||||
flink = file_full_path
|
||||
|
||||
ob.files.append(_item(date=ctime,
|
||||
author=commit.GetAuthor(),
|
||||
link=flink,
|
||||
rev=commit.GetRevision(),
|
||||
branch=commit.GetBranch(),
|
||||
plus=int(commit.GetPlusCount()),
|
||||
minus=int(commit.GetMinusCount()),
|
||||
))
|
||||
|
||||
return ob
|
||||
|
||||
def run_query(form_data):
|
||||
query = form_to_cvsdb_query(form_data)
|
||||
db = cvsdb.ConnectDatabaseReadOnly()
|
||||
db.RunQuery(query)
|
||||
|
||||
if not query.commit_list:
|
||||
return [ ]
|
||||
|
||||
commits = [ ]
|
||||
files = [ ]
|
||||
|
||||
current_desc = query.commit_list[0].GetDescription()
|
||||
for commit in query.commit_list:
|
||||
desc = commit.GetDescription()
|
||||
if current_desc == desc:
|
||||
files.append(commit)
|
||||
continue
|
||||
|
||||
commits.append(build_commit(current_desc, files))
|
||||
|
||||
files = [ commit ]
|
||||
current_desc = desc
|
||||
|
||||
## add the last file group to the commit list
|
||||
commits.append(build_commit(current_desc, files))
|
||||
|
||||
return commits
|
||||
|
||||
def handle_config():
|
||||
viewcvs.handle_config()
|
||||
global cfg
|
||||
cfg = viewcvs.cfg
|
||||
|
||||
def main():
|
||||
handle_config()
|
||||
|
||||
form = cgi.FieldStorage()
|
||||
form_data = FormData(form)
|
||||
|
||||
if form_data.valid:
|
||||
commits = run_query(form_data)
|
||||
query = None
|
||||
else:
|
||||
commits = [ ]
|
||||
query = 'skipped'
|
||||
|
||||
data = {
|
||||
'cfg' : cfg,
|
||||
'address' : cfg.general.address,
|
||||
'vsn' : viewcvs.__version__,
|
||||
|
||||
'repository' : cgi.escape(form_data.repository, 1),
|
||||
'branch' : cgi.escape(form_data.branch, 1),
|
||||
'directory' : cgi.escape(form_data.directory, 1),
|
||||
'file' : cgi.escape(form_data.file, 1),
|
||||
'who' : cgi.escape(form_data.who, 1),
|
||||
|
||||
'sortby' : form_data.sortby,
|
||||
'date' : form_data.date,
|
||||
|
||||
'query' : query,
|
||||
'commits' : commits,
|
||||
'num_commits' : len(commits),
|
||||
}
|
||||
|
||||
if form_data.hours:
|
||||
data['hours'] = form_data.hours
|
||||
else:
|
||||
data['hours'] = 2
|
||||
|
||||
template = ezt.Template()
|
||||
template.parse_file(os.path.join(viewcvs.g_install_dir,
|
||||
cfg.templates.query))
|
||||
|
||||
viewcvs.http_header()
|
||||
|
||||
# generate the page
|
||||
template.generate(sys.stdout, data)
|
||||
|
||||
def run_cgi():
|
||||
|
||||
### be nice to share all this logic with viewcvs.run_cgi
|
||||
|
||||
try:
|
||||
main()
|
||||
except SystemExit, e:
|
||||
# don't catch SystemExit (caused by sys.exit()). propagate the exit code
|
||||
sys.exit(e[0])
|
||||
except:
|
||||
info = sys.exc_info()
|
||||
viewcvs.http_header()
|
||||
print '<html><head><title>Python Exception Occurred</title></head>'
|
||||
print '<body bgcolor=white><h1>Python Exception Occurred</h1>'
|
||||
import traceback
|
||||
lines = apply(traceback.format_exception, info)
|
||||
print '<pre>'
|
||||
print cgi.escape(string.join(lines, ''))
|
||||
print '</pre>'
|
||||
viewcvs.html_footer()
|
||||
|
||||
|
||||
class _item:
|
||||
def __init__(self, **kw):
|
||||
vars(self).update(kw)
|
441
lib/rcsparse.py
Normal file
441
lib/rcsparse.py
Normal file
@@ -0,0 +1,441 @@
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This software is being maintained as part of the ViewCVS project.
|
||||
# Information is available at:
|
||||
# http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# This file was originally based on portions of the blame.py script by
|
||||
# Curt Hagenlocher.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import string
|
||||
import time
|
||||
|
||||
|
||||
class _TokenStream:
|
||||
token_term = string.whitespace + ';'
|
||||
|
||||
# the algorithm is about the same speed for any CHUNK_SIZE chosen.
|
||||
# grab a good-sized chunk, but not too large to overwhelm memory.
|
||||
CHUNK_SIZE = 100000
|
||||
|
||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||
|
||||
def __init__(self, file):
|
||||
self.rcsfile = file
|
||||
self.idx = 0
|
||||
self.buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if self.buf == '':
|
||||
raise RuntimeError, 'EOF'
|
||||
|
||||
def get(self):
|
||||
"Get the next token from the RCS file."
|
||||
|
||||
# Note: we can afford to loop within Python, examining individual
|
||||
# characters. For the whitespace and tokens, the number of iterations
|
||||
# is typically quite small. Thus, a simple iterative loop will beat
|
||||
# out more complex solutions.
|
||||
|
||||
buf = self.buf
|
||||
idx = self.idx
|
||||
|
||||
while 1:
|
||||
if idx == len(buf):
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
# signal EOF by returning None as the token
|
||||
del self.buf # so we fail if get() is called again
|
||||
return None
|
||||
idx = 0
|
||||
|
||||
if buf[idx] not in string.whitespace:
|
||||
break
|
||||
|
||||
idx = idx + 1
|
||||
|
||||
if buf[idx] == ';':
|
||||
self.buf = buf
|
||||
self.idx = idx + 1
|
||||
return ';'
|
||||
|
||||
if buf[idx] != '@':
|
||||
end = idx + 1
|
||||
token = ''
|
||||
while 1:
|
||||
# find token characters in the current buffer
|
||||
while end < len(buf) and buf[end] not in self.token_term:
|
||||
end = end + 1
|
||||
token = token + buf[idx:end]
|
||||
|
||||
if end < len(buf):
|
||||
# we stopped before the end, so we have a full token
|
||||
idx = end
|
||||
break
|
||||
|
||||
# we stopped at the end of the buffer, so we may have a partial token
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
idx = end = 0
|
||||
|
||||
self.buf = buf
|
||||
self.idx = idx
|
||||
return token
|
||||
|
||||
# a "string" which starts with the "@" character. we'll skip it when we
|
||||
# search for content.
|
||||
idx = idx + 1
|
||||
|
||||
chunks = [ ]
|
||||
|
||||
while 1:
|
||||
if idx == len(buf):
|
||||
idx = 0
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
raise RuntimeError, 'EOF'
|
||||
i = string.find(buf, '@', idx)
|
||||
if i == -1:
|
||||
chunks.append(buf[idx:])
|
||||
idx = len(buf)
|
||||
continue
|
||||
if i == len(buf) - 1:
|
||||
chunks.append(buf[idx:i])
|
||||
idx = 0
|
||||
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '@':
|
||||
raise RuntimeError, 'EOF'
|
||||
continue
|
||||
if buf[i + 1] == '@':
|
||||
chunks.append(buf[idx:i+1])
|
||||
idx = i + 2
|
||||
continue
|
||||
|
||||
chunks.append(buf[idx:i])
|
||||
|
||||
self.buf = buf
|
||||
self.idx = i + 1
|
||||
|
||||
return string.join(chunks, '')
|
||||
|
||||
# _get = get
|
||||
# def get(self):
|
||||
token = self._get()
|
||||
print 'T:', `token`
|
||||
return token
|
||||
|
||||
def match(self, match):
|
||||
"Try to match the next token from the input buffer."
|
||||
|
||||
token = self.get()
|
||||
if token != match:
|
||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
|
||||
'Expected token: %s, but saw: %s' % (match, token))
|
||||
|
||||
def unget(self, token):
|
||||
"Put this token back, for the next get() to return."
|
||||
|
||||
# Override the class' .get method with a function which clears the
|
||||
# overridden method then returns the pushed token. Since this function
|
||||
# will not be looked up via the class mechanism, it should be a "normal"
|
||||
# function, meaning it won't have "self" automatically inserted.
|
||||
# Therefore, we need to pass both self and the token thru via defaults.
|
||||
|
||||
# note: we don't put this into the input buffer because it may have been
|
||||
# @-unescaped already.
|
||||
|
||||
def give_it_back(self=self, token=token):
|
||||
del self.get
|
||||
return token
|
||||
|
||||
self.get = give_it_back
|
||||
|
||||
class Parser:
|
||||
|
||||
def parse_rcs_admin(self):
|
||||
while 1:
|
||||
# Read initial token at beginning of line
|
||||
token = self.ts.get()
|
||||
|
||||
# We're done once we reach the description of the RCS tree
|
||||
if token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
return
|
||||
|
||||
if token == "head":
|
||||
self.sink.set_head_revision(self.ts.get())
|
||||
self.ts.match(';')
|
||||
elif token == "branch":
|
||||
self.sink.set_principal_branch(self.ts.get())
|
||||
self.ts.match(';')
|
||||
elif token == "symbols":
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
(tag_name, tag_rev) = string.split(tag, ':')
|
||||
self.sink.define_tag(tag_name, tag_rev)
|
||||
elif token == "comment":
|
||||
self.sink.set_comment(self.ts.get())
|
||||
self.ts.match(';')
|
||||
|
||||
# Ignore all these other fields - We don't care about them. Also chews
|
||||
# up "newphrase".
|
||||
elif token in ("locks", "strict", "expand", "access"):
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
else:
|
||||
pass
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
|
||||
raise RuntimeError, "Unexpected EOF";
|
||||
|
||||
def parse_rcs_tree(self):
|
||||
while 1:
|
||||
revision = self.ts.get()
|
||||
|
||||
# End of RCS tree description ?
|
||||
if revision == 'desc':
|
||||
self.ts.unget(revision)
|
||||
return
|
||||
|
||||
# Parse date
|
||||
self.ts.match('date')
|
||||
date = self.ts.get()
|
||||
self.ts.match(';')
|
||||
|
||||
# Convert date into timestamp
|
||||
date_fields = string.split(date, '.') + ['0', '0', '0']
|
||||
date_fields = map(string.atoi, date_fields)
|
||||
if date_fields[0] < 100:
|
||||
date_fields[0] = date_fields[0] + 1900
|
||||
timestamp = time.mktime(tuple(date_fields))
|
||||
|
||||
# Parse author
|
||||
self.ts.match('author')
|
||||
author = self.ts.get()
|
||||
self.ts.match(';')
|
||||
|
||||
# Parse state
|
||||
self.ts.match('state')
|
||||
state = ''
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
state = state + token + ' '
|
||||
state = state[:-1] # toss the trailing space
|
||||
|
||||
# Parse branches
|
||||
self.ts.match('branches')
|
||||
branches = [ ]
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
branches.append(token)
|
||||
|
||||
# Parse revision of next delta in chain
|
||||
self.ts.match('next')
|
||||
next = self.ts.get()
|
||||
if next == ';':
|
||||
next = None
|
||||
else:
|
||||
self.ts.match(';')
|
||||
|
||||
# there are some files with extra tags in them. for example:
|
||||
# owner 640;
|
||||
# group 15;
|
||||
# permissions 644;
|
||||
# hardlinks @configure.in@;
|
||||
# this is "newphrase" in RCSFILE(5). we just want to skip over these.
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == 'desc' or token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
break
|
||||
# consume everything up to the semicolon
|
||||
while self.ts.get() != ';':
|
||||
pass
|
||||
|
||||
self.sink.define_revision(revision, timestamp, author, state, branches,
|
||||
next)
|
||||
|
||||
def parse_rcs_description(self):
|
||||
self.ts.match('desc')
|
||||
self.sink.set_description(self.ts.get())
|
||||
|
||||
def parse_rcs_deltatext(self):
|
||||
while 1:
|
||||
revision = self.ts.get()
|
||||
if revision is None:
|
||||
# EOF
|
||||
break
|
||||
self.ts.match('log')
|
||||
log = self.ts.get()
|
||||
### need to add code to chew up "newphrase"
|
||||
self.ts.match('text')
|
||||
text = self.ts.get()
|
||||
self.sink.set_revision_info(revision, log, text)
|
||||
|
||||
def parse(self, file, sink):
|
||||
self.ts = _TokenStream(file)
|
||||
self.sink = sink
|
||||
|
||||
self.parse_rcs_admin()
|
||||
self.parse_rcs_tree()
|
||||
|
||||
# many sinks want to know when the tree has been completed so they can
|
||||
# do some work to prep for the arrival of the deltatext
|
||||
self.sink.tree_completed()
|
||||
|
||||
self.parse_rcs_description()
|
||||
self.parse_rcs_deltatext()
|
||||
|
||||
# easiest for us to tell the sink it is done, rather than worry about
|
||||
# higher level software doing it.
|
||||
self.sink.parse_completed()
|
||||
|
||||
self.ts = self.sink = None
|
||||
|
||||
|
||||
class Sink:
|
||||
def set_head_revision(self, revision):
|
||||
pass
|
||||
def set_principal_branch(self, branch_name):
|
||||
pass
|
||||
def define_tag(self, name, revision):
|
||||
pass
|
||||
def set_comment(self, comment):
|
||||
pass
|
||||
def set_description(self, description):
|
||||
pass
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
pass
|
||||
def set_revision_info(self, revision, log, text):
|
||||
pass
|
||||
def tree_completed(self):
|
||||
pass
|
||||
def parse_completed(self):
|
||||
pass
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
#
|
||||
# TESTING AND DEBUGGING TOOLS
|
||||
#
|
||||
|
||||
class DebugSink:
|
||||
def set_head_revision(self, revision):
|
||||
print 'head:', revision
|
||||
|
||||
def set_principal_branch(self, branch_name):
|
||||
print 'branch:', branch_name
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
print 'tag:', name, '=', revision
|
||||
|
||||
def set_comment(self, comment):
|
||||
print 'comment:', comment
|
||||
|
||||
def set_description(self, description):
|
||||
print 'description:', description
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
print 'revision:', revision
|
||||
print ' timestamp:', timestamp
|
||||
print ' author:', author
|
||||
print ' state:', state
|
||||
print ' branches:', branches
|
||||
print ' next:', next
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
print 'revision:', revision
|
||||
print ' log:', log
|
||||
print ' text:', text[:100], '...'
|
||||
|
||||
class DumpSink:
|
||||
"""Dump all the parse information directly to stdout.
|
||||
|
||||
The output is relatively unformatted and untagged. It is intended as a
|
||||
raw dump of the data in the RCS file. A copy can be saved, then changes
|
||||
made to the parsing engine, then a comparison of the new output against
|
||||
the old output.
|
||||
"""
|
||||
def __init__(self):
|
||||
global sha
|
||||
import sha
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
print revision
|
||||
|
||||
def set_principal_branch(self, branch_name):
|
||||
print branch_name
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
print name, revision
|
||||
|
||||
def set_comment(self, comment):
|
||||
print comment
|
||||
|
||||
def set_description(self, description):
|
||||
print description
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
print revision, timestamp, author, state, branches, next
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
print revision, sha.new(log).hexdigest(), sha.new(text).hexdigest()
|
||||
|
||||
def tree_completed(self):
|
||||
print 'tree_completed'
|
||||
|
||||
def parse_completed(self):
|
||||
print 'parse_completed'
|
||||
|
||||
def dump_file(fname):
|
||||
Parser().parse(open(fname), DumpSink())
|
||||
|
||||
def time_file(fname):
|
||||
import time
|
||||
p = Parser().parse
|
||||
f = open(fname)
|
||||
s = Sink()
|
||||
t = time.time()
|
||||
p(f, s)
|
||||
t = time.time() - t
|
||||
print t
|
||||
|
||||
def _usage():
|
||||
print 'This is normally a module for importing, but it has a couple'
|
||||
print 'features for testing as an executable script.'
|
||||
print 'USAGE: %s COMMAND filename,v' % sys.argv[0]
|
||||
print ' where COMMAND is one of:'
|
||||
print ' dump: filename is "dumped" to stdout'
|
||||
print ' time: filename is parsed with the time written to stdout'
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if len(sys.argv) != 3:
|
||||
usage()
|
||||
if sys.argv[1] == 'dump':
|
||||
dump_file(sys.argv[2])
|
||||
elif sys.argv[1] == 'time':
|
||||
time_file(sys.argv[2])
|
||||
else:
|
||||
usage()
|
339
lib/rlog.py
Normal file
339
lib/rlog.py
Normal file
@@ -0,0 +1,339 @@
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import os
|
||||
import string
|
||||
import re
|
||||
import time
|
||||
|
||||
|
||||
## 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, cfg, 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))
|
||||
|
||||
temp = os.path.join(cfg.general.rcs_path, "rlog")
|
||||
self.cmd = '%s %s "%s"' % (temp, 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()
|
||||
# Since FreeBSD's rlog outputs extra "---...---\n" before
|
||||
# "===...===\n", _rlog_end may be occured here.
|
||||
if not line or line == _rlog_end:
|
||||
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.localtime(gmt_time)[8] == 1:
|
||||
# dst time active?
|
||||
# XXX: This is still wrong in those both nights,
|
||||
# where the switch between DST and normal time occurs.
|
||||
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(cfg, path, revision = '', date = ''):
|
||||
rlog = RLog(cfg, path, revision, date)
|
||||
rlog_parser = RLogOutputParser(rlog)
|
||||
return rlog_parser.rlog_data
|
2646
lib/viewcvs.py
Normal file
2646
lib/viewcvs.py
Normal file
File diff suppressed because it is too large
Load Diff
514
standalone.py
Executable file
514
standalone.py
Executable file
@@ -0,0 +1,514 @@
|
||||
#!/usr/bin/env python
|
||||
# $Id$
|
||||
# vim:sw=4:ts=4:et:nowrap
|
||||
# [Emacs: -*- python -*-]
|
||||
#
|
||||
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany
|
||||
# ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# Note: this module is designed to deploy instantly and run under any
|
||||
# version of Python from 1.5 and up. That's why some 2.0 features
|
||||
# (like string methods) are conspicuously avoided.
|
||||
|
||||
# XXX Security issues?
|
||||
|
||||
"""Run "standalone.py -p <port>" to start an HTTP server on a given port
|
||||
on the local machine to generate ViewCVS web pages.
|
||||
"""
|
||||
|
||||
__author__ = "Peter Funk <pf@artcom-gmbh.de>"
|
||||
__date__ = "11 November 2001"
|
||||
__version__ = "$Revision$"
|
||||
__credits__ = """Guido van Rossum, for an excellent programming language.
|
||||
Greg Stein, for writing ViewCVS in the first place.
|
||||
Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py.
|
||||
"""
|
||||
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# This value will be set during the installation process. During
|
||||
# development, it will remain None.
|
||||
#
|
||||
|
||||
LIBRARY_DIR = None
|
||||
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import urllib
|
||||
import socket
|
||||
import select
|
||||
import BaseHTTPServer
|
||||
|
||||
class Options:
|
||||
port = 7467 # default TCP/IP port used for the server
|
||||
start_gui = 0 # No GUI unless requested.
|
||||
repository = None # use default repository specified in config
|
||||
|
||||
# --- web browser interface: ----------------------------------------------
|
||||
|
||||
def serve(port, callback=None):
|
||||
"""start a HTTP server on the given port. call 'callback' when the
|
||||
server is ready to serve"""
|
||||
|
||||
class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
"""Serve a GET request."""
|
||||
if not self.path or self.path == "/":
|
||||
self.redirect()
|
||||
elif self.is_viewcvs():
|
||||
self.run_viewcvs()
|
||||
elif self.path[:7] == "/icons/":
|
||||
# XXX icon type should not be hardcoded to GIF:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "image/gif")
|
||||
self.end_headers()
|
||||
apache_icons.serve_icon(self.path, self.wfile)
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_POST(self):
|
||||
"""Serve a POST request."""
|
||||
if self.is_viewcvs():
|
||||
self.run_viewcvs()
|
||||
else:
|
||||
self.send_error(501, "Can only POST to viewcvs")
|
||||
|
||||
def is_viewcvs(self):
|
||||
"""Check whether self.path matches the hardcoded ScriptAlias
|
||||
/viewcvs"""
|
||||
if self.path[:8] == "/viewcvs":
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def redirect(self):
|
||||
"""redirect the browser to the viewcvs URL"""
|
||||
self.send_response(301, "moved (redirection follows)")
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Location", self.server.url + 'viewcvs/')
|
||||
self.end_headers()
|
||||
self.wfile.write("""<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="1; URL=%s">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Redirection to <a href="%s">ViewCVS</a></h1>
|
||||
Wait a second. You will be automatically redirected to <b>ViewCVS</b>.
|
||||
If this doesn't work, please click on the link above.
|
||||
</body>
|
||||
</html>
|
||||
""" % tuple([self.server.url + "viewcvs/"]*2))
|
||||
|
||||
def run_viewcvs(self):
|
||||
"""This is a quick and dirty cut'n'rape from Pythons
|
||||
standard library module CGIHTTPServer."""
|
||||
assert self.path[:8] == "/viewcvs"
|
||||
viewcvs_url, rest = self.server.url[:-1]+"/viewcvs", self.path[8:]
|
||||
i = string.rfind(rest, '?')
|
||||
if i >= 0:
|
||||
rest, query = rest[:i], rest[i+1:]
|
||||
else:
|
||||
query = ''
|
||||
i = string.find(rest, '/')
|
||||
if i >= 0:
|
||||
script, rest = rest[:i], rest[i:]
|
||||
else:
|
||||
script, rest = rest, ''
|
||||
scriptname = viewcvs_url + script
|
||||
# sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
|
||||
env = os.environ
|
||||
# Since we're going to modify the env in the parent, provide empty
|
||||
# values to override previously set values
|
||||
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
|
||||
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
|
||||
if env.has_key(k):
|
||||
env[k] = ""
|
||||
# XXX Much of the following could be prepared ahead of time!
|
||||
env['SERVER_SOFTWARE'] = self.version_string()
|
||||
env['SERVER_NAME'] = self.server.server_name
|
||||
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
||||
env['SERVER_PROTOCOL'] = self.protocol_version
|
||||
env['SERVER_PORT'] = str(self.server.server_port)
|
||||
env['REQUEST_METHOD'] = self.command
|
||||
uqrest = urllib.unquote(rest)
|
||||
env['PATH_INFO'] = uqrest
|
||||
env['SCRIPT_NAME'] = scriptname
|
||||
if query:
|
||||
env['QUERY_STRING'] = query
|
||||
host = self.address_string()
|
||||
if host != self.client_address[0]:
|
||||
env['REMOTE_HOST'] = host
|
||||
env['REMOTE_ADDR'] = self.client_address[0]
|
||||
# AUTH_TYPE
|
||||
# REMOTE_USER
|
||||
# REMOTE_IDENT
|
||||
if self.headers.typeheader is None:
|
||||
env['CONTENT_TYPE'] = self.headers.type
|
||||
else:
|
||||
env['CONTENT_TYPE'] = self.headers.typeheader
|
||||
length = self.headers.getheader('content-length')
|
||||
if length:
|
||||
env['CONTENT_LENGTH'] = length
|
||||
accept = []
|
||||
for line in self.headers.getallmatchingheaders('accept'):
|
||||
if line[:1] in string.whitespace:
|
||||
accept.append(string.strip(line))
|
||||
else:
|
||||
accept = accept + string.split(line[7:], ',')
|
||||
env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
|
||||
ua = self.headers.getheader('user-agent')
|
||||
if ua:
|
||||
env['HTTP_USER_AGENT'] = ua
|
||||
# XXX Other HTTP_* headers
|
||||
decoded_query = string.replace(query, '+', ' ')
|
||||
|
||||
self.send_response(200)
|
||||
# FIXME: I'm not sure about this: Sometimes it hurts, sometimes
|
||||
# it is required. Please enlight me
|
||||
if 1:
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
|
||||
# Preserve state, because we execute script in current process:
|
||||
save_argv = sys.argv
|
||||
save_stdin = sys.stdin
|
||||
save_stdout = sys.stdout
|
||||
save_stderr = sys.stderr
|
||||
# For external tools like enscript we also need to redirect
|
||||
# the real stdout file descriptor:
|
||||
save_realstdout = os.dup(1)
|
||||
try:
|
||||
try:
|
||||
sys.stdout = self.wfile
|
||||
os.close(1)
|
||||
assert os.dup(self.wfile.fileno()) == 1
|
||||
sys.stdin = self.rfile
|
||||
viewcvs.run_cgi()
|
||||
finally:
|
||||
sys.argv = save_argv
|
||||
sys.stdin = save_stdin
|
||||
sys.stdout.flush()
|
||||
os.close(1)
|
||||
assert os.dup(save_realstdout) == 1
|
||||
sys.stdout = save_stdout
|
||||
sys.stderr = save_stderr
|
||||
except SystemExit, status:
|
||||
self.log_error("ViewCVS exit status %s", str(status))
|
||||
else:
|
||||
self.log_error("ViewCVS exited ok")
|
||||
|
||||
class ViewCVS_Server(BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, port, callback):
|
||||
host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
|
||||
self.address = ('', port)
|
||||
self.url = 'http://%s:%d/' % (host, port)
|
||||
self.callback = callback
|
||||
BaseHTTPServer.HTTPServer.__init__(self, self.address,
|
||||
self.handler)
|
||||
|
||||
def serve_until_quit(self):
|
||||
self.quit = 0
|
||||
while not self.quit:
|
||||
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
|
||||
if rd:
|
||||
self.handle_request()
|
||||
|
||||
def server_activate(self):
|
||||
BaseHTTPServer.HTTPServer.server_activate(self)
|
||||
if self.callback:
|
||||
self.callback(self)
|
||||
|
||||
def server_bind(self):
|
||||
# set SO_REUSEADDR (if available on this platform)
|
||||
if hasattr(socket, 'SOL_SOCKET') \
|
||||
and hasattr(socket, 'SO_REUSEADDR'):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
BaseHTTPServer.HTTPServer.server_bind(self)
|
||||
|
||||
ViewCVS_Server.handler = ViewCVS_Handler
|
||||
|
||||
try:
|
||||
# XXX Move this code out of this function.
|
||||
# Early loading of configuration here. Used to
|
||||
# allow tinkering with some configuration settings:
|
||||
viewcvs.handle_config()
|
||||
if options.repository:
|
||||
if viewcvs.cfg.general.cvs_roots.has_key("Development"):
|
||||
viewcvs.cfg.general.cvs_roots["Development"] = options.repository
|
||||
else:
|
||||
sys.stderr.write("*** No default ViewCVS configuration. Edit viewcvs.conf\n")
|
||||
raise KeyboardInterrupt # Hack!
|
||||
elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \
|
||||
not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]):
|
||||
sys.stderr.write("*** No repository found. Please use the -r option.\n")
|
||||
sys.stderr.write(" Use --help for more info.\n")
|
||||
raise KeyboardInterrupt # Hack!
|
||||
os.close(0) # To avoid problems with shell job control
|
||||
ViewCVS_Server(port, callback).serve_until_quit()
|
||||
except (KeyboardInterrupt, select.error):
|
||||
pass
|
||||
print 'server stopped'
|
||||
|
||||
# --- graphical interface: --------------------------------------------------
|
||||
|
||||
def gui(port):
|
||||
"""Graphical interface (starts web server and pops up a control window)."""
|
||||
class GUI:
|
||||
def __init__(self, window, port):
|
||||
self.window = window
|
||||
self.server = None
|
||||
self.scanner = None
|
||||
|
||||
import Tkinter
|
||||
self.server_frm = Tkinter.Frame(window)
|
||||
self.title_lbl = Tkinter.Label(self.server_frm,
|
||||
text='Starting server...\n ')
|
||||
self.open_btn = Tkinter.Button(self.server_frm,
|
||||
text='open browser', command=self.open, state='disabled')
|
||||
self.quit_btn = Tkinter.Button(self.server_frm,
|
||||
text='quit serving', command=self.quit, state='disabled')
|
||||
|
||||
|
||||
self.window.title('ViewCVS standalone')
|
||||
self.window.protocol('WM_DELETE_WINDOW', self.quit)
|
||||
self.title_lbl.pack(side='top', fill='x')
|
||||
self.open_btn.pack(side='left', fill='x', expand=1)
|
||||
self.quit_btn.pack(side='right', fill='x', expand=1)
|
||||
|
||||
# Early loading of configuration here. Used to
|
||||
# allow tinkering with configuration settings through the gui:
|
||||
viewcvs.handle_config()
|
||||
if not LIBRARY_DIR:
|
||||
viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"
|
||||
|
||||
self.options_frm = Tkinter.Frame(window)
|
||||
|
||||
# cvsgraph toggle:
|
||||
self.cvsgraph_ivar = Tkinter.IntVar()
|
||||
self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph)
|
||||
self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
|
||||
command=self.toggle_use_cvsgraph)
|
||||
self.cvsgraph_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# enscript toggle:
|
||||
self.enscript_ivar = Tkinter.IntVar()
|
||||
self.enscript_ivar.set(viewcvs.cfg.options.use_enscript)
|
||||
self.enscript_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="enable enscript (needs binary)", var=self.enscript_ivar,
|
||||
command=self.toggle_use_enscript)
|
||||
self.enscript_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# show_subdir_lastmod toggle:
|
||||
self.subdirmod_ivar = Tkinter.IntVar()
|
||||
self.subdirmod_ivar.set(viewcvs.cfg.options.show_subdir_lastmod)
|
||||
self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
|
||||
command=self.toggle_subdirmod)
|
||||
self.subdirmod_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# use_re_search toggle:
|
||||
self.useresearch_ivar = Tkinter.IntVar()
|
||||
self.useresearch_ivar.set(viewcvs.cfg.options.use_re_search)
|
||||
self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="allow regular expr search", var=self.useresearch_ivar,
|
||||
command=self.toggle_useresearch)
|
||||
self.useresearch_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# directory view template:
|
||||
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
text='Chooose HTML Template for the Directory pages:')
|
||||
self.dirtemplate_lbl.pack(side='top', anchor='w')
|
||||
self.dirtemplate_svar = Tkinter.StringVar()
|
||||
self.dirtemplate_svar.set(viewcvs.cfg.templates.directory)
|
||||
self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.dirtemplate_svar)
|
||||
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
|
||||
self.dirtemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_dir = Tkinter.Radiobutton(self.options_frm,
|
||||
text="directory.ezt", value="templates/directory.ezt",
|
||||
var=self.dirtemplate_svar, command=self.set_templates_directory)
|
||||
self.templates_dir.pack(side='top', anchor='w')
|
||||
self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
|
||||
text="dir_alternate.ezt", value="templates/dir_alternate.ezt",
|
||||
var=self.dirtemplate_svar, command=self.set_templates_directory)
|
||||
self.templates_dir_alt.pack(side='top', anchor='w')
|
||||
|
||||
# log view template:
|
||||
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
text='Chooose HTML Template for the Log pages:')
|
||||
self.logtemplate_lbl.pack(side='top', anchor='w')
|
||||
self.logtemplate_svar = Tkinter.StringVar()
|
||||
self.logtemplate_svar.set(viewcvs.cfg.templates.log)
|
||||
self.logtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.logtemplate_svar)
|
||||
self.logtemplate_entry.bind('<Return>', self.set_templates_log)
|
||||
self.logtemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_log = Tkinter.Radiobutton(self.options_frm,
|
||||
text="log.ezt", value="templates/log.ezt",
|
||||
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||
self.templates_log.pack(side='top', anchor='w')
|
||||
self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
|
||||
text="log_table.ezt", value="templates/log_table.ezt",
|
||||
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||
self.templates_log_table.pack(side='top', anchor='w')
|
||||
|
||||
# query view template:
|
||||
self.querytemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
text='Template for the database query page:')
|
||||
self.querytemplate_lbl.pack(side='top', anchor='w')
|
||||
self.querytemplate_svar = Tkinter.StringVar()
|
||||
self.querytemplate_svar.set(viewcvs.cfg.templates.query)
|
||||
self.querytemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.querytemplate_svar)
|
||||
self.querytemplate_entry.bind('<Return>', self.set_templates_query)
|
||||
self.querytemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_query = Tkinter.Radiobutton(self.options_frm,
|
||||
text="query.ezt", value="templates/query.ezt",
|
||||
var=self.querytemplate_svar, command=self.set_templates_query)
|
||||
self.templates_query.pack(side='top', anchor='w')
|
||||
|
||||
# pack and set window manager hints:
|
||||
self.server_frm.pack(side='top', fill='x')
|
||||
self.options_frm.pack(side='top', fill='x')
|
||||
|
||||
self.window.update()
|
||||
self.minwidth = self.window.winfo_width()
|
||||
self.minheight = self.window.winfo_height()
|
||||
self.expanded = 0
|
||||
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
|
||||
self.window.wm_minsize(self.minwidth, self.minheight)
|
||||
|
||||
import threading
|
||||
threading.Thread(target=serve, args=(port, self.ready)).start()
|
||||
|
||||
def toggle_use_cvsgraph(self, event=None):
|
||||
viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
|
||||
|
||||
def toggle_use_enscript(self, event=None):
|
||||
viewcvs.cfg.options.use_enscript = self.enscript_ivar.get()
|
||||
|
||||
def toggle_subdirmod(self, event=None):
|
||||
viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()
|
||||
|
||||
def toggle_useresearch(self, event=None):
|
||||
viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get()
|
||||
|
||||
def set_templates_log(self, event=None):
|
||||
viewcvs.cfg.templates.log = self.logtemplate_svar.get()
|
||||
|
||||
def set_templates_directory(self, event=None):
|
||||
viewcvs.cfg.templates.directory = self.dirtemplate_svar.get()
|
||||
|
||||
def set_templates_query(self, event=None):
|
||||
viewcvs.cfg.templates.query = self.querytemplate_svar.get()
|
||||
|
||||
def ready(self, server):
|
||||
"""used as callback parameter to the serve() function"""
|
||||
self.server = server
|
||||
self.title_lbl.config(
|
||||
text='ViewCVS standalone server at\n' + server.url)
|
||||
self.open_btn.config(state='normal')
|
||||
self.quit_btn.config(state='normal')
|
||||
|
||||
def open(self, event=None, url=None):
|
||||
"""opens a browser window on the local machine"""
|
||||
url = url or self.server.url
|
||||
try:
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
except ImportError: # pre-webbrowser.py compatibility
|
||||
if sys.platform == 'win32':
|
||||
os.system('start "%s"' % url)
|
||||
elif sys.platform == 'mac':
|
||||
try:
|
||||
import ic
|
||||
ic.launchurl(url)
|
||||
except ImportError: pass
|
||||
else:
|
||||
rc = os.system('netscape -remote "openURL(%s)" &' % url)
|
||||
if rc: os.system('netscape "%s" &' % url)
|
||||
|
||||
def quit(self, event=None):
|
||||
if self.server:
|
||||
self.server.quit = 1
|
||||
self.window.quit()
|
||||
|
||||
import Tkinter
|
||||
try:
|
||||
gui = GUI(Tkinter.Tk(), port)
|
||||
Tkinter.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# --- command-line interface: ----------------------------------------------
|
||||
|
||||
def cli(argv):
|
||||
"""Command-line interface (looks at argv to decide what to do)."""
|
||||
import getopt
|
||||
class BadUsage(Exception): pass
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], 'gp:r:',
|
||||
['gui', 'port=', 'repository='])
|
||||
for opt, val in opts:
|
||||
if opt in ('-g', '--gui'):
|
||||
options.start_gui = 1
|
||||
elif opt in ('-r', '--repository'):
|
||||
options.repository = val
|
||||
elif opt in ('-p', '--port'):
|
||||
try:
|
||||
options.port = int(val)
|
||||
except ValueError:
|
||||
raise BadUsage
|
||||
if options.start_gui:
|
||||
gui(options.port)
|
||||
return
|
||||
elif options.port:
|
||||
def ready(server):
|
||||
print 'server ready at %s' % server.url
|
||||
serve(options.port, ready)
|
||||
return
|
||||
raise BadUsage
|
||||
except (getopt.error, BadUsage):
|
||||
cmd = sys.argv[0]
|
||||
port = options.port
|
||||
print """ViewCVS standalone - a simple standalone HTTP-Server
|
||||
|
||||
Usage: %(cmd)s [ <options> ]
|
||||
|
||||
Available Options:
|
||||
-p <port> or --port=<port>
|
||||
Start an HTTP server on the given port on the local machine.
|
||||
Default port is %(port)d.
|
||||
|
||||
-r <path> or --repository=<path>
|
||||
Specify another path for the default CVS repository "Development".
|
||||
If you don't have your repository at /home/cvsroot you will need to
|
||||
use this option or you have to install first and edit viewcvs.conf.
|
||||
|
||||
-g or --gui
|
||||
Pop up a graphical interface for serving and testing ViewCVS.
|
||||
|
||||
""" % locals()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path[:0] = ['lib']
|
||||
os.chdir('lib')
|
||||
import viewcvs
|
||||
import apache_icons
|
||||
options = Options()
|
||||
cli(sys.argv)
|
3
templates/annotate.ezt
Normal file
3
templates/annotate.ezt
Normal file
@@ -0,0 +1,3 @@
|
||||
[include "header.ezt" "annotate"]
|
||||
|
||||
<hr noshade>
|
88
templates/diff.ezt
Normal file
88
templates/diff.ezt
Normal file
@@ -0,0 +1,88 @@
|
||||
[include "header.ezt" "diff"]
|
||||
|
||||
<h3 align=center>Diff for /[where] between version [rev1] and [rev2]</h3>
|
||||
<table border=0 cellspacing=0 cellpadding=0 width="100%">
|
||||
<tr bgcolor=white>
|
||||
<th width="50%" valign=top>
|
||||
version [rev1][date1]
|
||||
[if-any tag1]<br>Tag: [tag1][end]
|
||||
</th>
|
||||
<th width="50%" valign=top>
|
||||
version [rev2][date2]
|
||||
[if-any tag2]<br>Tag: [tag2][end]
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
[for changes]
|
||||
[is changes.type "header"]
|
||||
<tr bgcolor="#99cccc"><td width="50%">
|
||||
<table width="100%" border=1 cellpadding=5><tr>
|
||||
<td><b>Line [changes.line1]</b> <font
|
||||
size="-1">[changes.extra]</font></td>
|
||||
</tr></table></td><td width="50%">
|
||||
<table width="100%" border=1 cellpadding=5><tr>
|
||||
<td><b>Line [changes.line2]</b> <font
|
||||
size="-1">[changes.extra]</font></td>
|
||||
</tr></table>
|
||||
</td></tr>
|
||||
[else]
|
||||
[is changes.type "add"]
|
||||
<tr>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
<td bgcolor="#aaffaa">[changes.right]</td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "remove"]
|
||||
<tr>
|
||||
<td bgcolor="#ffaaaa">[changes.left]</td>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "change"]
|
||||
<tr>
|
||||
[if-any changes.have_left]<td bgcolor="#ffff77">[changes.left]</td>
|
||||
[else]<td bgcolor="#eeee77"> </td>[end]
|
||||
[if-any changes.have_right]<td bgcolor="#ffff77">[changes.right]</td>
|
||||
[else]<td bgcolor="#eeee77"> </td>[end]
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "no-changes"]
|
||||
<tr><td colspan=2> </td></tr>
|
||||
<tr bgcolor="#cccccc"><td colspan=2 align=center>
|
||||
<br><b>- No changes -</b><br>
|
||||
</td></tr>
|
||||
[else][# a line of context]
|
||||
<tr><td>[changes.left]</td><td>[changes.right]</td></tr>
|
||||
[end][end][end][end][end]
|
||||
[end]
|
||||
|
||||
</table><br><hr noshade width="100%">
|
||||
<table border=0 cellpadding=10><tr><td>
|
||||
|
||||
<table border=1><tr><td>Legend:<br>
|
||||
<table border=0 cellspacing=0 cellpadding=1>
|
||||
<tr>
|
||||
<td align=center bgcolor="#ffaaaa">Removed from v.[rev1]</td>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
</tr>
|
||||
<tr bgcolor="#ffff77"><td align=center colspan=2>changed lines</td></tr>
|
||||
<tr>
|
||||
<td bgcolor="#cccccc"> </td>
|
||||
<td align=center bgcolor="#aaffaa">Added in v.[rev2]</td>
|
||||
</tr>
|
||||
</table></td></tr></table></td>
|
||||
|
||||
<td><form method="GET" action="[request.url]">
|
||||
[hidden_values]
|
||||
<select name="diff_format" onchange="submit()">
|
||||
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
<input type=submit value="Show">
|
||||
</form></td></tr>
|
||||
</table>
|
||||
|
||||
[include "footer.ezt"]
|
307
templates/dir_alternate.ezt
Normal file
307
templates/dir_alternate.ezt
Normal file
@@ -0,0 +1,307 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[if-any where][else]
|
||||
<!-- you may insert repository access instructions here -->
|
||||
|
||||
[if-any roots]
|
||||
<h3>Project Root</h3>
|
||||
<form method=GET action="./">
|
||||
<select name=cvsroot onchange="submit()">
|
||||
[for roots]
|
||||
[is roots current_root]
|
||||
<option selected>[roots]</option>
|
||||
[else]
|
||||
<option>[roots]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value="Go">
|
||||
</form>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<p><a name="dirlist"></a></p>
|
||||
|
||||
[if-any where]
|
||||
<table>
|
||||
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
|
||||
[if-any view_tag]
|
||||
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
|
||||
[end]
|
||||
[if-any search_re]
|
||||
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
|
||||
[end]
|
||||
[is num_files "0"]
|
||||
[else]
|
||||
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
|
||||
[end]
|
||||
</table>
|
||||
[end]
|
||||
|
||||
<hr noshade>
|
||||
|
||||
[# if you want a colored border around the table of directory
|
||||
information, then add this additional table tag:
|
||||
|
||||
<table border=0 cellpadding=0 width="100%"><tr>
|
||||
<td bgcolor="#999999">
|
||||
]
|
||||
|
||||
|
||||
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||
<tr>
|
||||
[if-any have_logs]
|
||||
[is sortby "rev"]
|
||||
<th align=left bgcolor="#88ff88">Rev.</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
|
||||
[end]
|
||||
[is sortby "file"]
|
||||
<th align=left bgcolor="#88ff88"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
>File</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
><a href="./[sortby_file_href]#dirlist">File</a></th>
|
||||
[end]
|
||||
[is sortby "date"]
|
||||
<th align=left bgcolor="#88ff88">Age</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_date_href]#dirlist">Age</a></th>
|
||||
[end]
|
||||
[is sortby "author"]
|
||||
<th align=left bgcolor="#88ff88">Author</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_author_href]#dirlist">Author</a></th>
|
||||
[end]
|
||||
[is sortby "log"]
|
||||
<th align=left bgcolor="#88ff88">Last log entry</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
|
||||
[end]
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc">File</th>
|
||||
[end]
|
||||
</tr>
|
||||
|
||||
[for rows]
|
||||
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
|
||||
[is rows.type "unreadable"]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[end]
|
||||
<td><a name="[rows.anchor]">[rows.name]</a>
|
||||
[if-any have_logs]
|
||||
</td>
|
||||
<td colspan=[is cfg.options.use_cvsgraph "1"]4[else]3[end]>
|
||||
<i>this entry is unreadable</i>
|
||||
</td>
|
||||
[else]
|
||||
- <i>this entry is unreadable</i>
|
||||
[end]
|
||||
</td>
|
||||
[else]
|
||||
[is rows.type "dir"]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[end]
|
||||
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
|
||||
<a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.name "Attic/"]
|
||||
<a href="./[show_attic_href]#dirlist">[[]show contents]</a>
|
||||
[end]
|
||||
</td>
|
||||
[is rows.cvs "error"]
|
||||
[# for an error to occur, we must have some logs. always use colspan]
|
||||
<td colspan=3>
|
||||
<i>Last modification unavailable - could not read CVS information</i>
|
||||
</td>
|
||||
[else]
|
||||
[if-any have_logs]
|
||||
[is rows.cvs "none"]
|
||||
<td> </td> [# age ]
|
||||
<td> </td> [# author ]
|
||||
<td> </td> [# log ]
|
||||
[else]
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log_file]/[rows.log_rev]<br>
|
||||
<font size="-1">[rows.log]</font></td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[else]
|
||||
[is rows.cvs "error"]
|
||||
<td> </td>
|
||||
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
|
||||
<a name="[rows.anchor]">[rows.name]</a>
|
||||
</td>
|
||||
<td colspan=3><i>CVS information is unreadable</i></td>
|
||||
[else]
|
||||
<td> <a href="[rows.href]"><b>[rows.rev]</b></a></td>
|
||||
<td width="1%"><a name="[rows.anchor]" href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
[# to display the revision in a separate window, you could use:
|
||||
|
||||
<a name="{rows.anchor}" href="{rows.rev_href}" target="cvs_checkout"
|
||||
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||
width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.state "dead"]
|
||||
[# don't let this phrase/link be wrapped ]
|
||||
[if-any view_tag](not exist)[else](in the Attic)[end] <a href="./[hide_attic_href]#dirlist">[[]hide]</a>
|
||||
[end]
|
||||
</td>
|
||||
[if-any rows.graph_href]
|
||||
<td width="1%"><a href="[rows.graph_href]"><img
|
||||
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
|
||||
alt="(graph)" width=16 height=16 border=0>
|
||||
</a></td>
|
||||
[end]
|
||||
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log]</td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
</table>
|
||||
|
||||
[# if you are using a table border (see above), then add:
|
||||
|
||||
</td></tr></table>
|
||||
]
|
||||
|
||||
[if-any no_match]
|
||||
<p><b>NOTE:</b> There are [num_files] files, but none match the
|
||||
current selection criteria.
|
||||
[end]
|
||||
|
||||
[if-any unreadable]
|
||||
<hr size=1 noshade>
|
||||
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
|
||||
repository should be readable by the web server process. Please
|
||||
report this condition to the administrator of this CVS repository.
|
||||
[end]
|
||||
|
||||
[if-any selection_form]
|
||||
<hr size=1 noshade>
|
||||
|
||||
[# this table holds the selectors on the left, and reset on the right ]
|
||||
<table><tr><td>
|
||||
|
||||
<form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
|
||||
<table>
|
||||
[if-any has_tags]
|
||||
<tr>
|
||||
<td>Show files using tag:</td>
|
||||
<td>
|
||||
<select name=only_with_tag onchange="submit()">
|
||||
[if-any branch_tags]
|
||||
<option value="">- Branches -</option>
|
||||
[for branch_tags]
|
||||
[is branch_tags view_tag]
|
||||
<option selected>[branch_tags]</option>
|
||||
[else]
|
||||
<option>[branch_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
<option value="">- Non-branch tags -</option>
|
||||
[for plain_tags]
|
||||
[is plain_tags view_tag]
|
||||
<option selected>[plain_tags]</option>
|
||||
[else]
|
||||
<option>[plain_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select></td>
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
[is cfg.options.use_re_search "1"]
|
||||
<tr>
|
||||
<td>Show files containing the regular expression:</td>
|
||||
<td><input type="text" name="search" value="[search_re]">
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
<tr><td> </td><td><input type="submit" value="Show"></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</td>
|
||||
|
||||
[if-any view_tag]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[else][if-any search_re]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[end][end]
|
||||
|
||||
</tr></table>
|
||||
[end]
|
||||
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
[if-any tarball_href]
|
||||
<a href="[tarball_href]">Download tarball</a>
|
||||
[end]
|
||||
|
||||
[include "footer.ezt"]
|
305
templates/directory.ezt
Normal file
305
templates/directory.ezt
Normal file
@@ -0,0 +1,305 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[if-any where][else]
|
||||
<!-- you may insert repository access instructions here -->
|
||||
|
||||
[if-any roots]
|
||||
<h3>Project Root</h3>
|
||||
<form method=GET action="./">
|
||||
<select name=cvsroot onchange="submit()">
|
||||
[for roots]
|
||||
[is roots current_root]
|
||||
<option selected>[roots]</option>
|
||||
[else]
|
||||
<option>[roots]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value="Go">
|
||||
</form>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<p><a name="dirlist"></a></p>
|
||||
|
||||
[if-any where]
|
||||
<table>
|
||||
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
|
||||
[if-any view_tag]
|
||||
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
|
||||
[end]
|
||||
[if-any search_re]
|
||||
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
|
||||
[end]
|
||||
[is num_files "0"]
|
||||
[else]
|
||||
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
|
||||
[end]
|
||||
</table>
|
||||
[end]
|
||||
|
||||
<hr noshade>
|
||||
|
||||
[# if you want a colored border around the table of directory
|
||||
information, then add this additional table tag:
|
||||
|
||||
<table border=0 cellpadding=0 width="100%"><tr>
|
||||
<td bgcolor="#999999">
|
||||
]
|
||||
|
||||
|
||||
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||
<tr>
|
||||
[if-any have_logs]
|
||||
[is sortby "file"]
|
||||
<th align=left bgcolor="#88ff88"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
>File</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
|
||||
><a href="./[sortby_file_href]#dirlist">File</a></th>
|
||||
[end]
|
||||
[is sortby "rev"]
|
||||
<th align=left bgcolor="#88ff88">Rev.</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
|
||||
[end]
|
||||
[is sortby "date"]
|
||||
<th align=left bgcolor="#88ff88">Age</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_date_href]#dirlist">Age</a></th>
|
||||
[end]
|
||||
[is sortby "author"]
|
||||
<th align=left bgcolor="#88ff88">Author</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_author_href]#dirlist">Author</a></th>
|
||||
[end]
|
||||
[is sortby "log"]
|
||||
<th align=left bgcolor="#88ff88">Last log entry</th>
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc"
|
||||
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
|
||||
[end]
|
||||
[else]
|
||||
<th align=left bgcolor="#cccccc">File</th>
|
||||
[end]
|
||||
</tr>
|
||||
|
||||
[for rows]
|
||||
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
|
||||
[is rows.type "unreadable"]
|
||||
<td><a name="[rows.anchor]">[rows.name]</a>
|
||||
[if-any have_logs]
|
||||
</td>
|
||||
<td colspan=[is cfg.options.use_cvsgraph "1"]5[else]4[end]>
|
||||
<i>this entry is unreadable</i>
|
||||
</td>
|
||||
[else]
|
||||
- <i>this entry is unreadable</i>
|
||||
[end]
|
||||
</td>
|
||||
[else]
|
||||
[is rows.type "dir"]
|
||||
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
|
||||
<a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.name "Attic/"]
|
||||
<a href="./[show_attic_href]#dirlist">[[]show contents]</a>
|
||||
[end]
|
||||
</td>
|
||||
[is rows.cvs "error"]
|
||||
[# for an error to occur, we must have some logs. always use colspan]
|
||||
<td colspan=4>
|
||||
<i>Last modification unavailable - could not read CVS information</i>
|
||||
</td>
|
||||
[else]
|
||||
[if-any have_logs]
|
||||
<td> </td> [# revision ]
|
||||
[is rows.cvs "none"]
|
||||
<td> </td> [# age ]
|
||||
<td> </td> [# author ]
|
||||
<td> </td> [# log ]
|
||||
[else]
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log_file]/[rows.log_rev]<br>
|
||||
<font size="-1">[rows.log]</font></td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[else]
|
||||
[is rows.cvs "error"]
|
||||
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
|
||||
<a name="[rows.anchor]">[rows.name]</a>
|
||||
</td>
|
||||
<td colspan=4><i>CVS information is unreadable</i></td>
|
||||
[else]
|
||||
<td><a name="[rows.anchor]" href="[rows.href]">
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0
|
||||
width=16 height=16>
|
||||
[rows.name]
|
||||
</a>
|
||||
[is rows.state "dead"]
|
||||
[# don't let this phrase/link be wrapped ]
|
||||
[if-any view_tag](not exist)[else](in the Attic)[end] <a href="./[hide_attic_href]#dirlist">[[]hide]</a>
|
||||
[end]
|
||||
</td>
|
||||
[if-any rows.graph_href]
|
||||
<td width="1%"><a href="[rows.graph_href]"><img
|
||||
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
|
||||
alt="(graph)" width=16 height=16 border=0>
|
||||
</a></td>
|
||||
[end]
|
||||
|
||||
<td>
|
||||
<a href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
|
||||
<b>[rows.rev]</b>
|
||||
</a>
|
||||
[# to display the revision in a separate window, you could use:
|
||||
|
||||
<a href="{rows.rev_href}" target="cvs_checkout"
|
||||
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
</td>
|
||||
<td> [rows.time]</td>
|
||||
[if-any rows.author]
|
||||
<td> [rows.author]</td>
|
||||
[end]
|
||||
[if-any rows.show_log]
|
||||
<td> [rows.log]</td>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
</table>
|
||||
|
||||
[# if you are using a table border (see above), then add:
|
||||
|
||||
</td></tr></table>
|
||||
]
|
||||
|
||||
[if-any no_match]
|
||||
<p><b>NOTE:</b> There are [num_files] files, but none match the
|
||||
current selection criteria.
|
||||
[end]
|
||||
|
||||
[if-any unreadable]
|
||||
<hr size=1 noshade>
|
||||
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
|
||||
repository should be readable by the web server process. Please
|
||||
report this condition to the administrator of this CVS repository.
|
||||
[end]
|
||||
|
||||
[if-any selection_form]
|
||||
<hr size=1 noshade>
|
||||
|
||||
[# this table holds the selectors on the left, and reset on the right ]
|
||||
<table><tr><td>
|
||||
|
||||
<form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
|
||||
<table>
|
||||
[if-any has_tags]
|
||||
<tr>
|
||||
<td>Show files using tag:</td>
|
||||
<td>
|
||||
<select name=only_with_tag onchange="submit()">
|
||||
[if-any branch_tags]
|
||||
<option value="">- Branches -</option>
|
||||
[for branch_tags]
|
||||
[is branch_tags view_tag]
|
||||
<option selected>[branch_tags]</option>
|
||||
[else]
|
||||
<option>[branch_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
<option value="">- Non-branch tags -</option>
|
||||
[for plain_tags]
|
||||
[is plain_tags view_tag]
|
||||
<option selected>[plain_tags]</option>
|
||||
[else]
|
||||
<option>[plain_tags]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select></td>
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
[is cfg.options.use_re_search "1"]
|
||||
<tr>
|
||||
<td>Show files containing the regular expression:</td>
|
||||
<td><input type="text" name="search" value="[search_re]">
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
<tr><td> </td><td><input type="submit" value="Show"></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</td>
|
||||
|
||||
[if-any view_tag]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[else][if-any search_re]
|
||||
<td valign=bottom><form method=GET action="./">
|
||||
[for params]
|
||||
<input type=hidden name="[params.name]" value="[params.value]">
|
||||
[end]
|
||||
<input type="submit" value="Show all files">
|
||||
</form></td>
|
||||
[end][end]
|
||||
|
||||
</tr></table>
|
||||
[end]
|
||||
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
[if-any tarball_href]
|
||||
<a href="[tarball_href]">Download tarball</a>
|
||||
[end]
|
||||
|
||||
[include "footer.ezt"]
|
9
templates/footer.ezt
Normal file
9
templates/footer.ezt
Normal file
@@ -0,0 +1,9 @@
|
||||
[# standard footer used by all ViewCVS pages ]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
|
||||
<td align=left><address>[cfg.general.address]</address></td>
|
||||
<td align=right>
|
||||
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
|
||||
</td></tr></table>
|
||||
</body></html>
|
12
templates/graph.ezt
Normal file
12
templates/graph.ezt
Normal file
@@ -0,0 +1,12 @@
|
||||
[include "header.ezt" "graph"]
|
||||
|
||||
<center>
|
||||
<h1>Revision graph of [request.where]</h1>
|
||||
|
||||
[imagemap]
|
||||
<img border="0" usemap="#MyMapName"
|
||||
src="[request.url]?graph=[rev]&makeimage=1[request.amp_query]"
|
||||
alt="Revisions of [request.where]">
|
||||
</center>
|
||||
|
||||
[include "footer.ezt"]
|
25
templates/header.ezt
Normal file
25
templates/header.ezt
Normal file
@@ -0,0 +1,25 @@
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
[### NOTE: the "diff" is the TITLE param to navigate_header() ]
|
||||
<title>[path]/[filename] - [arg0] - [rev]</title>
|
||||
</head>
|
||||
<body bgcolor="#eeeeee">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=1 bgcolor="#9999ee">
|
||||
<tr valign=bottom>
|
||||
<td>
|
||||
<a href="[file_url][qquery]#rev[rev]">
|
||||
<img src="/icons/small/back.gif" alt="(file)" border=0 width=16 height=16>
|
||||
</a>
|
||||
<b>Return to
|
||||
<a href="[file_url][qquery]#rev[rev]">[filename]</a>
|
||||
CVS log</b>
|
||||
<img src="/icons/small/text.gif" alt="(file)" border=0 width=16 height=16>
|
||||
</td>
|
||||
<td align=right>
|
||||
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
|
||||
<b>Up to [nav_path]</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
246
templates/log.ezt
Normal file
246
templates/log.ezt
Normal file
@@ -0,0 +1,246 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>CVS log for [where]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td rowspan=2><h1>CVS log for [where]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_log.html">Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||
width=16 height=16></a>
|
||||
<b>Up to [nav_path]</b><p>
|
||||
<a href="#diff">Request diff between arbitrary revisions</a>
|
||||
[if-any graph_href]
|
||||
/ <a href="[graph_href]">Display revisions graphically</a>
|
||||
[end]
|
||||
<hr noshade>
|
||||
|
||||
[if-any branch]
|
||||
Default branch: [branch]
|
||||
<br>
|
||||
Bookmark a link to:
|
||||
[if-any viewable]
|
||||
<a href="[head_href]"><b>HEAD</b></a>
|
||||
/
|
||||
(<a href="[head_abs_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>download</b></a>)
|
||||
[else]
|
||||
<a href="[head_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>HEAD</b></a>
|
||||
[end]
|
||||
[else]
|
||||
No default branch
|
||||
[end]
|
||||
<br>
|
||||
|
||||
[if-any view_tag]
|
||||
Current tag: [view_tag] <br>
|
||||
[end]
|
||||
|
||||
[for entries]
|
||||
<hr size=1 noshade>
|
||||
|
||||
[is entries.state "dead"]
|
||||
Revision <b>[entries.rev]</b>
|
||||
[else]
|
||||
<a name="rev[entries.rev]"></a>
|
||||
[for entries.tag_names]<a name="[entries.tag_names]"></a>
|
||||
[end]
|
||||
[for entries.branch_names]<a name="[entries.branch_names]"></a>
|
||||
[end]
|
||||
|
||||
Revision
|
||||
[if-any viewable]
|
||||
<a href="[entries.href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>[entries.rev]</b></a>
|
||||
[else]
|
||||
<a href="[entries.view_href]"><b>[entries.rev]</b></a>
|
||||
/
|
||||
(<a href="[entries.href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>download</b></a>)
|
||||
[end]
|
||||
[is mime_type "text/plain"]
|
||||
[else]
|
||||
/
|
||||
(<a href="[entries.text_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
|
||||
[end]
|
||||
[if-any viewable]
|
||||
/
|
||||
<a href="[entries.view_href]"><b>(view)</b></a>
|
||||
[end]
|
||||
|
||||
[# if you don't want to allow annotation, then remove this line]
|
||||
- <a href="[href]?annotate=[entries.rev][query]">annotate</a>
|
||||
|
||||
[# if you don't want to allow select for diffs then remove this section]
|
||||
[is entries.rev rev_selected]
|
||||
- <b>[[]selected]</b>
|
||||
[else]
|
||||
- <a href="[href]?r1=[entries.rev][query]">[[]select for diffs]</a>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.vendor_branch]
|
||||
<i>(vendor branch)</i>
|
||||
[end]
|
||||
|
||||
, <i>[entries.utc_date] UTC</i> ([entries.ago] ago) by <i>[entries.author]</i>
|
||||
|
||||
[if-any entries.branches]
|
||||
<br>Branch:
|
||||
[for entries.branches]
|
||||
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a>[if-index entries.branches last][else],[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.tags]
|
||||
<br>CVS Tags:
|
||||
[for entries.tags]
|
||||
<a href="[entries.tags.href]"><b>[entries.tags.name]</b></a>[if-index entries.tags last][else],[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.branch_points]
|
||||
<br>Branch point for:
|
||||
[for entries.branch_points]
|
||||
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a>[if-index entries.branch_points last][else],[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.prev]
|
||||
[if-any entries.changed]
|
||||
<br>Changes since <b>[entries.prev]: [entries.changed] lines</b>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[is entries.state "dead"]
|
||||
<br><b><i>FILE REMOVED</i></b>
|
||||
[else]
|
||||
[if-any entries.prev]
|
||||
<br>Diff to <a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]">previous [entries.prev]</a>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
(<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.branch_point]
|
||||
to <a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]">branch point [entries.branch_point]</a>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
(<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.next_main]
|
||||
to <a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]">next main [entries.next_main]</a>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
(<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.to_selected]
|
||||
[if-any entries.prev][else]<br>Diff[end]
|
||||
to <a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]">selected [rev_selected]</a>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
(<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<pre>[entries.html_log]</pre>
|
||||
[end]
|
||||
|
||||
<a name=diff></a>
|
||||
<hr noshade>
|
||||
This form allows you to request diffs between any two revisions of
|
||||
a file. You may select a symbolic revision name using the selection
|
||||
box or you may type in a numeric name using the type-in text box.
|
||||
<p>
|
||||
<form method="GET" action="[href].diff[qquery]" name="diff_select">
|
||||
|
||||
Diffs between
|
||||
<select name="r1">
|
||||
<option value="text" selected>Use Text Field</option>
|
||||
[for tags]
|
||||
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type="TEXT" size="12" name="tr1" value="[tr1]"
|
||||
onChange="document.diff_select.r1.selectedIndex=0">
|
||||
|
||||
and
|
||||
<select name="r2">
|
||||
<option value="text" selected>Use Text Field</option>
|
||||
[for tags]
|
||||
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type="TEXT" size="12" name="tr2" value="[tr2]"
|
||||
onChange="document.diff_select.r1.selectedIndex=0">
|
||||
|
||||
<br>Type of Diff should be a
|
||||
<select name="diff_format" onchange="submit()">
|
||||
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
|
||||
<input type=submit value=" Get Diffs "></form>
|
||||
<hr noshade>
|
||||
|
||||
[if-any branch_names]
|
||||
<a name=branch></a>
|
||||
<form method="GET" action="[href]">
|
||||
[hidden_values]
|
||||
View only Branch:
|
||||
<select name="only_with_tag" onchange="submit()">
|
||||
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
|
||||
[for branch_names]
|
||||
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value=" View Branch ">
|
||||
</form>
|
||||
[end]
|
||||
|
||||
<a name=logsort></a>
|
||||
<form method="GET" action="[href]">
|
||||
[hidden_values]
|
||||
Sort log by:
|
||||
<select name="logsort" onchange="submit()">
|
||||
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
|
||||
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
|
||||
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
|
||||
</select>
|
||||
<input type=submit value=" Sort ">
|
||||
</form>
|
||||
|
||||
[include "footer.ezt"]
|
251
templates/log_table.ezt
Normal file
251
templates/log_table.ezt
Normal file
@@ -0,0 +1,251 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>CVS log for [where]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td rowspan=2><h1>CVS log for [where]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right><h3><b><a target="_blank"
|
||||
href="[request.script_name]/*docroot*/help_logtable.html">Help</a></b></h3></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
|
||||
width=16 height=16></a>
|
||||
<b>Up to [nav_path]</b><p>
|
||||
<a href="#diff">Request diff between arbitrary revisions</a>
|
||||
[if-any graph_href]
|
||||
/ <a href="[graph_href]">Display revisions graphically</a>
|
||||
[end]
|
||||
<hr noshade>
|
||||
|
||||
[if-any branch]
|
||||
Default branch: [branch]
|
||||
<br>
|
||||
Bookmark a link to:
|
||||
[if-any viewable]
|
||||
<a href="[head_href]"><b>HEAD</b></a>
|
||||
/
|
||||
(<a href="[head_abs_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>download</b></a>)
|
||||
[else]
|
||||
<a href="[head_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
|
||||
><b>HEAD</b></a>
|
||||
[end]
|
||||
[else]
|
||||
No default branch
|
||||
[end]
|
||||
<br>
|
||||
|
||||
[if-any view_tag]
|
||||
Current tag: [view_tag] <br>
|
||||
[end]
|
||||
<hr noshade>
|
||||
<table width="100%" border=0 cellspacing=1 cellpadding=2>
|
||||
<th align=left bgcolor="#88ff88">Revision</th>
|
||||
<th align=left bgcolor="#88ff88">Tasks</th>
|
||||
<th align=left bgcolor="#88ff88">Diffs</th>
|
||||
<th align=left bgcolor="#88ff88">Branches and Tags</th>
|
||||
<th align=left bgcolor="#88ff88">Age</th>
|
||||
<th align=left bgcolor="#88ff88">Author</th>
|
||||
[for entries]
|
||||
<tr valign="top" bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
|
||||
|
||||
[# Revision column]
|
||||
<td rowspan=2>
|
||||
<b>[entries.rev]</b>
|
||||
<a name="rev[entries.rev]"></a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
[# Tasks column]
|
||||
<a href="[entries.href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,menubar=1,scrollbars=1[is mime_type "text/html"],status,
|
||||
toolbar[end]')"><b>Download</b></a><br>
|
||||
[is mime_type "text/plain"]
|
||||
[else]
|
||||
<a href="[entries.text_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>View as text</b></a><br>
|
||||
[end]
|
||||
[if-any viewable]
|
||||
<a href="[entries.view_href]"><b>View [entries.rev]</b></a><br>
|
||||
[end]
|
||||
|
||||
[# if you don't want to allow annotation, then remove this line]
|
||||
<a href="[href]?annotate=[entries.rev][query]"><b>View annotated</b></a><br>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
[# if you don't want to allow select for diffs then remove this section]
|
||||
[is entries.rev rev_selected]
|
||||
<b>[[]selected]</b><br>
|
||||
[else]
|
||||
<a href="[href]?r1=[entries.rev][query]"><b>[[]select for diffs]</b></a><br>
|
||||
[end]
|
||||
|
||||
[is entries.state "dead"]
|
||||
<b><i>FILE REMOVED</i></b>
|
||||
[else]
|
||||
[if-any entries.prev]
|
||||
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]"><b>Diff to previous [entries.prev]</b></a><br>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
[if-any entries.to_selected]
|
||||
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]"><b>Diff to selected [rev_selected]</b></a><br>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
</td>
|
||||
|
||||
<td>
|
||||
[# Branches column]
|
||||
[if-any entries.vendor_branch]
|
||||
<i>vendor branch</i><br>
|
||||
[end]
|
||||
[if-any entries.branches]
|
||||
[for entries.branches]
|
||||
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
[if-any entries.branch_points]
|
||||
Branch point for:
|
||||
[for entries.branch_points]
|
||||
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
[if-any entries.next_main]
|
||||
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]"><b>Diff to next MAIN [entries.next_main]</b></a><br>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
[if-any entries.branch_point]
|
||||
<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]"><b>Diff to branch point [entries.branch_point]</b></a><br>
|
||||
[if-any human_readable]
|
||||
[else]
|
||||
<a href="[href].diff?r1=[entries.branches.name]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[# Tags ]
|
||||
[if-any entries.tags]
|
||||
<form action="[href]" >
|
||||
<select name="only_with_tag" onChange="submit()">
|
||||
<option value="" [is view_tag ""]selected[end]>Show all tags</option>
|
||||
[for entries.tags]
|
||||
<option [is view_tag entries.tags.name]selected[end]>[entries.tags.name]</option>
|
||||
[end]
|
||||
</select>
|
||||
</form>
|
||||
[else]
|
||||
[end]
|
||||
</td>
|
||||
|
||||
[# Time column]
|
||||
<td>
|
||||
[entries.ago] ago<br><i>[entries.utc_date]</i>
|
||||
</td>
|
||||
|
||||
[# Author column]
|
||||
<td>
|
||||
[entries.author]
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
|
||||
<td colspan=5><b>Log: </b><i><pre>[entries.html_log]</pre></i></td>
|
||||
</tr>
|
||||
|
||||
[end]
|
||||
</table>
|
||||
|
||||
<a name=diff></a>
|
||||
<hr noshade>
|
||||
This form allows you to request diffs between any two revisions of
|
||||
a file. You may select a symbolic revision name using the selection
|
||||
box or you may type in a numeric name using the type-in text box.
|
||||
<p>
|
||||
<form method="GET" action="[href].diff[qquery]" name="diff_select">
|
||||
|
||||
Diffs between
|
||||
<select name="r1">
|
||||
<option value="text" selected>Use Text Field</option>
|
||||
[for tags]
|
||||
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type="TEXT" size="12" name="tr1" value="[tr1]"
|
||||
onChange="document.diff_select.r1.selectedIndex=0">
|
||||
|
||||
and
|
||||
<select name="r2">
|
||||
<option value="text" selected>Use Text Field</option>
|
||||
[for tags]
|
||||
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type="TEXT" size="12" name="tr2" value="[tr2]"
|
||||
onChange="document.diff_select.r1.selectedIndex=0">
|
||||
|
||||
<br>Type of Diff should be a
|
||||
<select name="diff_format" onchange="submit()">
|
||||
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
|
||||
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
|
||||
</select>
|
||||
|
||||
<input type=submit value=" Get Diffs "></form>
|
||||
<hr noshade>
|
||||
|
||||
[if-any branch_names]
|
||||
<a name=branch></a>
|
||||
<form method="GET" action="[href]">
|
||||
[hidden_values]
|
||||
View only Branch:
|
||||
<select name="only_with_tag" onchange="submit()">
|
||||
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
|
||||
[for branch_names]
|
||||
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type=submit value=" View Branch ">
|
||||
</form>
|
||||
[end]
|
||||
|
||||
<a name=logsort></a>
|
||||
<form method="GET" action="[href]">
|
||||
[hidden_values]
|
||||
Sort log by:
|
||||
<select name="logsort" onchange="submit()">
|
||||
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
|
||||
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
|
||||
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
|
||||
</select>
|
||||
<input type=submit value=" Sort ">
|
||||
</form>
|
||||
|
||||
[include "footer.ezt"]
|
49
templates/markup.ezt
Normal file
49
templates/markup.ezt
Normal file
@@ -0,0 +1,49 @@
|
||||
[include "header.ezt" "view"]
|
||||
|
||||
<hr noshade>
|
||||
<table width="100%"><tr><td bgcolor="#ffffff">
|
||||
File: [nav_file]
|
||||
|
||||
(<a href="[href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank','cvs_checkout',
|
||||
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]');"
|
||||
><b>download</b></a>)
|
||||
[is mime_type "text/plain"]
|
||||
[else]
|
||||
/
|
||||
(<a href="[text_href]" target="cvs_checkout"
|
||||
onClick="window.open('about:blank', 'cvs_checkout',
|
||||
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
|
||||
[end]
|
||||
<br>
|
||||
|
||||
[if-any log]
|
||||
Revision: <b>[rev]</b>[if-any vendor_branch] <i>(vendor branch)</i>[end],
|
||||
<i>[utc_date] UTC</i> ([ago] ago) by <i>[author]</i>
|
||||
|
||||
[if-any branches]
|
||||
<br>Branch: <b>[branches]</b>
|
||||
[end]
|
||||
[if-any tags]
|
||||
<br>CVS Tags: <b>[tags]</b>
|
||||
[end]
|
||||
[if-any branch_points]
|
||||
<br>Branch point for: <b>[branch_points]</b>
|
||||
[end]
|
||||
[if-any prev]
|
||||
[if-any changed]
|
||||
<br>Changes since <b>[prev]: [changed] lines</b>
|
||||
[end]
|
||||
[end]
|
||||
[is state "dead"]
|
||||
<br><b><i>FILE REMOVED</i></b>
|
||||
[end]
|
||||
<pre>[log]</pre>
|
||||
[else]
|
||||
Revision: <b>[rev]</b><br>
|
||||
[if-any tag]
|
||||
Tag: <b>[tag]</b><br>
|
||||
[end]
|
||||
[end]
|
||||
</td></tr></table>
|
||||
<hr noshade>
|
228
templates/query.ezt
Normal file
228
templates/query.ezt
Normal file
@@ -0,0 +1,228 @@
|
||||
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
|
||||
by Greg Stein -- mailto:gstein@lyra.org
|
||||
-->
|
||||
<title>[cfg.general.main_title]</title>
|
||||
</head>
|
||||
<body text="#000000" bgcolor="#ffffff">
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td><h1>[cfg.general.main_title]</h1></td>
|
||||
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
|
||||
width=259 height=32></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Select your parameters for querying the CVS commit database. You
|
||||
can search for multiple matches by typing a comma-seperated list
|
||||
into the text fields. Regular expressions, and wildcards are also
|
||||
supported. Blank text input fields are treated as wildcards.
|
||||
</p>
|
||||
<p>
|
||||
Any of the text entry fields can take a comma-seperated list of
|
||||
search arguments. For example, to search for all commits from
|
||||
authors <i>jpaint</i> and <i>gstein</i>, just type: <b>jpaint,
|
||||
gstein</b> in the <i>Author</i> input box. If you are searching
|
||||
for items containing spaces or quotes, you will need to quote your
|
||||
request. For example, the same search above with quotes is:
|
||||
<b>"jpaint", "gstein"</b>.
|
||||
</p>
|
||||
<p>
|
||||
|
||||
Wildcard and regular expression searches are entered in a similar
|
||||
way to the quoted requests. You must quote any wildcard or
|
||||
regular expression request, and a command charactor preceeds the
|
||||
first quote. The command charactor <b>l</b> is for wildcard
|
||||
searches, and the wildcard charactor is a percent (<b>%</b>). The
|
||||
command charactor for regular expressions is <b>r</b>, and is
|
||||
passed directly to MySQL, so you'll need to refer to the MySQL
|
||||
manual for the exact regex syntax. It is very similar to Perl. A
|
||||
wildard search for all files with a <i>.py</i> extention is:
|
||||
<b>l"%.py"</b> in the <i>File</i> input box. The same search done
|
||||
with a regular expression is: <b>r".*\.py"</b>.
|
||||
</p>
|
||||
<p>
|
||||
All search types can be mixed, as long as they are seperated by
|
||||
commas.
|
||||
</p>
|
||||
|
||||
<form method=get action="query.cgi">
|
||||
|
||||
<table border=0 cellspacing=0 cellpadding=2 width=100% bgcolor=e6e6e6>
|
||||
<tr>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td valign=top>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align=right>CVS Repository:</td>
|
||||
<td>
|
||||
<input type=text name=repository size=40 value="[repository]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right>CVS Branch:</td>
|
||||
<td>
|
||||
<input type=text name=branch size=40 value="[branch]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right>Directory:</td>
|
||||
<td>
|
||||
<input type=text name=directory size=40 value="[directory]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right>File:</td>
|
||||
<td>
|
||||
<input type=text name=file size=40 value="[file]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=right>Author:</td>
|
||||
<td>
|
||||
<input type=text name=who size=40 value="[who]">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
<td valign=top>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align=left>Sort By:</td>
|
||||
<td>
|
||||
<select name=sortby>
|
||||
<option value=date [is sortby "date"]selected[end]>Date</option>
|
||||
<option value=author [is sortby "author"]selected[end]>Author</option>
|
||||
<option value=file [is sortby "file"]selected[end]>File</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan=2>
|
||||
<table border=0 cellspacing=0 cellpadding=0>
|
||||
<tr>
|
||||
<td>Date:</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type=radio name=date value=hours
|
||||
[is date "hours"]checked[end]></td>
|
||||
<td>In the last
|
||||
<input type=text name=hours value=[hours] size=4>hours
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type=radio name=date value=day
|
||||
[is date "day"]checked[end]></td>
|
||||
<td>In the last day</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type=radio name=date value=week
|
||||
[is date "week"]checked[end]></td>
|
||||
<td>In the last week</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type=radio name=date value=month
|
||||
[is date "month"]checked[end]></td>
|
||||
<td>In the last month</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type=radio name=date value=all
|
||||
[is date "all"]checked[end]></td>
|
||||
<td>Since the beginning of time</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<input type=submit value="Search">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
|
||||
[is query "skipped"]
|
||||
[else]
|
||||
<p><b>[num_commits]</b> matches found.</p>
|
||||
|
||||
[if-any commits]
|
||||
<table width="100%" border=0 cellspacing=0 cellpadding=2>
|
||||
<tr bgcolor="#88ff88">
|
||||
<th align=left valign=top>Revision</th>
|
||||
<th align=left valign=top>File</th>
|
||||
<th align=left valign=top>Branch</th>
|
||||
<th align=left valign=top>+/-</th>
|
||||
<th align=left valign=top>Date</th>
|
||||
<th align=left valign=top>Author</th>
|
||||
[# uncommment, if you want a separate Description column: (also see below)
|
||||
<th align=left valign=top>Description</th>
|
||||
]
|
||||
</tr>
|
||||
[for commits]
|
||||
[for commits.files]
|
||||
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
|
||||
<td align=left valign=top>
|
||||
[if-any commits.files.rev][commits.files.rev][else] [end]
|
||||
</td>
|
||||
<td align=left valign=top>[commits.files.link]</td>
|
||||
<td align=left valign=top>
|
||||
[if-any commits.files.branch][commits.files.branch][else] [end]
|
||||
</td>
|
||||
<td align=left valign=top>[commits.files.plus]/[commits.files.minus]</td>
|
||||
<td align=left valign=top>
|
||||
[if-any commits.files.date][commits.files.date][else] [end]
|
||||
</td>
|
||||
<td align=left valign=top>
|
||||
[if-any commits.files.author][commits.files.author][else] [end]
|
||||
</td>
|
||||
|
||||
[# uncommment, if you want a separate Description column:
|
||||
{if-index commits.files first{
|
||||
<td align=left valign=top rowspan={commits.num_files}>
|
||||
{commits.desc}
|
||||
</td>
|
||||
{end}
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
</tr>
|
||||
[# and also take the following out in the "Description column"-case:]
|
||||
[if-index commits.files last]
|
||||
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
|
||||
<td> </td>
|
||||
<td colspan=5><b>Log:</b><br>
|
||||
<pre>[commits.desc]</pre></td>
|
||||
</tr>
|
||||
[end]
|
||||
[# ---]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<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>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[include "footer.ezt"]
|
139
tests/timelog.py
Normal file
139
tests/timelog.py
Normal file
@@ -0,0 +1,139 @@
|
||||
|
||||
import time
|
||||
import string
|
||||
import profile
|
||||
|
||||
import rcsparse
|
||||
import viewcvs
|
||||
|
||||
def lines_changed(delta):
|
||||
idx = 0
|
||||
added = deleted = 0
|
||||
while idx < len(delta):
|
||||
op = delta[idx]
|
||||
i = string.find(delta, ' ', idx + 1)
|
||||
j = string.find(delta, '\n', i + 1)
|
||||
line = int(delta[idx+1:i])
|
||||
count = int(delta[i+1:j])
|
||||
idx = j + 1
|
||||
if op == 'd':
|
||||
deleted = deleted + count
|
||||
else: # 'a' for adding text
|
||||
added = added + count
|
||||
# skip new text
|
||||
while count > 0:
|
||||
nl = string.find(delta, '\n', idx)
|
||||
assert nl > 0, 'missing a newline in the delta in the RCS file'
|
||||
idx = nl + 1
|
||||
count = count - 1
|
||||
return added, deleted
|
||||
|
||||
class FetchSink(rcsparse.Sink):
|
||||
def __init__(self, which_rev=None):
|
||||
self.head = self.branch = ''
|
||||
self.tags = { }
|
||||
self.meta = { }
|
||||
self.revs = [ ]
|
||||
self.base = { }
|
||||
self.entries = { }
|
||||
self.which = which_rev
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head = revision
|
||||
|
||||
def set_principal_branch(self, branch_name):
|
||||
self.branch = branch_name
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
self.tags[name] = revision
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
self.meta[revision] = (timestamp, author, state)
|
||||
self.base[next] = revision
|
||||
for b in branches:
|
||||
self.base[b] = revision
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
timestamp, author, state = self.meta[revision]
|
||||
entry = viewcvs.LogEntry(revision, int(timestamp) - time.timezone, author,
|
||||
state, None, log)
|
||||
|
||||
# .revs is "order seen" and .entries is for random access
|
||||
self.revs.append(entry)
|
||||
self.entries[revision] = entry
|
||||
|
||||
if revision != self.head:
|
||||
added, deleted = lines_changed(text)
|
||||
if string.count(revision, '.') == 1:
|
||||
# on the trunk. reverse delta.
|
||||
changed = '+%d -%d' % (deleted, added)
|
||||
self.entries[self.base[revision]].changed = changed
|
||||
else:
|
||||
# on a branch. forward delta.
|
||||
changed = '+%d -%d' % (added, deleted)
|
||||
self.entries[revision].changed = changed
|
||||
|
||||
def parse_completed(self):
|
||||
if self.which:
|
||||
self.revs = [ self.entries[self.which] ]
|
||||
|
||||
def fetch_log2(full_name, which_rev=None):
|
||||
sink = FetchSink(which_rev)
|
||||
rcsparse.Parser().parse(open(full_name), sink)
|
||||
return sink.head, sink.branch, sink.tags, sink.revs
|
||||
|
||||
def compare_fetch(full_name, which_rev=None):
|
||||
d1 = viewcvs.fetch_log(full_name, which_rev)
|
||||
d2 = fetch_log2(full_name, which_rev)
|
||||
if d1[:3] != d2[:3]:
|
||||
print 'd1:', d1[:3]
|
||||
print 'd2:', d2[:3]
|
||||
return
|
||||
if len(d1[3]) != len(d2[3]):
|
||||
print 'len(d1[3])=%d len(d2[3])=%d' % (len(d1[3]), len(d2[3]))
|
||||
return
|
||||
def sort_func(e, f):
|
||||
return cmp(e.rev, f.rev)
|
||||
d1[3].sort(sort_func)
|
||||
d2[3].sort(sort_func)
|
||||
import pprint
|
||||
for i in range(len(d1[3])):
|
||||
if vars(d1[3][i]) != vars(d2[3][i]):
|
||||
pprint.pprint((i, vars(d1[3][i]), vars(d2[3][i])))
|
||||
|
||||
def time_fetch(full_name, which_rev=None):
|
||||
t = time.time()
|
||||
viewcvs.fetch_log(full_name, which_rev)
|
||||
t1 = time.time() - t
|
||||
t = time.time()
|
||||
fetch_log2(full_name, which_rev)
|
||||
t2 = time.time() - t
|
||||
print t1, t2
|
||||
|
||||
def profile_fetch(full_name, which_rev=None):
|
||||
p = profile.Profile()
|
||||
def many_calls(*args):
|
||||
for i in xrange(10):
|
||||
apply(fetch_log2, args)
|
||||
p.runcall(many_calls, full_name, which_rev)
|
||||
p.print_stats()
|
||||
|
||||
def varysize(full_name, which_rev=None):
|
||||
def one_run(n, *args):
|
||||
rcsparse._TokenStream.CHUNK_SIZE = n
|
||||
t = time.time()
|
||||
for i in xrange(5):
|
||||
apply(fetch_log2, args)
|
||||
print n, time.time() - t
|
||||
|
||||
#one_run(2020, full_name, which_rev)
|
||||
#one_run(4070, full_name, which_rev)
|
||||
#one_run(8170, full_name, which_rev)
|
||||
#one_run(8192, full_name, which_rev)
|
||||
#one_run(16384, full_name, which_rev)
|
||||
one_run(32740, full_name, which_rev)
|
||||
one_run(65500, full_name, which_rev)
|
||||
one_run(100000, full_name, which_rev)
|
||||
one_run(200000, full_name, which_rev)
|
||||
one_run(500000, full_name, which_rev)
|
116
tools/bin2inline_py.py
Executable file
116
tools/bin2inline_py.py
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
## vim:ts=4:et:nowrap
|
||||
# [Emacs: -*- python -*-]
|
||||
"""bin2inline_py.py -- creates Python source from directories containing images.
|
||||
|
||||
This is a very simple tool to pack a lot of small icons into a single file.
|
||||
|
||||
This version is a quick rape-and-past for the ViewCVS project.
|
||||
Run this script from within the tools subdirectory to recreate the source file
|
||||
'../lib/apache_icons.py'.
|
||||
|
||||
"""
|
||||
|
||||
import sys, os, string, fnmatch
|
||||
|
||||
|
||||
PREAMBLE="""#! /usr/bin/env python
|
||||
# This file was automatically generated! DO NOT EDIT!
|
||||
# Howto regenerate: see ../tools/bin2inline.py
|
||||
# $Id$
|
||||
# You have been warned. But if you want to edit, go ahead using your
|
||||
# favorite editor :-)
|
||||
## vim:ts=4:et:nowrap
|
||||
# [Emacs: -*- python -*-]
|
||||
|
||||
_ap_icons = """
|
||||
|
||||
def encodefile(filename):
|
||||
"""returns the binary content of 'filename' as string"""
|
||||
s = open(filename, 'rb').read()
|
||||
result = [ ]
|
||||
while s:
|
||||
result.append(repr(s[:16]))
|
||||
s = s[16:]
|
||||
return string.join(result, '\n ')
|
||||
|
||||
class Encode:
|
||||
"""Starting at a given directory find all files matching a certain
|
||||
filename pattern in this subtree, encode them as base64 strings and
|
||||
return a Python language dictionary with the filenames as keys and
|
||||
the files contents as values.
|
||||
"""
|
||||
def __init__(self, startdir=os.curdir, fn_pattern="*.gif"):
|
||||
self.startdir = startdir
|
||||
self.fn_pattern = fn_pattern
|
||||
self.walk()
|
||||
|
||||
def walk(self):
|
||||
"""walk through the subtree starting at self.startdir"""
|
||||
self.result = ['{\n']
|
||||
os.path.walk(self.startdir, self.visitor, None)
|
||||
self.result.append('}\n')
|
||||
|
||||
def visitor(self, dummy, dirname, filenames):
|
||||
"""A visitor compatible with os.path.walk()."""
|
||||
for candidate in filenames:
|
||||
pathname = os.path.join(dirname, candidate)
|
||||
if not os.path.isfile(pathname):
|
||||
continue
|
||||
if self.match(pathname):
|
||||
self.put_item(pathname)
|
||||
|
||||
def match(self, candidate):
|
||||
"""should return false, if pathname 'candidate' should be skipped.
|
||||
"""
|
||||
return fnmatch.fnmatch(candidate, self.fn_pattern)
|
||||
|
||||
def put_item(self, pathname):
|
||||
self.result.append(' "%s" :\n %s,\n\n'
|
||||
% (self.compute_key(pathname),
|
||||
encodefile(pathname)))
|
||||
|
||||
def compute_key(self, pathname):
|
||||
"""computes the dictionary key. Tkinter compatible"""
|
||||
return os.path.splitext(pathname)[0]
|
||||
|
||||
def __str__(self):
|
||||
return string.join(self.result, '')
|
||||
|
||||
#
|
||||
# The remainder of this module is best described as hack, run and throw away
|
||||
# software. You may want to edit it, if you want to other icons.
|
||||
#
|
||||
class WebserverIconEncode(Encode):
|
||||
minimal_list = [ # List of icons actually used by ViewCVS as of 2001-11-17
|
||||
"/icons/apache_pb.gif",
|
||||
"/icons/small/back.gif",
|
||||
"/icons/small/dir.gif",
|
||||
"/icons/small/text.gif",
|
||||
]
|
||||
def match(self, candidate):
|
||||
return self.compute_key(candidate) in self.minimal_list
|
||||
|
||||
def compute_key(self, pathname):
|
||||
l = len(self.startdir)
|
||||
if pathname[:l] == self.startdir:
|
||||
return pathname[l:]
|
||||
return pathname
|
||||
|
||||
POSTAMBLE="""
|
||||
|
||||
def serve_icon(pathname, fp):
|
||||
if _ap_icons.has_key(pathname):
|
||||
fp.write(_ap_icons[pathname])
|
||||
else:
|
||||
raise OSError # icon not found
|
||||
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
f = open("../lib/apache_icons.py", "wt")
|
||||
f.write(PREAMBLE)
|
||||
f.write(str(WebserverIconEncode(startdir="/usr/local/httpd")))
|
||||
f.write(POSTAMBLE)
|
163
tools/cvsdbadmin
Executable file
163
tools/cvsdbadmin
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# administrative program for CVSdb; this is primarily
|
||||
# used to add/rebuild CVS repositories to the database
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# These values will be set during the installation process. During
|
||||
# development, they will remain None.
|
||||
#
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
# Adjust sys.path to include our library directory
|
||||
import sys
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path[:0] = ['../lib'] # any other places to look?
|
||||
|
||||
#########################################################################
|
||||
|
||||
import os
|
||||
import string
|
||||
import cvsdb
|
||||
|
||||
|
||||
def UpdateFile(db, repository, path):
|
||||
try:
|
||||
commit_list = cvsdb.GetUnrecordedCommitList(repository, path)
|
||||
except cvsdb.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 = cvsdb.ConnectDatabase()
|
||||
repository = sys.argv[2]
|
||||
RecurseUpdate(db, repository, repository)
|
||||
|
||||
|
||||
def RebuildFile(db, repository, path):
|
||||
try:
|
||||
commit_list = cvsdb.GetCommitListFromRCSFile(repository, path)
|
||||
except cvsdb.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 = cvsdb.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 -*-
|
||||
#
|
||||
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://www.lyra.org/viewcvs/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# updates SQL database with new commit records
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# INSTALL-TIME CONFIGURATION
|
||||
#
|
||||
# These values will be set during the installation process. During
|
||||
# development, they will remain None.
|
||||
#
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
# Adjust sys.path to include our library directory
|
||||
import sys
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path[:0] = ['../lib'] # any other places to look?
|
||||
|
||||
#########################################################################
|
||||
|
||||
import os
|
||||
import string
|
||||
import getopt
|
||||
import re
|
||||
import cvsdb
|
||||
import rlog
|
||||
import config
|
||||
|
||||
|
||||
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:
|
||||
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.ctype = "added"
|
||||
elif self.new_version == 'NONE':
|
||||
self.ctype = "removed"
|
||||
else:
|
||||
self.ctype = "changed"
|
||||
|
||||
def CommitFromFileData(cfg, repository, file_data):
|
||||
## construct 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 = rlog.GetRLogData(cfg, filename, file_data.new_version)
|
||||
commit_list = cvsdb.RLogDataToCommitList(repository, rlog_data)
|
||||
commit = commit_list[0]
|
||||
|
||||
## set the type of commit from the file_data setting
|
||||
if file_data.ctype == "changed":
|
||||
commit.SetTypeChange()
|
||||
elif file_data.ctype == "added":
|
||||
commit.SetTypeAdd()
|
||||
elif file_data.ctype == "removed":
|
||||
commit.SetTypeRemove()
|
||||
|
||||
return commit
|
||||
|
||||
def GetUnrecordedCommitList(repository, file_data):
|
||||
filename = os.path.join(repository, file_data.directory, file_data.file)
|
||||
return cvsdb.GetUnrecordedCommitList(repository, filename)
|
||||
|
||||
def ProcessLoginfo(repository, stdin_list):
|
||||
## XXX This is a somewhat dirty hack:
|
||||
## cvsdb already loads the configuration file and provides a cfg
|
||||
## instance. Pick it from there in order to be able to pass it down
|
||||
## to rlog (see below).
|
||||
cfg = cvsdb.cfg
|
||||
|
||||
## 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')
|
||||
return
|
||||
|
||||
## 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.ctype == "removed":
|
||||
temp = GetUnrecordedCommitList(repository, file_data)
|
||||
commit_list = commit_list + temp
|
||||
else:
|
||||
commit_list.append(CommitFromFileData(cfg, repository, file_data))
|
||||
|
||||
## add to the database
|
||||
db = cvsdb.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)
|
150
tools/make-database
Executable file
150
tools/make-database
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# administrative program for CVSdb; creates a clean database in
|
||||
# MySQL 3.22 or later
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import os, sys, string
|
||||
import popen2
|
||||
|
||||
INTRO_TEXT = """\
|
||||
This script creates the database and tables in MySQL used by the ViewCVS
|
||||
checkin database. You will be prompted for: database user, database user
|
||||
password, and database name. This script will use mysql to create the
|
||||
database for you. You will then need to set the appropriate parameters
|
||||
in your viewcvs.conf file under the [cvsdb] section.
|
||||
"""
|
||||
|
||||
DATABASE_SCRIPT="""\
|
||||
DROP DATABASE IF EXISTS <dbname>;
|
||||
CREATE DATABASE <dbname>;
|
||||
|
||||
USE <dbname>;
|
||||
|
||||
DROP TABLE IF EXISTS branches;
|
||||
CREATE TABLE branches (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
branch varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE branch (branch)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS checkins;
|
||||
CREATE TABLE checkins (
|
||||
type enum('Change','Add','Remove'),
|
||||
ci_when datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
whoid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
||||
stickytag varchar(255) binary DEFAULT '' NOT NULL,
|
||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
addedlines int(11) DEFAULT '0' NOT NULL,
|
||||
removedlines int(11) DEFAULT '0' NOT NULL,
|
||||
descid mediumint(9),
|
||||
UNIQUE repositoryid (repositoryid,dirid,fileid,revision),
|
||||
KEY ci_when (ci_when),
|
||||
KEY whoid (whoid),
|
||||
KEY repositoryid_2 (repositoryid),
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS descs;
|
||||
CREATE TABLE descs (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
description text,
|
||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY hash (hash)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS dirs;
|
||||
CREATE TABLE dirs (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
dir varchar(128) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE dir (dir)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS files;
|
||||
CREATE TABLE files (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
file varchar(128) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE file (file)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS people;
|
||||
CREATE TABLE people (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
who varchar(32) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE who (who)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS repositories;
|
||||
CREATE TABLE repositories (
|
||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
||||
repository varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE repository (repository)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS tags;
|
||||
CREATE TABLE tags (
|
||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
||||
UNIQUE repositoryid (repositoryid,dirid,fileid,branchid,revision),
|
||||
KEY repositoryid_2 (repositoryid),
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
);
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print INTRO_TEXT
|
||||
|
||||
user = raw_input("MySQL User: ")
|
||||
passwd = raw_input("MySQL Password: ")
|
||||
dbase = raw_input("ViewCVS Database Name [default: ViewCVS]: ")
|
||||
if not dbase:
|
||||
dbase = "ViewCVS"
|
||||
|
||||
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
|
||||
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
|
||||
|
||||
pipes = popen2.Popen3(cmd)
|
||||
pipes.tochild.write(dscript)
|
||||
pipes.tochild.close()
|
||||
print pipes.fromchild.read()
|
||||
status = pipes.wait()
|
||||
|
||||
if status:
|
||||
print "[ERROR] the database did not create sucessfully."
|
||||
sys.exit(1)
|
||||
|
||||
print "Database created successfully."
|
||||
sys.exit(0)
|
||||
|
53
tools/make-release
Executable file
53
tools/make-release
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# make-release: internal tool for creating ViewCVS releases
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
if test $# != 2; then
|
||||
echo "USAGE: $0 tagname target-directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test -e $2; then
|
||||
echo "ERROR: must remove $2 first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# grab a copy of the CVS repository
|
||||
echo 'Checking out into:' $2
|
||||
cvs -d :pserver:anonymous@cvs.viewcvs.sourceforge.net:/cvsroot/viewcvs export -r $1 -d $2 viewcvs
|
||||
|
||||
# various shifting, cleanup.
|
||||
# documentation is now also distributed together with the release,
|
||||
# but we still copy the license file to its traditional place (it is small
|
||||
# and many files still contain comments refering to this location):
|
||||
cp $2/website/license-1.html $2/LICENSE.html
|
||||
# rm -r $2/website
|
||||
# remove some tools only useful for ViewCVS developers:
|
||||
rm $2/tools/make-release
|
||||
rm -f $2/tools/bin2inline_py.py
|
||||
|
||||
# Make sure, permissions are reasonable:
|
||||
find $2 -print | xargs chmod uoa+r
|
||||
find $2 -type d -print | xargs chmod uoa+x
|
||||
|
||||
# cut the tarball:
|
||||
tar cf - $2 | gzip -9 > $2.tar.gz
|
||||
# create also a ZIP file for those poor souls :-) still using Windows:
|
||||
zip -qor9 $2.zip $2
|
||||
|
||||
echo 'Done.'
|
262
viewcvs-install
Executable file
262
viewcvs-install
Executable file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewCVS
|
||||
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
|
||||
#
|
||||
# Contact information:
|
||||
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
|
||||
# gstein@lyra.org, http://viewcvs.sourceforge.net/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# install script for viewcvs -- temporary?
|
||||
#
|
||||
# ### this will eventually be replaced by autoconf plus tools. an
|
||||
# ### interactive front-end to ./configure may be provided.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
import traceback
|
||||
import py_compile
|
||||
import StringIO
|
||||
|
||||
# get access to our library modules
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
|
||||
|
||||
import compat
|
||||
import viewcvs
|
||||
import ndiff
|
||||
version = viewcvs.__version__
|
||||
|
||||
|
||||
## installer text
|
||||
INFO_TEXT = """\
|
||||
|
||||
This is the ViewCVS %s installer.
|
||||
|
||||
It will allow you to choose the install path for ViewCVS. You will
|
||||
now be asked some installation questions.
|
||||
|
||||
Defaults are given in square brackets. Just hit [Enter] if a default
|
||||
is okay.
|
||||
""" % version
|
||||
|
||||
## installer defaults
|
||||
ROOT_DIR = "/usr/local/viewcvs-" + version
|
||||
|
||||
|
||||
## list of files for installation
|
||||
## tuple (source path, destination path, install mode, true/false flag for
|
||||
## search-and-replace, flag or text for prompt before replace,
|
||||
## compile_it)
|
||||
##
|
||||
|
||||
FILE_INFO_LIST = [
|
||||
("cgi/viewcvs.cgi", "cgi/viewcvs.cgi", 0755, 1, 0, 0),
|
||||
("cgi/query.cgi", "cgi/query.cgi", 0755, 1, 0, 0),
|
||||
("standalone.py", "standalone.py", 0755, 1, 0, 0),
|
||||
|
||||
("cgi/viewcvs.conf.dist", "viewcvs.conf", 0644, 1,
|
||||
"""Note: If you are upgrading from viewcvs-0.7 or earlier:
|
||||
The section [text] has been removed from viewcvs.conf. The functionality
|
||||
went into the new files in subdirectory templates.""", 0),
|
||||
("cgi/cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
|
||||
|
||||
("lib/PyFontify.py", "lib/PyFontify.py", 0644, 0, 0, 1),
|
||||
("lib/blame.py", "lib/blame.py", 0644, 0, 0, 1),
|
||||
("lib/compat.py", "lib/compat.py", 0644, 0, 0, 1),
|
||||
("lib/config.py", "lib/config.py", 0644, 0, 0, 1),
|
||||
("lib/cvsdb.py", "lib/cvsdb.py", 0644, 1, 0, 1),
|
||||
("lib/dbi.py", "lib/dbi.py", 0644, 0, 0, 1),
|
||||
("lib/debug.py", "lib/debug.py", 0644, 0, 0, 1),
|
||||
("lib/popen.py", "lib/popen.py", 0644, 0, 0, 1),
|
||||
("lib/py2html.py", "lib/py2html.py", 0644, 0, 0, 1),
|
||||
("lib/query.py", "lib/query.py", 0644, 1, 0, 1),
|
||||
("lib/rcsparse.py", "lib/rcsparse.py", 0644, 0, 0, 1),
|
||||
("lib/rlog.py", "lib/rlog.py", 0644, 0, 0, 1),
|
||||
("lib/viewcvs.py", "lib/viewcvs.py", 0644, 1, 0, 1),
|
||||
("lib/ezt.py", "lib/ezt.py", 0644, 0, 0, 1),
|
||||
("lib/apache_icons.py", "lib/apache_icons.py", 0644, 0, 0, 1),
|
||||
("lib/accept.py", "lib/accept.py", 0644, 0, 0, 1),
|
||||
|
||||
("templates/annotate.ezt", "templates/annotate.ezt", 0644, 0, 1, 0),
|
||||
("templates/diff.ezt", "templates/diff.ezt", 0644, 0, 1, 0),
|
||||
("templates/directory.ezt", "templates/directory.ezt", 0644, 0, 1, 0),
|
||||
("templates/dir_alternate.ezt", "templates/dir_alternate.ezt", 0644, 0, 1, 0),
|
||||
("templates/footer.ezt", "templates/footer.ezt", 0644, 0, 1, 0),
|
||||
("templates/graph.ezt", "templates/graph.ezt", 0644, 0, 1, 0),
|
||||
("templates/header.ezt", "templates/header.ezt", 0644, 0, 1, 0),
|
||||
("templates/log.ezt", "templates/log.ezt", 0644, 0, 1, 0),
|
||||
("templates/log_table.ezt", "templates/log_table.ezt", 0644, 0, 1, 0),
|
||||
("templates/markup.ezt", "templates/markup.ezt", 0644, 0, 1, 0),
|
||||
("templates/query.ezt", "templates/query.ezt", 0644, 0, 1, 0),
|
||||
|
||||
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
|
||||
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
|
||||
("tools/make-database", "make-database", 0755, 1, 0, 0),
|
||||
|
||||
("website/help_rootview.html", "doc/help_rootview.html", 0644, 0, 0, 0),
|
||||
("website/help_dirview.html", "doc/help_dirview.html", 0644, 0, 0, 0),
|
||||
("website/help_query.html", "doc/help_query.html", 0644, 0, 0, 0),
|
||||
("website/help_log.html", "doc/help_log.html", 0644, 0, 0, 0),
|
||||
("website/help_logtable.html", "doc/help_logtable.html", 0644, 0, 0, 0),
|
||||
|
||||
("website/images/logo.png", "doc/images/logo.png", 0644, 0, 0, 0),
|
||||
("website/images/chalk.jpg", "doc/images/chalk.jpg", 0644, 0, 0, 0),
|
||||
("website/images/cvsgraph_16x16.png", "doc/images/cvsgraph_16x16.png", 0644, 0, 0, 0),
|
||||
("website/images/cvsgraph_32x32.png", "doc/images/cvsgraph_32x32.png", 0644, 0, 0, 0),
|
||||
]
|
||||
|
||||
|
||||
def Error(text, etype=None, evalue=None):
|
||||
print
|
||||
print "[ERROR] %s" % text
|
||||
if etype:
|
||||
print '[ERROR] ',
|
||||
traceback.print_exception(etype, evalue, None, file=sys.stdout)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def MkDir(path):
|
||||
try:
|
||||
compat.makedirs(path)
|
||||
except os.error, e:
|
||||
if e[0] == 17:
|
||||
# EEXIST: file exists
|
||||
return
|
||||
if e[0] == 13:
|
||||
# EACCES: permission denied
|
||||
Error("You do not have permission to create directory %s" % path)
|
||||
Error("Unknown error creating directory %s" % path, OSError, e)
|
||||
|
||||
|
||||
|
||||
def SetOnePath(contents, var, value):
|
||||
pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
|
||||
repl = '%s = "%s"' % (var, os.path.join(ROOT_DIR, value))
|
||||
return re.sub(pattern, repl, contents)
|
||||
|
||||
|
||||
def SetPythonPaths(contents):
|
||||
if contents[:2] == '#!':
|
||||
shbang = '#!' + sys.executable
|
||||
contents = re.sub('^#![^\n]*', shbang, contents)
|
||||
contents = re.sub("<VIEWCVS_INSTALL_DIRECTORY>", ROOT_DIR, contents)
|
||||
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
|
||||
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewcvs.conf')
|
||||
return contents
|
||||
|
||||
|
||||
def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
|
||||
compile_it):
|
||||
dest_path = os.path.join(ROOT_DIR, dest_path)
|
||||
|
||||
if prompt_replace and os.path.exists(dest_path):
|
||||
# Collect ndiff output from ndiff
|
||||
sys.stdout = StringIO.StringIO()
|
||||
ndiff.main([dest_path,src_path])
|
||||
ndiff_output = sys.stdout.getvalue()
|
||||
|
||||
# Return everything to normal
|
||||
sys.stdout = sys.__stdout__
|
||||
|
||||
# Collect the '+ ' and '- ' lines
|
||||
# total collects the difference lines to be printed later
|
||||
total = ""
|
||||
# I use flag to throw out match lines.
|
||||
flag = 1
|
||||
for line in string.split(ndiff_output,'\n'):
|
||||
# Print line if it is a difference line
|
||||
if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
|
||||
total = total + line + "\n"
|
||||
flag = 1
|
||||
else:
|
||||
# Compress lines that are the same to print one blank line
|
||||
if flag:
|
||||
total = total + "\n"
|
||||
flag = 0
|
||||
|
||||
if total == "\n":
|
||||
print " File %s exists,\n but there is no difference between target and source files.\n" % (dest_path)
|
||||
return
|
||||
|
||||
if type(prompt_replace) == type(""):
|
||||
print prompt_replace
|
||||
while 1:
|
||||
temp = raw_input("\n File %s\n exists and is different from source file.\n DO YOU WANT TO,\n overwrite [o]\n do not overwrite [d]\n view differences [v]: " % (dest_path))
|
||||
print
|
||||
|
||||
temp = string.lower(temp[0])
|
||||
|
||||
if temp == "d":
|
||||
return
|
||||
|
||||
if temp == "v":
|
||||
print total
|
||||
print "\nLEGEND\n A leading '- ' indicates line to remove from installed file\n A leading '+ ' indicates line to add to installed file\n A leading '? ' shows intraline differences."
|
||||
|
||||
if temp == "o":
|
||||
ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it)
|
||||
return
|
||||
else:
|
||||
ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it)
|
||||
return
|
||||
|
||||
def ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it):
|
||||
try:
|
||||
contents = open(src_path, "r").read()
|
||||
except IOError, e:
|
||||
Error(str(e))
|
||||
|
||||
if set_python_paths:
|
||||
contents = SetPythonPaths(contents)
|
||||
|
||||
## write the file to the destination location
|
||||
path, basename = os.path.split(dest_path)
|
||||
MkDir(path)
|
||||
|
||||
try:
|
||||
open(dest_path, "w").write(contents)
|
||||
except IOError, e:
|
||||
if e[0] == 13:
|
||||
# EACCES: permission denied
|
||||
Error("You do not have permission to write file %s" % dest_path)
|
||||
Error("Unknown error writing file %s" % dest_path, IOError, e)
|
||||
|
||||
os.chmod(dest_path, mode)
|
||||
|
||||
if compile_it:
|
||||
py_compile.compile(dest_path)
|
||||
|
||||
return
|
||||
|
||||
|
||||
## MAIN
|
||||
if __name__ == "__main__":
|
||||
print INFO_TEXT
|
||||
|
||||
## get the install path
|
||||
temp = raw_input("Installation Path [%s]: " % ROOT_DIR)
|
||||
temp = string.strip(temp)
|
||||
if len(temp):
|
||||
ROOT_DIR = temp
|
||||
|
||||
## install the files
|
||||
print
|
||||
print "Installing ViewCVS to:", ROOT_DIR
|
||||
|
||||
for args in FILE_INFO_LIST:
|
||||
print " ", args[0]
|
||||
apply(InstallFile, args)
|
||||
|
||||
print
|
||||
print "Installation Complete"
|
173
website/contributing.html
Normal file
173
website/contributing.html
Normal file
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Contributing to ViewCVS Development</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>Contributing to ViewCVS Development</h1>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top>
|
||||
<a href="index.html">Home</a><br>
|
||||
<a href="upgrading.html">Upgrading</a><br>
|
||||
Contributing<br>
|
||||
<a href="license-1.html">License</a><br>
|
||||
<a href="who.html">Who</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p>
|
||||
Contributions to ViewCVS are very welcome.
|
||||
</p>
|
||||
|
||||
<h2>Getting Started</h2>
|
||||
<p>
|
||||
Some basic knowledge about <a href="http://www.python.org"><i>Python</i></a> and
|
||||
development Tools like <code>diff</code> is required. Best is you start
|
||||
with a fresh snapshot, which you may obtain from the
|
||||
<a href="http://sourceforge.net/cvs/?group_id=18760">CVS-Repository</a>
|
||||
at SourceForge.
|
||||
</p>
|
||||
|
||||
<h2>Testing and reporting</h2>
|
||||
<p>
|
||||
Testing usability and the installation process on different platforms is
|
||||
also a valuable contribution. Please report your results back to
|
||||
us developers. But always tell us, which version of ViewCVS
|
||||
you used on which platform.
|
||||
</p><p>
|
||||
If you are using the latest current development version please note
|
||||
the date of your latest download or cvs update when reporting.
|
||||
</p>
|
||||
|
||||
<h2>Does a Certain Coding Style Guide apply?</h2>
|
||||
<p>
|
||||
Maintain whatever style is present in the code being modified. New
|
||||
code can use anything sane (which generally means
|
||||
<a href="http://python.sourceforge.net/peps/pep-0008.html"><i>PEP 8</i></a>).
|
||||
Greg's only real peeve is if someone writes a function call as:
|
||||
<code>some_func (args)</code> -- that space is Huge Badness.
|
||||
Otherwise... <i>shrug</i>.
|
||||
</p>
|
||||
|
||||
<h2>Submitting patches</h2>
|
||||
<p>
|
||||
Please use the
|
||||
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=318760">SourceForge project patch manager</a>
|
||||
to submit your patches. Unified context diffs relative to the latest
|
||||
development version are preferred.
|
||||
</p><p>
|
||||
If you have commit access, then you should know what
|
||||
you're doing. Just make changes directly. Subscribing to
|
||||
the <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>developer mailing list</i></a>
|
||||
is recommended in any case.
|
||||
</p>
|
||||
|
||||
<h2>Preserving security</h2>
|
||||
<p>
|
||||
Since ViewCVS is used in the internet, security is a major issue.
|
||||
If you need to pass data from the request into an external program,
|
||||
please don't use <code>os.system()</code> or <code>os.popen()</code>.
|
||||
Please use the module <code>lib/popen</code> that comes with ViewCVS
|
||||
instead.
|
||||
</p>
|
||||
|
||||
<h2>Adding new features</h2>
|
||||
<p>
|
||||
If a new file or module is added, a new line in the installer program
|
||||
<code>viewcvs-install</code> is required.
|
||||
</p><p>
|
||||
The library subdirectory contains a module <code>debug</code>, which may
|
||||
useful to make performance tests.
|
||||
</p><p>
|
||||
If you need a new configuration option think carefully, into
|
||||
which section it belongs. Try to keep the content of
|
||||
<code>cgi/viewcvs.conf.dist</code> file and the library module
|
||||
<code>lib/config.py</code> in sync.
|
||||
</p>
|
||||
|
||||
<h2>Hacking on templates</h2>
|
||||
<p>
|
||||
The library module <code>ezt</code> contains a module doc string which
|
||||
describes the directives used in the HTML templates, which can be found
|
||||
in the <code>templates</code> sub directory.
|
||||
</p>
|
||||
|
||||
<h2>Cutting a release</h2>
|
||||
<p>
|
||||
Also there actually is a script <code>tools/make-release</code>,
|
||||
which creates a release directory, all other steps required to get
|
||||
a ViewCVS release out of the door will be currently executed manually
|
||||
by Greg Stein.
|
||||
</p><p>
|
||||
Nevertheless in case he ever wants to retire from this job, it is
|
||||
probably a good idea to write the procedure down here:
|
||||
<ol>
|
||||
<li>Add a new subsection to the file
|
||||
<code>website/upgrading.html</code> describing all user visible
|
||||
changes for users of previous releases of ViewCVS.
|
||||
<li>Test, Test, Test! At the time of this writing (0.8-dev) there
|
||||
is no automatic testsuite available. So just run with permuting
|
||||
different <code>viewcvs.conf</code> settings and ... pray.
|
||||
<li>Review any <a
|
||||
href="http://sourceforge.net/tracker/?atid=118760&group_id=18760&func=browse">
|
||||
bug reports, that are still marked open.</a>
|
||||
<li>Edit the file <code>lib/viewcvs.py</code> and remove the
|
||||
<tt>"-dev"</tt> suffix from <code>__version__</code>. The remainder
|
||||
should be of the form X.Y, where X is a positive number and
|
||||
Y is a single digit.
|
||||
<li>commit this changed version of <code>lib/viewcvs.py</code>
|
||||
and than <code>cvs tag V</code><i>X</i><code>_</code><i>Y</i>, where
|
||||
<i>X</i> and <i>Y</i> should be replaced by the release number
|
||||
from above. If a developer is willing to volunteer as a
|
||||
bug fix patch release manager, it is now possible to start here
|
||||
at this point with a feature freezed branch using the command
|
||||
<blockquote>
|
||||
<code>cvs tag -b V</code><i>X</i><code>_</code><i>Y</i><code>_maint</code>
|
||||
</blockquote>
|
||||
<li>go into an empty directory and run the command:
|
||||
<blockquote>
|
||||
<code>tools/make-release V</code><i>X_Y</i> <code>viewcvs-</code><i>X.Y</i>
|
||||
</blockquote>
|
||||
This step requires anonymous CVS access to repository at SourceForge.
|
||||
<li>pack the content of this <code>viewcvs-</code><i>X.Y</i> directory
|
||||
into a tarball.
|
||||
<li>Upload the created tarball into the download files section of the
|
||||
ViewCVS project at SourceForge.
|
||||
<br><b>Greg:</b><i>Could you please elaborate this step?</i>
|
||||
<li>Edit the file <code>lib/viewcvs.py</code> again and this time
|
||||
increment the <code>__version__</code> for the next release cycle,
|
||||
again append the <code>"-dev"</code> to the version and again
|
||||
<blockquote>
|
||||
<code>cvs commit -m "new release cycle begins" lib/viewcvs.py</code>.
|
||||
</blockquote>
|
||||
<li>Write an announcement explaining all the cool new features
|
||||
and put it out to:
|
||||
<ul>
|
||||
<li><a href="http://www.freshmeat.net"><i>www.freshmeat.net</i></a>
|
||||
<li><a href="http://www.vex.net/parnassus/apyllo.py?i=91022454"><i>Vaults of Parnassus</i></a>
|
||||
<li><a href="news:comp.lang.python">comp.lang.python</a>
|
||||
<li><i>Where else? I dunno. Suggestions welcome.</i>
|
||||
</ul>
|
||||
</ol>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 18 09:05:40 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Mon Nov 19 20:25:38 CEST 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
53
website/help_dirview.html
Normal file
53
website/help_dirview.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: Directory View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: Directory View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<b>Directory View</b><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p>
|
||||
Click on a directory to enter that directory. Click in the leftmost
|
||||
column of a row to display the revision history of a file and to get
|
||||
a chance to display diffs between revisions. Click on second column
|
||||
to view the content of the latest revision of that file.
|
||||
</p>
|
||||
<p>
|
||||
Directories are always displayed first in alphabetically order.
|
||||
Ordinary files follow and are sorted according to the selected
|
||||
sort criteria. You may click on a particular column header to
|
||||
select this column as sort criteria. It is than displayed in
|
||||
another color (e.g. light green, if using the default configuration).
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Tue Nov 13 20:40:02 CEST 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
61
website/help_log.html
Normal file
61
website/help_log.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: Classic CVS Log View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: Classic CVS Log View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<b>Classic Log View</b><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
<p>
|
||||
The log view displays the revision history of the selected source
|
||||
file. For each revision the following information is displayed:
|
||||
<ul>
|
||||
<li>the revision number (clickable to download it)</li>
|
||||
<li>a link to view this revision annotated</li>
|
||||
<li>a link to select this revision for diffs (see below)</li>
|
||||
<li>the date and age of this change</li>
|
||||
<li>and the author of this modification.</li>
|
||||
<li>The CVS branch (usually <code>MAIN</code>, if not on a branch).</li>
|
||||
<li>Possibly a list of CVS tags bound to this revision (if any).</li>
|
||||
<li>The size of this change measured in added and removed lines of
|
||||
code.</li>
|
||||
<li>Links to view Diffs to the previous revision or possibly to
|
||||
an arbitrary selected revision (if any, see above).
|
||||
<li>And last but not least, the commit log message which should tell
|
||||
about the reason for this change.</li>
|
||||
</ul>
|
||||
</p><p>
|
||||
At the bottom of such a page you will find a form which allows
|
||||
to request diffs between arbitrary revisions.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Dec 12 13:56:52 CET 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
59
website/help_logtable.html
Normal file
59
website/help_logtable.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: CVS Log Table View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: CVS Log Table View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<b>Alternative Log View</b><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
<p>
|
||||
The table based log view must be enabled in the site wide
|
||||
ViewCVS configuration file.
|
||||
</p><p>
|
||||
It displays the revision history of the selected source
|
||||
file in a table containing the following columns:
|
||||
<ul>
|
||||
<li><b>Revision</b>: The CVS revision number.</li>
|
||||
<li><b>Tasks</b>: This column contains several links to view
|
||||
a revision.</li>
|
||||
<li><b>Diffs</b>: Several links to view the changes between this
|
||||
and other revisions.
|
||||
<li><b>Branches and Tags</b>: Displays the branch and a pulldown
|
||||
menu button displaying all tags bound to that revision</li>
|
||||
<li>FIXME: Document the other columns</li>
|
||||
</ul>
|
||||
</p><p>
|
||||
At the bottom of such a page you will find a form which allows
|
||||
to request diffs between arbitrary revisions.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sat Dec 8 23:26:52 CET 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
64
website/help_query.html
Normal file
64
website/help_query.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html><head>
|
||||
<title>ViewCVS Help: Query The Commit Database</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" border=0 cellspacing=5 cellpadding=0>
|
||||
<tr>
|
||||
<td width="1%"><a href=".."><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td><h1>ViewCVS Help: Query The Commit Database</h1></td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign="top" bgcolor="#ffffff">
|
||||
<h3>Other Help:</h3>
|
||||
<a href="help_rootview.html">General</a><br>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<b>Query Database</b>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p>
|
||||
Select your parameters for querying the CVS commit database in the
|
||||
form at the top of the page. You
|
||||
can search for multiple matches by typing a comma-seperated list
|
||||
into the text fields. Regular expressions, and wildcards are also
|
||||
supported. Blank text input fields are treated as wildcards.
|
||||
</p>
|
||||
<p>
|
||||
Any of the text entry fields can take a comma-seperated list of
|
||||
search arguments. For example, to search for all commits from
|
||||
authors <i>jpaint</i> and <i>gstein</i>, just type: <code>jpaint,
|
||||
gstein</code> in the <i>Author</i> input box. If you are searching
|
||||
for items containing spaces or quotes, you will need to quote your
|
||||
request. For example, the same search above with quotes is:
|
||||
<code>"jpaint", "gstein"</code>.
|
||||
</p>
|
||||
<p>
|
||||
Wildcard and regular expression searches are entered in a similar
|
||||
way to the quoted requests. You must quote any wildcard or
|
||||
regular expression request, and a command character preceeds the
|
||||
first quote. The command character <code>l</code>(lowercase L) is for wildcard
|
||||
searches, and the wildcard character is a percent (<code>%</code>). The
|
||||
command character for regular expressions is <code>r</code>, and is
|
||||
passed directly to MySQL, so you'll need to refer to the MySQL
|
||||
manual for the exact regex syntax. It is very similar to Perl. A
|
||||
wildard search for all files with a <i>.py</i> extention is:
|
||||
<code>l"%.py"</code> in the <i>File</i> input box. The same search done
|
||||
with a regular expression is: <code>r".*\.py"</code>.
|
||||
</p>
|
||||
<p>
|
||||
All search types can be mixed, as long as they are seperated by
|
||||
commas.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
</body></html>
|
83
website/help_rootview.html
Normal file
83
website/help_rootview.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS Help: Toplevel Directory View</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href=".."><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS Help: Toplevel Directory View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top bgcolor="#ffffff">
|
||||
<h3>Help</h3>
|
||||
<b>General</b>
|
||||
<a href="help_dirview.html">Directory View</a><br>
|
||||
<a href="help_log.html">Classic Log View</a><br>
|
||||
<a href="help_logtable.html">Alternative Log View</a><br>
|
||||
<a href="help_query.html">Query Database</a><br>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
|
||||
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
|
||||
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
|
||||
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p><b>ViewCVS</b> is a WWW interface for CVS Repositories.</b>
|
||||
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 display 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>
|
||||
<h3>ViewCVS History and Credits</h3>
|
||||
<p>
|
||||
This program
|
||||
(<a href="http://viewcvs.sourceforge.net/">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://viewcvs.sourceforge.net/">ViewCVS distribution site</a>.
|
||||
Please send any suggestions, comments, etc. to the
|
||||
<a href="mailto:viewcvs-dev@lyra.org">ViewCVS Developers Mailinglist</a>.
|
||||
</p>
|
||||
|
||||
<h3>Documentation about CVS</h3>
|
||||
<blockquote>
|
||||
<p>
|
||||
<a href="http://cvsbook.red-bean.com/">Karl Fogel's CVS book</a><br>
|
||||
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||
User's Guide</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>
|
||||
|
||||
<!-- insert repository access instructions here -->
|
||||
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Thu Oct 25 22:16:29 CEST 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Tue Nov 13 20:41:02 CEST 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
BIN
website/images/chalk.jpg
Normal file
BIN
website/images/chalk.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 755 B |
BIN
website/images/cvsgraph_16x16.png
Normal file
BIN
website/images/cvsgraph_16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 B |
BIN
website/images/cvsgraph_32x32.png
Normal file
BIN
website/images/cvsgraph_32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 275 B |
BIN
website/images/logo.png
Normal file
BIN
website/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
@@ -3,95 +3,303 @@
|
||||
<head>
|
||||
<title>ViewCVS: Viewing CVS Repositories</title>
|
||||
</head>
|
||||
<!-- Editors: Please keep all links to external sites in italics. -->
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><img border=0 src="images/logo.png"></td>
|
||||
<td>
|
||||
<h1>ViewCVS: Viewing CVS Repositories</h1>
|
||||
</td>
|
||||
<td align=center valign=top bgcolor="white" width="1%">
|
||||
<b>Quickstart:</b>
|
||||
<a href="viewcvs-0.9.2.tar.gz">download</a>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top>
|
||||
<h3>Links:</h3>
|
||||
<a href="http://viewcvs.sourceforge.net">Home</a><br>
|
||||
<a href="upgrading.html">Upgrading</a><br>
|
||||
<a href="contributing.html">Contributing</a><br>
|
||||
<a href="license-1.html">License</a><br>
|
||||
<a href="who.html">Who</a><br>
|
||||
<h3>Sections:</h3>
|
||||
<a href="#Features">Features</a><br>
|
||||
<a href="#History">History</a><br>
|
||||
<a href="#Mail">Mailing Lists</a><br>
|
||||
<a href="#Cvsweb">vs. cvsweb</a><br>
|
||||
<a href="#Download">Download</a><br>
|
||||
<a href="#Future">Future directions</a><br>
|
||||
<a href="#Colorize">Colorization</a><br>
|
||||
</td>
|
||||
<td colspan=3>
|
||||
|
||||
<body background="/images/chalk.jpg">
|
||||
<h1>ViewCVS: Viewing CVS Repositories</h1>
|
||||
|
||||
<p>
|
||||
The ViewCVS software was inspired by
|
||||
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi"><i>cvsweb</i></a>
|
||||
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
||||
I wanted to make some changes and updates, but cvsweb was
|
||||
implemented in Perl (and rather poorly, IMO). So I undertook the
|
||||
task to convert the software to Python.
|
||||
</p>
|
||||
<hr width="75%">
|
||||
<h2><a name="Features">Features</a></h2>
|
||||
<p>
|
||||
ViewCVS can browse directories, change logs, and specific
|
||||
revisions of files. It can display diffs between versions and
|
||||
show selections of files based on tags or branches.
|
||||
show selections of files based on tags or branches. In addition,
|
||||
ViewCVS has "annotation" or "blame" support, Bonsai-like query
|
||||
facilities, template-based page generation, and support for
|
||||
individually configuring virtual hosts.
|
||||
|
||||
It also includes support for
|
||||
<a href="http://www.akhphd.au.dk/~bertho/cvsgraph/"><i>CvsGraph</i></a>
|
||||
-- a program to display the tree of revisions and branches
|
||||
graphically.
|
||||
</p>
|
||||
<p>
|
||||
ViewCVS is currently at version 0.2. It is almost a straight
|
||||
port of the cvsweb script, but has had numerous cleanups and other
|
||||
modifications, based on some of Python's strengths. There is
|
||||
still some "badness" in there, but I've been working on flushing
|
||||
that out, along with my work to start adding features. The
|
||||
functionality of ViewCVS is equal to that of cvsweb, minus the
|
||||
"annotation" support. Annotation requires read/write access to
|
||||
the CVS repository (at this time), and I believe that is a
|
||||
Bad Thing to do. One of my tasks will be eliminating the
|
||||
read/write requirement.
|
||||
Currently, the functionality of ViewCVS surpasses that of cvsweb.
|
||||
See <a href="#Cvsweb">below</a> for a list of additional features.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h2><a name="History">History</a></h2>
|
||||
<p>
|
||||
The ViewCVS software was inspired by
|
||||
<a href="http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/"><i>cvsweb</i></a>
|
||||
(originally written by Bill Fenner and then further developed by
|
||||
<a href="mailto:zeller@think.de">Henner Zeller</a>).
|
||||
Greg Stein wanted to make some changes and updates, but cvsweb was
|
||||
implemented in Perl. He wrote:
|
||||
<blockquote><i>While I can manage some Perl, cvsweb was
|
||||
rather unmaintainable for me. So I undertook the
|
||||
task to convert the software to
|
||||
<a href="http://www.python.org/">Python</a>. As a result,
|
||||
I've actually been able to go <em>way</em> beyond the simple
|
||||
changes that I had envisioned.
|
||||
</i></blockquote>
|
||||
</p>
|
||||
<p>
|
||||
ViewCVS started as a port of the cvsweb script, but has had
|
||||
numerous cleanups and other modifications, based on some of
|
||||
Python's strengths. There is still some minor "badness"
|
||||
remaining from the Perl code, but Greg has been working on
|
||||
flushing that out, while adding new features.
|
||||
</p>
|
||||
<p>
|
||||
ViewCVS has been developed by the <a href="who.html">ViewCVS
|
||||
Group</a> and is made available under a
|
||||
<a href="license-1.html">BSD-type license</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
ViewCVS requires <strong>Python 1.5</strong> or later (Python
|
||||
1.5 has been out for a couple years and is readily available for
|
||||
your favorite operating system). If you choose to use the SQL Checkin
|
||||
Database feature, then you must use <strong>Python 1.5.2</strong>
|
||||
or later and have the
|
||||
<a href="http://sourceforge.net/projects/mysql-python"><i>MySQLdb
|
||||
module</i></a> installed which itself requires
|
||||
Marc-Andre Lemburgs
|
||||
<a href="http://www.lemburg.com/files/python/eGenix-mx-Extensions.html#Download-mxBASE"><i>mxDateTime extension</i></a>.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h2><a name="Mail">Mailing Lists</a></h2>
|
||||
<p>
|
||||
If you have any comments, questions, or suggestions,
|
||||
then please send them to the
|
||||
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs"><i>ViewCVS
|
||||
mailing list</i></a>, which is also
|
||||
<a href="http://mailman.lyra.org/pipermail/viewcvs/"><i>archived</i></a>.
|
||||
</p>
|
||||
<p>
|
||||
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>mailing
|
||||
list for ViewCVS developers</i></a> is also available
|
||||
(<a href="http://mailman.lyra.org/pipermail/viewcvs-dev/"><i>Archive</i></a>).
|
||||
</p>
|
||||
<p>
|
||||
ViewCVS is an <a href="http://www.opensource.org/"><i>Open
|
||||
Source</i></a> project, and all
|
||||
<a href="contributing.html">contributions</a> are welcome.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h2><a name="Cvsweb">Additional features over cvsweb</a></h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Template support: you can now customize the look and feel of
|
||||
ViewCVS by editing the provided EZT templates, which are used
|
||||
to generate the pages.
|
||||
</li>
|
||||
<li>
|
||||
Internationalization support: ViewCVS will parse and handle
|
||||
the Accept-Language request header, and can select different
|
||||
inputs for localized page generation.
|
||||
</li>
|
||||
<li>
|
||||
Colorization for many file types via <code>enscript</code>.
|
||||
</li>
|
||||
<li>
|
||||
Bonsai-like query features. (Requires MySQL and some other
|
||||
prerequisites)
|
||||
</li>
|
||||
<li>
|
||||
Annotation/blame viewing support against a <strong>read-only</strong>
|
||||
repository.
|
||||
</li>
|
||||
<li>
|
||||
Configuration on a per-virtual-host basis. This allows you
|
||||
to share the configuration file and ViewCVS installation
|
||||
across virtual hosts, yet still be able to fine-tune the
|
||||
options when necessary.
|
||||
</li>
|
||||
<li>
|
||||
Automatic generation of tarballs for the HEAD or a specified
|
||||
tag.
|
||||
</li>
|
||||
<li>
|
||||
Runs either as CGI script, called from an installed web server
|
||||
(such as <a href="http://httpd.apache.org/"><i>Apache</i></a>),
|
||||
or as a standalone server.
|
||||
</li>
|
||||
<li>
|
||||
Searching files for matches for a regular expression.
|
||||
</li>
|
||||
<li>Better reporting for unreadable files.</li>
|
||||
<li>
|
||||
More robust when given varying <code>rcsdiff</code> or
|
||||
<code>rlog</code> outputs.
|
||||
</li>
|
||||
<li>Hard breaks in human-readable diffs.</li>
|
||||
<li>
|
||||
The configuration file is optional (you can change the values
|
||||
right in the CGI script and avoid the config file, if you so
|
||||
choose). The config file syntax is also cleaner, since it is
|
||||
human-manageable rather than source code.
|
||||
</li>
|
||||
<li>
|
||||
Directories with a large number of files can be viewed.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Security</strong>: ViewCVS only requires read access
|
||||
to the CVS repository (not read/write). With the correct
|
||||
security partitioning, this means that even if ViewCVS were to
|
||||
be subverted, your source code is safe. Further, ViewCVS does
|
||||
not use any <code>system()</code> or <code>popen()</code>
|
||||
calls, which are very susceptible to abuse.
|
||||
<br>
|
||||
<small>(cvsweb had a hole due to a popen() call)</small>
|
||||
</li>
|
||||
<li>
|
||||
Last but not least: it doesn't suffer from the "unmaintainable
|
||||
code effect" that hits most Perl projects sooner or later:
|
||||
<blockquote><i>[Perl] combines all the worst aspects of C and Lisp:
|
||||
a billion different sublanguages in one monolithic executable.
|
||||
It combines the power of C with the readability of PostScript.</i>
|
||||
-- Jamie Zawinski
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The changes present in each release are available in
|
||||
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">ViewCVS's
|
||||
CHANGES file</a>.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h2><a name="Download">Download</a></h2>
|
||||
<p>
|
||||
The software is available for download:
|
||||
</p>
|
||||
<blockquote>
|
||||
<a href="viewcvs-0.2.tar.gz">Version 0.2 of ViewCVS</a>
|
||||
<a href="viewcvs-0.9.2.tar.gz">Version 0.9.2 of ViewCVS as a gzipped
|
||||
tar</a>
|
||||
<br>
|
||||
<a href="viewcvs-0.9.2.zip">Version 0.9.2 of ViewCVS as a ZIP
|
||||
file</a>
|
||||
</blockquote>
|
||||
<p>
|
||||
Of course, it is also available through ViewCVS itself:
|
||||
Of course the current development version is also available
|
||||
through ViewCVS itself:
|
||||
</p>
|
||||
<blockquote>
|
||||
<a href="/cgi-bin/viewcvs.cgi/gjspy/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/gjspy/viewcvs/</a>
|
||||
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
ViewCVS <font color=red>requires <strong>Python
|
||||
1.5.2</strong></font>.
|
||||
</p>
|
||||
|
||||
<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>
|
||||
[ <a href="../">up to Python software</a> ]
|
||||
[ <a href="../../">up to Greg's page</a> ]
|
||||
You can also
|
||||
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">see
|
||||
the changes</a> for this release.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h2>FAQ</h2>
|
||||
<h2><a name="Future">Future features, directions</a></h2>
|
||||
<p>
|
||||
ViewCVS is a Open Source project. So any future development depends
|
||||
on the contributions that will be made by its user community.
|
||||
Certainly working patches have a greater chance to become realized
|
||||
quickly than feature requests. <i>But don't hesitate to submit your
|
||||
suggestions! Send mail to the
|
||||
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
||||
mailing list or even better use the
|
||||
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=368760">SF tracker</a>.
|
||||
</i>
|
||||
</p>
|
||||
<ul>
|
||||
<li>See the feature requests already submitted through
|
||||
the <a href="http://sourceforge.net/tracker/?atid=368760&group_id=18760&func=browse">SF feature request tracker</a>
|
||||
</li>
|
||||
<li>UI streamlining/simplification</li>
|
||||
<li>Integration with CVS checkin auto-mail scripts</li>
|
||||
<li>Tighter integration with the query features</li>
|
||||
<p>
|
||||
</ul>
|
||||
|
||||
<dl>
|
||||
<!-- one of (dd dt) -->
|
||||
<dt><strong>I got an <code>AttributeError: urlencode</code></strong></dt>
|
||||
<dd>
|
||||
If you see the following traceback in your browser:
|
||||
<blockquote>
|
||||
<pre>
|
||||
Traceback (innermost last):
|
||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2205, in ?
|
||||
main()
|
||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2123, in main
|
||||
query_string = sticky_query(query_dict)
|
||||
File "/home/httpd/cgi-bin/viewcvs.cgi", line 444, in sticky_query
|
||||
bare_query = urllib.urlencode(sticky_dict)
|
||||
AttributeError: urlencode
|
||||
</pre>
|
||||
</blockquote>
|
||||
This is indicative of using an earlier version of
|
||||
Python. <code>urllib.urlencode()</code> was introduced in
|
||||
Python 1.5.2.
|
||||
</dd>
|
||||
</dl>
|
||||
<p>
|
||||
And another longer term pet of Greg Stein:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Integration with an indexer such as LXR</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
||||
<hr width="75%">
|
||||
<h2><a name="Colorize">Colorization of files</a></h2>
|
||||
<p>
|
||||
ViewCVS can make use of the <code>enscript</code> program to
|
||||
colorize files in the CVS repository. If <code>enscript</code>
|
||||
is present on your system, then set the
|
||||
<code>use_enscript</code> option in the
|
||||
<code>viewcvs.conf</code> configuration file to <code>1</code>.
|
||||
If necessary,
|
||||
update the <code>enscript_path</code> option to point to your
|
||||
installation directory. ... That's it! Now, as you view files
|
||||
through ViewCVS, they will be colored.
|
||||
</p>
|
||||
|
||||
<h3>Colorization of Python files</h3>
|
||||
<p>
|
||||
ViewCVS currently also comes with a builtin colorizer for Python
|
||||
source files. This may go away in a future version, given the new
|
||||
<code>enscript</code> support...
|
||||
</p>
|
||||
<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>
|
||||
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Wed Jan 12 03:12:22 PST 2000
|
||||
Last modified: Tue Jan 15 01:51:03 PST 2002
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
|
102
website/license-1.html
Normal file
102
website/license-1.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewCVS License Agreement (v1)</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>ViewCVS License Agreement (v1)</h1>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top>
|
||||
<a href="index.html">Overview</a><br>
|
||||
<a href="upgrading.html">Upgrading</a><br>
|
||||
<a href="contributing.html">Contributing</a><br>
|
||||
License<br>
|
||||
<a href="who.html">Who</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p>
|
||||
The following text constitutes the license agreement for the
|
||||
<a href="./">ViewCVS</a> software. It
|
||||
is an agreement between
|
||||
<a href="who.html">The ViewCVS
|
||||
Group</a> and the users of ViewCVS.
|
||||
</p>
|
||||
<p>
|
||||
<small>
|
||||
<em>
|
||||
Note: the copyright years were updated on May 12, 2001. No
|
||||
other changes were made to the license.
|
||||
</em>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
<b>
|
||||
Copyright © 1999-2001 The ViewCVS Group. All rights reserved.
|
||||
</b>
|
||||
</p>
|
||||
<p>
|
||||
By using ViewCVS, you agree to the terms and conditions set
|
||||
forth below:
|
||||
</p>
|
||||
<p>
|
||||
Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>
|
||||
Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||
<!-- Created: Mon May 8 19:01:27 PDT 2000 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sat Oct 20 16:09:37 PDT 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
356
website/upgrading.html
Normal file
356
website/upgrading.html
Normal file
@@ -0,0 +1,356 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Upgrading a ViewCVS Installation</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>Upgrading a ViewCVS Installation</h1>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top>
|
||||
<a href="index.html">Overview</a><br>
|
||||
Upgrading<br>
|
||||
<a href="contributing.html">Contributing</a><br>
|
||||
<a href="license-1.html">License</a><br>
|
||||
<a href="who.html">Who</a><br>
|
||||
</td><td colspan=2>
|
||||
|
||||
<p>
|
||||
This document describes some of the things that you will need to
|
||||
consider, change, or handle when upgrading an existing ViewCVS
|
||||
installation to a newer version.
|
||||
</p>
|
||||
<p>
|
||||
It is always recommended to install the new version in a fresh directory
|
||||
and to carefully compare the configuration files. A possible approach
|
||||
is to name the directories <code>/usr/local/viewcvs-0.6</code>,
|
||||
<code>/usr/local/viewcvs-0.7</code> and so on and than create a
|
||||
symbolic link <code>viewcvs</code> pointing to the production
|
||||
version. This way you can easily test several versions and switch
|
||||
back, if your users start to complain.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="#from8">Upgrading from ViewCVS 0.8</a></li>
|
||||
<li><a href="#from7">Upgrading from ViewCVS 0.7 or earlier</a></li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2><a name="from8">Upgrading from ViewCVS 0.8</a></h2>
|
||||
<p>
|
||||
This section discusses how to upgrade ViewCVS 0.8 to version
|
||||
0.9 or a later version of the software.
|
||||
</p>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<p>
|
||||
More templates were introduced in version 0.8 of the software,
|
||||
which made many of the configuration options obsolete. This
|
||||
section covers which options were removed. If you made any
|
||||
changes to these options, then you will need to make
|
||||
corresponding changes in the templates.
|
||||
</p>
|
||||
<blockquote>
|
||||
<dl>
|
||||
<dt>
|
||||
Colors:
|
||||
<strong>diff_heading</strong>,
|
||||
<strong>diff_empty</strong>,
|
||||
<strong>diff_remove</strong>,
|
||||
<strong>diff_change</strong>,
|
||||
<strong>diff_add</strong>,
|
||||
and <strong>diff_dark_change</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>diff.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>markup_log</strong></dt>
|
||||
<dd>
|
||||
This option has been incorporated into the
|
||||
<code>markup.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>Colors: <strong>nav_header</strong>
|
||||
and <strong>alt_background</strong></dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>header.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
Images:
|
||||
<strong>back_icon</strong>,
|
||||
<strong>dir_icon</strong>,
|
||||
and <strong>file_icon</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>directory.ezt</code>, <code>header.ezt</code>,
|
||||
<code>log.ezt</code>, <code>log_table.ezt</code>, and
|
||||
<code>query.ezt</code> templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>use_java_script</strong>
|
||||
and <strong>open_extern_window</strong></dt>
|
||||
<dd>
|
||||
The templates now use JavaScript in all applicable places,
|
||||
and open external windows for most downloading and viewing
|
||||
of files. If you wish to not use JavaScript and/or external
|
||||
windows, then remove the feature(s) from the templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>show_author</strong></dt>
|
||||
<dd>
|
||||
Changing this option would be quite strange and rare. If you
|
||||
do not want to show the author for the revisions, then you
|
||||
should remove it from the various templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>hide_non_readable</strong></dt>
|
||||
<dd>
|
||||
This option was never used, so it has been removed.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>flip_links_in_dirview</strong></dt>
|
||||
<dd>
|
||||
This option is no longer available. If you want the links in
|
||||
your directory view flipped, then you may use the
|
||||
<code>dir_alternate.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</blockquote>
|
||||
|
||||
<h3>Template Variables</h3>
|
||||
<p>
|
||||
Some template variables that were available in 0.8 have been
|
||||
removed in 0.9. If you have custom templates that refer to these
|
||||
variables, then you will need to modify your templates.
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<dl>
|
||||
<dt><code>directory.ezt</code>: <var>headers</var></dt>
|
||||
<dd>
|
||||
The headers are now listed explicitly in the template,
|
||||
rather than made available through a list.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
<code>directory.ezt</code>:
|
||||
<var>rows.cols</var>,
|
||||
and <var>rows.span</var>
|
||||
</dt>
|
||||
<dd>
|
||||
These variables were used in conjunction with the
|
||||
<var>headers</var> variable to control the column
|
||||
displays. This is now controlled explicitly within the
|
||||
templates.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><code>directory.ezt</code>:
|
||||
<var>rev_in_front</var></dt>
|
||||
<dd>
|
||||
This was used to indicate that revision links should
|
||||
be used in the first column, rather than in their
|
||||
standard place in the second column. Changing the
|
||||
links should now be done in the template, rather than
|
||||
according to this variable. You may want to look at
|
||||
the <code>dir_alternate.ezt</code> template, which has
|
||||
the revision in front.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><code>directory.ezt</code>:
|
||||
<var>rows.attic</var>
|
||||
and <var>rows.hide_attic_href</var></dt>
|
||||
<dd>
|
||||
These variable were used to manage the hide and
|
||||
showing of the contents of the <code>Attic/</code>
|
||||
subdirectory. Several new variables were introduced
|
||||
which can be used to replace this functionality:
|
||||
<var>show_attic_href</var>,
|
||||
<var>hide_attic_href</var>, and <var>rows.state</var>.
|
||||
<p></p>
|
||||
</dd>
|
||||
</dl>
|
||||
</blockquote>
|
||||
<hr>
|
||||
|
||||
<h2><a name="from7">Upgrading from ViewCVS 0.7 or earlier</a></h2>
|
||||
<p>
|
||||
This section discusses how to upgrade ViewCVS 0.7, or earlier,
|
||||
to 0.8 or a later version of the software.
|
||||
</p>
|
||||
<p>
|
||||
<strong>NOTE:</strong> these changes will bring you up to the
|
||||
requirements of version 0.8. You must also follow the directions
|
||||
for <a href="#from8">upgrading from 0.8</a>.
|
||||
</p>
|
||||
|
||||
<h3>Configuration Options</h3>
|
||||
<p>
|
||||
The largest change from 0.7 to 0.8, that you will need to deal
|
||||
with, is the introduction of templates. This shifted many
|
||||
configuration file options into the templates, for more direct
|
||||
editing of the output style, colors, and layout. Below is a list
|
||||
of options that no longer exist, and where you can find their
|
||||
counterpart in the current version of ViewCVS.
|
||||
</p>
|
||||
<p>
|
||||
The following options have all been removed in ViewCVS 0.8. If
|
||||
you made local changes to your ViewCVS configuration, then you
|
||||
will need to edit templates in the <code>templates/</code>
|
||||
subdirectory.
|
||||
</p>
|
||||
|
||||
<blockquote>
|
||||
<dl>
|
||||
<dt>
|
||||
The [text] section:
|
||||
<strong>short_intro</strong>,
|
||||
<strong>long_intro</strong>,
|
||||
and <strong>doc_info</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>doc/help_rootview.html</code> page and the
|
||||
<code>doc/help_dirview.html</code> page.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>repository_info</strong></dt>
|
||||
<dd>
|
||||
This option is now incorporated into the
|
||||
<code>directory.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>table_padding</strong></dt>
|
||||
<dd>
|
||||
The table padding values can be changed in the
|
||||
<code>directory.ezt</code> template.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>table_border</strong></dt>
|
||||
<dd>
|
||||
Edit <code>directory.ezt</code> to add a border around the
|
||||
directory table.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
<strong>column_header_normal</strong> and
|
||||
<strong>column_header_sorted</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
Edit <code>directory.ezt</code> to modify the colors of the
|
||||
column headers.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
<strong>extern_window_width</strong> and
|
||||
<strong>extern_window_height</strong>
|
||||
</dt>
|
||||
<dd>
|
||||
These options were never used and have been removed.
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>logo</strong></dt>
|
||||
<dd>
|
||||
Edit the templates directly (<code>directory.ezt</code>,
|
||||
<code>log.ezt</code> or <code>log_table.ezt</code> and if
|
||||
needed <code>query.ezt</code>) to alter the URL and size of
|
||||
your logo.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>allow_version_select</strong></dt>
|
||||
<dd>
|
||||
Edit the <code>log.ezt</code> template if you want to remove
|
||||
the link which allows the user to select a revision for a
|
||||
diff.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>input_text_size</strong></dt>
|
||||
<dd>
|
||||
Edit the <code>log.ezt</code> template if you want to change
|
||||
the size of the entry box for revisions for performing
|
||||
diffs.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt><strong>even_odd</strong></dt>
|
||||
<dd>
|
||||
Edit the <code>directory.ezt</code> and
|
||||
<code>query.ezt</code> templates if you want to change the
|
||||
colors of the rows in the directory and query result tables.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
<dt>Colors: <strong>text</strong>
|
||||
and <strong>background</strong></dt>
|
||||
<dd>
|
||||
These options have been incorporated into the
|
||||
<code>directory.ezt</code>, <code>log.ezt</code>, and
|
||||
<code>log_table.ezt</code> templates.
|
||||
|
||||
<p></p>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</blockquote>
|
||||
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
|
||||
<!-- Created: Mon Sep 24 04:23:53 PDT 2001 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Sat Dec 22 20:05:14 PST 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
59
website/who.html
Normal file
59
website/who.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>The ViewCVS Group</title>
|
||||
</head>
|
||||
|
||||
<body background="images/chalk.jpg">
|
||||
<table width="100%" cellspacing=5>
|
||||
<tr>
|
||||
<td width="1%"><a href="index.html"><img border=0
|
||||
src="images/logo.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
<h1>The ViewCVS Group</h1>
|
||||
</td>
|
||||
<td width="1%"><a href="http://sourceforge.net/"><img border=0
|
||||
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS project page</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="1%" valign=top>
|
||||
<a href="index.html">Overview</a><br>
|
||||
<a href="upgrading.html">Upgrading</a><br>
|
||||
<a href="contributing.html">Contributing</a><br>
|
||||
<a href="license-1.html">License</a><br>
|
||||
Who<br>
|
||||
</td><td colspan=2>
|
||||
|
||||
|
||||
<p>
|
||||
The ViewCVS Group is an informal group of people working on and
|
||||
developing the ViewCVS package. The current set of members are:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="http://www.lyra.org/greg/"><b>Greg Stein</b></a></li>
|
||||
<li>Tanaka Akira</li>
|
||||
<li>Tim Cera</li>
|
||||
<li>Peter Funk</li>
|
||||
<li>Jay Painter</li>
|
||||
</ul>
|
||||
<p>
|
||||
In 2001 the project has been moved to SourceForge and some
|
||||
<a href="http://sourceforge.net/project/memberlist.php?group_id=18760">more
|
||||
developers</a> were given commit access.
|
||||
</p>
|
||||
<p>
|
||||
Please note that the <a href="./">ViewCVS</a> package is offered
|
||||
under a BSD-type license, which is detailed on the
|
||||
<a href="license-1.html">ViewCVS License</a> page.
|
||||
</p>
|
||||
|
||||
</td></tr></table>
|
||||
<hr>
|
||||
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
|
||||
<!-- Created: Mon May 8 19:08:58 PDT 2000 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Thu Oct 25 01:47:02 PDT 2001
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user