# # # add_dir "www/viewmtn" # # add_dir "www/viewmtn/MochiKit" # # add_dir "www/viewmtn/grapher" # # add_dir "www/viewmtn/mimetypes" # # add_dir "www/viewmtn/templates" # # add_dir "www/viewmtn/tests" # # add_file "www/viewmtn/.htaccess" # content [696ab9f90e6cae432c3f8628a91c765433f00756] # # add_file "www/viewmtn/AUTHORS" # content [efc3dd1070798deb1fc2a2312204cc2f9faf00d4] # # add_file "www/viewmtn/ChangeLog" # content [c16ac2013e466b7eafcdf54b808052056d2f3e89] # # add_file "www/viewmtn/LICENSE" # content [dfac199a7539a404407098a2541b9482279f690d] # # add_file "www/viewmtn/MochiKit/MochiKit.js" # content [db162319790852ff3e02f6e673b729a3c262f4ec] # # add_file "www/viewmtn/MochiKit/__package__.js" # content [6f05da1941ddb410b89f26ea4ea4e72c235ceaed] # # add_file "www/viewmtn/README" # content [46ffe1ce5080c9fd0081574ae8a8da30c3df65dc] # # add_file "www/viewmtn/TODO" # content [6372d06a8696794b11e1f5dece588de4d1057896] # # add_file "www/viewmtn/about.psp" # content [92120d31e268182f68917b20874c60f8f0ef70d2] # # add_file "www/viewmtn/authors.py" # content [0ed0661cf3ddf1b402a73dfc1ff0fccc64ad05f1] # # add_file "www/viewmtn/branch.psp" # content [08a0e167ebb6416ca636a59ae0bb4ac020b148af] # # add_file "www/viewmtn/builtpython.sh" # content [c1d18a362622ec209ea6818957a5778d5374de45] # # add_file "www/viewmtn/common.py" # content [19a25a6ea138661ce01454ec58c1ea20fcd884aa] # # add_file "www/viewmtn/config.py.example" # content [7627e154e30ee6e88e547312876aa5cca8bf7c7e] # # add_file "www/viewmtn/diff.psp" # content [4c495def64a5c64f8da213eaa870663f859b404e] # # add_file "www/viewmtn/enscriptlangs.py" # content [95c443cac402a883b7f523a46faba66d2c79caaf] # # add_file "www/viewmtn/error.psp" # content [312eeeb8ea00ab36b0b5d64eb8fa5b65e75cd418] # # add_file "www/viewmtn/file.psp" # content [f19f6fefe38a97ca636705f070f4535534947a48] # # add_file "www/viewmtn/fileinbranch.psp" # content [4f01b7f1260ca7a9c756047ea0dcfaa5fabc4b0c] # # add_file "www/viewmtn/grapher/grapher.py" # content [5d38fbee117ab4cf50f8d9fb90050a29d8fa3c4d] # # add_file "www/viewmtn/headofbranch.psp" # content [f2f0d9d64ffa71086b2451fa0eb994d40fe35db6] # # add_file "www/viewmtn/help.psp" # content [02a65e870a2cfc34680ba57740ea4fe9f82a48c7] # # add_file "www/viewmtn/html.py" # content [8344c7b901ccd05b338a1322b0c0b57998985fcc] # # add_file "www/viewmtn/index.psp" # content [0b257820d558b3a44081ccba10279e76d9e82555] # # add_file "www/viewmtn/json.py" # content [8011edc26270fc6ae7e34887279cb08479418368] # # add_file "www/viewmtn/manifest.psp" # content [722b15a5c3eeca08a8415264c691f2f1966b0cbc] # # add_file "www/viewmtn/mimetypes/gnome-fs-directory.png" # content [f79ed53a7400ffe50afbbbb085221efb4857caec] # # add_file "www/viewmtn/mimetypes/gnome-library.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-msword.png" # content [e1112f34189ff864d2b8edf22d44fcdec9a5fbf3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-ogg.png" # content [1693339c452cdf728566dfeb8b6d3ffca37f61f3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-par.png" # content [131842477237314ceacac51b8d16899a9a4bf69b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pdf.png" # content [4b6c29ff36f567c3fce222ef3332c9f7521fb2f1] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png" # content [0973c972918fc59fd6beb167401a3ee02dcc9c38] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png" # content [af8e3c600348a7d60b68f15da96986694ed48148] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-pgp.png" # content [4d0507cd5023c4d4c7344579fcdc9bc06e5f41e7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-postscript.png" # content [38ed48c0066385e1952697e5d2780b0c4a594d22] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-qif.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png" # content [91e77f6b28f956b3fae1b303ce1c5d9e9012eb3b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png" # content [437e838a2c0ec07c28fdff8333db59ccfc59ad40] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-rtf.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-smil.png" # content [02f0c0f4ce3fdb88a273c594fc446e111adf9160] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png" # content [b5683645633e56bea766aa54fe3642c831eb18a5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png" # content [c1cf7fd30c5301e50f24a6683414ad9fc908a9f7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png" # content [f790c027f1a99367ef8198f8bee39545034a36cd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png" # content [e1112f34189ff864d2b8edf22d44fcdec9a5fbf3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png" # content [8c242711a9550d15e763272b8d07fe9589e3954f] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png" # content [39f4a1531594cdb2b2adb28324d621c7e8a37870] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png" # content [739d087d785e1e42d2322a029f64bbfc06f1f737] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png" # content [9810bc38085f24aa49208ca138dff5171bc1e105] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png" # content [8b880870364d822ccdd7f0a8ebb9947f7fcea941] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png" # content [b5683645633e56bea766aa54fe3642c831eb18a5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png" # content [a286e242089888b1c185e73575850cb1b80e0de3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-archive.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-arj.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png" # content [75eabe830158329f465cc1cd64fc69048b2e8780] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bla.png" # content [42afef70502d28ac2cdff4638f168e195085eed5] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blender.png" # content [43dc9850549ed898502ac12f9c38e749cd52529c] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blf.png" # content [19242cf6b4d5531c96c2282859d403ce255892bf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-blv.png" # content [19242cf6b4d5531c96c2282859d403ce255892bf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png" # content [8e5f068b2026b9da64555c47bb4bfac05e3f5a18] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png" # content [c9ff30653096ffbcafb308446c2e6fafe9c272d9] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-compress.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-core.png" # content [d5780e52bd7d33b580d149d4930e80320c694346] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-deb.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png" # content [456ed7f266c5de63d6128f441b32445f83785c8b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png" # content [84514d7aae7eb6f94b787642985708f2fc29c72d] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dv.png" # content [7a0ec215aa2b8469fbd9d414b439a8338f4c51fc] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png" # content [c8a083a1554f6a0892bc0cfb9d19091347319da7] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-executable.png" # content [79c7b7baf3a7f1fd0b08f72ad823c497f40a5485] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png" # content [131842477237314ceacac51b8d16899a9a4bf69b] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png" # content [ad93bc3d05f87a5a740ead51d6fd08549e8dc5d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png" # content [ef29014d5b82cc5b9f032992a1801cab703fbaed] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png" # content [ad93bc3d05f87a5a740ead51d6fd08549e8dc5d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png" # content [f165b959b5b75ed8c345758e810018e06f307c0d] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png" # content [ef11f15a3fc7a9b626348f7fc6da17c31cb5b883] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-glade.png" # content [f1c95cffc82943a2c1dda13d3dde427ce3e567e0] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png" # content [a27c59f6252100d0ff754c233d625b419ea94b8a] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png" # content [7cafdcfe9aebcdb39f41d2a008d27022f373bf38] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png" # content [3af3bf3a96bf2f10056d7f02387518e730972322] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-jar.png" # content [6692e92dc5db912423752071dc28baa565dbd0dd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png" # content [8ef47847c64047e2ca3617f004175bd3393d86cf] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png" # content [9c967a00b93b709a78902a194fa9ace44564fdbd] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png" # content [63ce8f4fab889bafc7df12c842c31d41e4ae7711] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-kword.png" # content [6676c3fe23bee4fc6d90f2db44c126315ad51583] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-lha.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png" # content [4ae99b14dd1d66176da71f49715ff1face60087e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-object.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-perl.png" # content [8cf2de3f4126c9910e5e76956ece641b27cd3c2e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-php.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png" # content [e1ca7e759d348f3efb508fcf6bdb48ae707189d3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-python.png" # content [e1ca7e759d348f3efb508fcf6bdb48ae707189d3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-qw.png" # content [28567d468893846922cdee08431623efef56d05e] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-rar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-reject.png" # content [4af5c0c2ab2a9db8c923144f3821a214fd49edab] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png" # content [2ca2fd30c5f6069a82caed12233c4ef0d942cb84] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png" # content [ebd329284c6e1795dad94eb38a21e51f56935e59] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png" # content [3681109a34a94241b36206c5b89907e9e3713f27] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png" # content [1c3bf562aeb737e47bd8afee682c17242ec80d98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png" # content [7124ebd35822064f481bbd42a6e9b40c8ed70a98] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-sql.png" # content [d4311b0c14f8aea2a4355e06b62a73e545a1b230] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png" # content [5908130299934cfcc315b344fb9c5ff58db6b743] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-tar.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-tex.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-trash.png" # content [31d6a4e66859ca048c936d42008828547dc924d2] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png" # content [1e9b7b2dc52bbf4a671db2f840472bc4a8aebdeb] # # add_file "www/viewmtn/mimetypes/gnome-mime-application-zip.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/gnome-mime-application.png" # content [720047d3ca1bc3732209ca22375224e84ced5ffc] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-ac3.png" # content [7a968533b90ee52d29f974a0b984008e11e3b197] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-basic.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-midi.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png" # content [62b5e1a199a7cd6bcb4994b871b5f5eaa3773814] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-it.png" # content [8253bd3c980fd20458b228963dd3d1680ff5f90d] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png" # content [503ac481576798bf1c252379e8b5e3ccf9d076fe] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png" # content [87718ba80b769468fac73d151201bd93b988db33] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png" # content [a1349a504ebc0a0430fed2ca06d2b9e16e2d1129] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png" # content [e3ab485e60bec66b954f2de8057bc4c6a56bf107] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png" # content [1a9a9beb8154f8b28896b6bdd95f9bd6a1233afd] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png" # content [23a589c2adf360b4dee4892e7b2189f1ad414960] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png" # content [8d41654fac8a0661a7cc080970621ef21062193a] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png" # content [88a2b15d50b108d9b143ff2bbc961ff3cb260c55] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png" # content [d069dfa614727fcde17ba56c13215a666eb1d0c6] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png" # content [5f2977093bb4c63a6e1662170ec3ecfb4ef5be89] # # add_file "www/viewmtn/mimetypes/gnome-mime-audio.png" # content [f9a2fd7fd93a96a811de884c6d45329c9a832493] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-bmp.png" # content [b242be3384e0d1d6bb187ac6eba78966f98dfea5] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-gif.png" # content [1ac422e10e6d1b87681a73fadcc7e06cee44f6ec] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-jpeg.png" # content [a91d9172010836be94c5da85a4e582c8b0e3086c] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-png.png" # content [d73ddf225c6195d32cb68abdd3d1d2b7c0eca0f0] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png" # content [1a0212e4ff3a2b5d2e3c0b3665942b11712f24ca] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-svg.png" # content [f0972930316fc2dfac32bfc45040089f73bf4c01] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-tiff.png" # content [dc1514a3b2181662d10aae62404258702db2ed06] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-wmf.png" # content [3dd0e3e4b6051e17f10650e8b2487d72b3438230] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png" # content [a80f834ce6f7da4fffc9863e33d5796d213bca3f] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png" # content [b5ef331f879718da43a5529ea3b83deaf3b905f3] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png" # content [b563e9550b55017c7b266d4de80c6b696642cdf9] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png" # content [94287f107b6ed6ff32d3eec34e712029eef90eaa] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-lws.png" # content [d8f646deb6bd9a130aadad5732a2a2137149e4b4] # # add_file "www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png" # content [b1ed95e9eb5579fe0c710b84d065b06e9d698113] # # add_file "www/viewmtn/mimetypes/gnome-mime-image.png" # content [8bacc8bae4d551bd9548981c35c01b7867ce87ac] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-css.png" # content [534aa3cfa7e87ba71b3f279369016dcc78591a5c] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-html.png" # content [534aa3cfa7e87ba71b3f279369016dcc78591a5c] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-authors.png" # content [944f28ee96f2d9a6ecc0dea253990fa25fe25807] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png" # content [c43823aef326aa88ec29c1661ad19c33be4d4b70] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-c.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png" # content [c43823aef326aa88ec29c1661ad19c33be4d4b70] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-copying.png" # content [1e9b7b2dc52bbf4a671db2f840472bc4a8aebdeb] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-credits.png" # content [944f28ee96f2d9a6ecc0dea253990fa25fe25807] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csh.png" # content [79c7b7baf3a7f1fd0b08f72ad823c497f40a5485] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png" # content [309944a912142103ef93853e2f7fe115fef3ee19] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png" # content [41e2702ff9aebff23bb3ab745d605d273caa9f2f] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-install.png" # content [458303f02450bff93bca68f1e3b5c2c6dc23b0a7] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-java.png" # content [a3b64323cf44fc00e659d3d926abe130789b2b54] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png" # content [41e2702ff9aebff23bb3ab745d605d273caa9f2f] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png" # content [7125d2793f43014235d3a73fc4a8025c562395ec] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png" # content [37ec02d20fe9be5ebce54bf732eb80789db54e18] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png" # content [9d8175b9b5561fd940bdbb176707846ab39f0212] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-patch.png" # content [17f158169a515bbe08c4980b12a2404aa70c7db4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-readme.png" # content [9ca1f04881a070880fdece18c748222e439d96f7] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png" # content [72861f75f24eb30e25c0b455606c76816d697b3a] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-sql.png" # content [9fa1802efbdcbc8c28219db2ef999fb48e97a4c4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-tex.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png" # content [d49baf527b02e113d0bcf5fd91975690a6108b24] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-txt.png" # content [7934576ac8828ad3e9fb2a781c6eb7ce4bf6dcea] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png" # content [3857f66f89d54022741c23b78bcbce0167fa9ecc] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png" # content [7deb5dc19c36923a10a8b18b54c26885829ab0a4] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png" # content [5905b531efd06bcac7fe88cddfcf89093fcde27e] # # add_file "www/viewmtn/mimetypes/gnome-mime-text-xml.png" # content [e3a454d829935a7f5bf466367a92b77a31c4acf0] # # add_file "www/viewmtn/mimetypes/gnome-mime-text.png" # content [cdb065454549392e47e63a1070b42feee806a1de] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-mpeg.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-quicktime.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-video.png" # content [09f6566f2c1a2d395af353dbd86a550cb6233abe] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png" # content [c08ada537f4211734ec93ffe5b7e99db9c0e3a24] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png" # content [2e2122d5bb87c6589fd42c353a2d68a944c14123] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png" # content [d79a265bcf526b8dc6627433cc1d427521cf60b3] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png" # content [67c53c04c13c3b5db93a07322d173ff34d0bae9f] # # add_file "www/viewmtn/mimetypes/gnome-mime-x-font-afm.png" # content [94eb3dabef44920053f00e6c5146fc9a9ece66d7] # # add_file "www/viewmtn/mimetypes/gnome-package.png" # content [84f46f2d1fe05ffa9cf3c2031351159ae82fb4e3] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-database.png" # content [75af2189c6086d38c16cf1da70ae820f61a91304] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png" # content [4926861dd6bcc4837f5200793a26712e92479070] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-drawing.png" # content [46234e776bb71be41b2a2301c658d573692dac87] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-formula.png" # content [68974d489be536a81223d4d71436f7050b85feec] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-master-document.png" # content [31b5b33e1c2caf0f7fc314293ac2104ac2e82d93] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png" # content [fb0e854ec6d6a983d53d2bd2ef5aff2c02960766] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png" # content [67ff9b09a567ee9d04276bbe54cea47aa32593be] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png" # content [c743b78d95bbcb82a7a0aab3287eec6ab8d83bf5] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png" # content [89b5b5b574966e84d34cc192829c51e81e724659] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png" # content [bdb0a62d8efb12dbc34599481ef3e122f9cc4be2] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png" # content [8de665dac190c60219d18c13ff1ff313f97960f5] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png" # content [27dd1f4b90dece268b3d7589ff0125bfd0712cfb] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png" # content [d51e92c30ddc424ed2b13f3589de7c2e894431f6] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png" # content [24e9fa7049318165e13acac08452a55e08a8d7b0] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png" # content [0752fa6fa92e01497298d0eaaba375e0b2a3404a] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png" # content [ae00b6f2eaac52ab6188b27ddba261251a7dc470] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png" # content [5df111ae4802669436855769381590fc64db4cba] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png" # content [5d5ff5bc82de286dd7e8857ad92d6e832f1d90cb] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-presentation.png" # content [bf141c3cbc5ccc8f771d6f41e3f989b703539641] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png" # content [cefe2bbd334ab8eddedb2e92f7d49a4e7858fdb2] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png" # content [9bb1e9e5d3457a33b844c4acbc2336731a0a5325] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-text-template.png" # content [5296ce7641867a68669ee3d9bbedd14f30a0941d] # # add_file "www/viewmtn/mimetypes/openofficeorg-19-text.png" # content [5ee318f65c862137f6f52240daa3105f454935ea] # # add_file "www/viewmtn/monotone.py" # content [f8211dca24b2b5e48c89d5e88d95388a683282b7] # # add_file "www/viewmtn/mtn.py" # content [9aacc29cd3af6277ef411f7e582a450f58c727ee] # # add_file "www/viewmtn/revision.psp" # content [b5b23a31a00ed137eaa2d46681966ad758efc90b] # # add_file "www/viewmtn/rss_feed.gif" # content [027515fd4558abf317d54c437b83ec6bc76e3dd8] # # add_file "www/viewmtn/tags.psp" # content [7b81023a3fbf60ce0ed258d7c9b9086b233e4e8d] # # add_file "www/viewmtn/tarofbranch.psp" # content [08acdaaf0dbe9111f0053d02b91b4e814c62c798] # # add_file "www/viewmtn/templates/about.html" # content [6b1c55564ae50e2de59d5eda418bfad86f0b596d] # # add_file "www/viewmtn/templates/base.html" # content [828ac669dcf846b15ebedc558c7da907079dd685] # # add_file "www/viewmtn/templates/index.html" # content [08145fdb8457c0f05ead0cdec4717e0f7f0de864] # # add_file "www/viewmtn/tests/automate.py" # content [f04942987a6f0f4c71b14d9be69256d5dffa2bfa] # # add_file "www/viewmtn/utility.py" # content [78cd2c53af05e63bb76c097dc832b6f75394e40d] # # add_file "www/viewmtn/version.py" # content [a071b2192e2092ec222ab0d59ef95efd1c9c81e6] # # add_file "www/viewmtn/viewmtn.css" # content [6efcadac0d56fb3d77a22786f477ba621f4af33d] # # add_file "www/viewmtn/viewmtn.js" # content [b36f4e737e9b054f563c320becbc74d7a4c1df48] # # add_file "www/viewmtn/viewmtn.py" # content [574970773ca270f6282d13161905dae81274170b] # # add_file "www/viewmtn/web.py" # content [4c396c690078ecd159e98a016dd3c9de67d63f42] # # add_file "www/viewmtn/wrapper.py" # content [dbbc871353ddfec1b56bf57b743a6b5cd70688c2] # # set "www/viewmtn/builtpython.sh" # attr "mtn:execute" # value "true" # # set "www/viewmtn/grapher/grapher.py" # attr "mtn:execute" # value "true" # # set "www/viewmtn/mimetypes/gnome-fs-directory.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-library.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-msword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-ogg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-par.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pdf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-pgp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-postscript.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-qif.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-rtf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-smil.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-archive.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-arj.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bla.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blender.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-blv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-compress.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-core.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-deb.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-executable.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-glade.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-jar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-kword.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-lha.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-object.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-perl.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-php.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-python.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-qw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-rar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-reject.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-sql.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-tar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-tex.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-trash.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application-zip.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-application.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-ac3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-basic.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-midi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-it.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-audio.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-bmp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-gif.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-jpeg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-png.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-svg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-tiff.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-wmf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-lws.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-image.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-css.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-html.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-authors.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-c.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-copying.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-credits.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csh.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-install.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-java.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-patch.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-readme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-sql.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-tex.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-txt.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text-xml.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-mpeg.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-quicktime.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-video.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-mime-x-font-afm.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/gnome-package.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-database.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-drawing.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-formula.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-master-document.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-presentation.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-text-template.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/mimetypes/openofficeorg-19-text.png" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/rss_feed.gif" # attr "mtn:manual_merge" # value "true" # # set "www/viewmtn/tests/automate.py" # attr "mtn:execute" # value "true" # # set "www/viewmtn/viewmtn.py" # attr "mtn:execute" # value "true" # # set "www/viewmtn/web.py" # attr "mtn:execute" # value "true" # ============================================================ --- www/viewmtn/.htaccess 696ab9f90e6cae432c3f8628a91c765433f00756 +++ www/viewmtn/.htaccess 696ab9f90e6cae432c3f8628a91c765433f00756 @@ -0,0 +1,7 @@ +AddHandler mod_python .psp +Options FollowSymLinks +PythonHandler wrapper +PythonInterpreter viewmtn +AddHandler mod_python .py +DirectoryIndex index.psp +PythonAutoReload On ============================================================ --- www/viewmtn/AUTHORS efc3dd1070798deb1fc2a2312204cc2f9faf00d4 +++ www/viewmtn/AUTHORS efc3dd1070798deb1fc2a2312204cc2f9faf00d4 @@ -0,0 +1,10 @@ +Authors: +Grahame Bowland + +Contributors: +Matt Johnston +Nathaniel Smith +Bruce Stephens +Lapo Luchini +David Reiss + ============================================================ --- www/viewmtn/ChangeLog c16ac2013e466b7eafcdf54b808052056d2f3e89 +++ www/viewmtn/ChangeLog c16ac2013e466b7eafcdf54b808052056d2f3e89 @@ -0,0 +1,42 @@ +2005-11-21 Grahame Bowland + + * release 0.05 + * requires Monotone 0.23 or greater + * new file browser + * branch view showing recent changes, including RSS support + * source highlighting file view + * much heavier use of "monotone automate stdio"; significant + reduction in number of "monotone" processes created to service + requests. + * requires monotone 0.24; 0.23 will work, however you'll find + "monotone automate stdio" process tend to leak due to bugs in + monotone. + +2005-05-21 Grahame Bowland + + * release 0.03 + * fileinbranch.psp&branch=net.angrygoats.interapplet&path=ChangeLog + links to version of file in HEAD + * tarofbranch.psp&branch=net.angrygoats.interapplet + links to tar of HEAD manifest + * now works with monotone 0.19 + * branches and tags pages split + * user interface improved, help added + * ancestry graph direction fixed (thanks to Bruce Stephens) + * ancestry graph much improved (thanks to Emile Snyder) + * ancestry graph colours nodes using an algorithm nicked + from monotone-viz (with tweaks from Matt Johnston) + * abbreviate hashes, clean up linking code. + +2005-04-26 Grahame Bowland + + * release 0.02 + * fixed bug where monotone processes were left running as childs + of Apache. + * changed the ancestry to be a directed graph + * added blue highlight on target revision + +2005-04-25 Grahame Bowland + + * release 0.01 + ============================================================ --- www/viewmtn/LICENSE dfac199a7539a404407098a2541b9482279f690d +++ www/viewmtn/LICENSE dfac199a7539a404407098a2541b9482279f690d @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. ============================================================ --- www/viewmtn/MochiKit/MochiKit.js db162319790852ff3e02f6e673b729a3c262f4ec +++ www/viewmtn/MochiKit/MochiKit.js db162319790852ff3e02f6e673b729a3c262f4ec @@ -0,0 +1,4123 @@ +/*** + + MochiKit.MochiKit 1.2 : PACKED VERSION + + THIS FILE IS AUTOMATICALLY GENERATED. If creating patches, please + diff against the source tree, not this file. + + See for documentation, downloads, license, etc. + + (c) 2005 Bob Ippolito. All rights Reserved. + +***/ + +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Base"); +} +if(typeof (MochiKit)=="undefined"){ +MochiKit={}; +} +if(typeof (MochiKit.Base)=="undefined"){ +MochiKit.Base={}; +} +MochiKit.Base.VERSION="1.2"; +MochiKit.Base.NAME="MochiKit.Base"; +MochiKit.Base.update=function(_1,_2){ +if(_1==null){ +_1={}; +} +for(var i=1;i>b; +},zrshift:function(a,b){ +return a>>>b; +},eq:function(a,b){ +return a==b; +},ne:function(a,b){ +return a!=b; +},gt:function(a,b){ +return a>b; +},ge:function(a,b){ +return a>=b; +},lt:function(a,b){ +return al){ +_37=l; +} +} +_36=[]; +for(i=0;i<_37;i++){ +var _38=[]; +for(var j=1;j0){ +_51=m.concat(me.im_preargs,_51); +} +var _46=me.im_self; +if(!_46){ +_46=this; +} +return me.im_func.apply(_46,_51); +}; +_50.im_self=_49; +_50.im_func=_47; +_50.im_preargs=_48; +return _50; +},bindMethods:function(_52){ +var _53=MochiKit.Base.bind; +for(var k in _52){ +var _54=_52[k]; +if(typeof (_54)=="function"){ +_52[k]=_53(_54,_52); +} +} +},registerComparator:function(_55,_56,_57,_58){ +MochiKit.Base.comparatorRegistry.register(_55,_56,_57,_58); +},_primitives:{"bool":true,"string":true,"number":true},compare:function(a,b){ +if(a==b){ +return 0; +} +var _59=(typeof (a)=="undefined"||a==null); +var _60=(typeof (b)=="undefined"||b==null); +if(_59&&_60){ +return 0; +}else{ +if(_59){ +return -1; +}else{ +if(_60){ +return 1; +} +} +} +var m=MochiKit.Base; +var _61=m._primitives; +if(!(typeof (a) in _61&&typeof (b) in _61)){ +try{ +return m.comparatorRegistry.match(a,b); +} +catch(e){ +if(e!=m.NotFound){ +throw e; +} +} +} +if(ab){ +return 1; +} +} +var _62=m.repr; +throw new TypeError(_62(a)+" and "+_62(b)+" can not be compared"); +},compareDateLike:function(a,b){ +return MochiKit.Base.compare(a.getTime(),b.getTime()); +},compareArrayLike:function(a,b){ +var _63=MochiKit.Base.compare; +var _64=a.length; +var _65=0; +if(_64>b.length){ +_65=1; +_64=b.length; +}else{ +if(_640))){ +var kv=MochiKit.DOM.formContents(_103); +_103=kv[0]; +_104=kv[1]; +}else{ +if(arguments.length==1){ +var o=_103; +_103=[]; +_104=[]; +for(var k in o){ +var v=o[k]; +if(typeof (v)!="function"){ +_103.push(k); +_104.push(v); +} +} +} +} +var rval=[]; +var len=Math.min(_103.length,_104.length); +var _108=MochiKit.Base.urlEncode; +for(var i=0;i=stop){ +throw self.StopIteration; +} +_137+=step; +return rval; +}}; +},imap:function(fun,p,q){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +var _141=m.map(self.iter,m.extend(null,arguments,1)); +var map=m.map; +var next=self.next; +return {repr:function(){ +return "imap(...)"; +},toString:m.forward("repr"),next:function(){ +return fun.apply(this,map(next,_141)); +}}; +},applymap:function(fun,seq,self){ +seq=MochiKit.Iter.iter(seq); +var m=MochiKit.Base; +return {repr:function(){ +return "applymap(...)"; +},toString:m.forward("repr"),next:function(){ +return fun.apply(self,seq.next()); +}}; +},chain:function(p,q){ +var self=MochiKit.Iter; +var m=MochiKit.Base; +if(arguments.length==1){ +return self.iter(arguments[0]); +} +var _143=m.map(self.iter,arguments); +return {repr:function(){ +return "chain(...)"; +},toString:m.forward("repr"),next:function(){ +while(_143.length>1){ +try{ +return _143[0].next(); +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +_143.shift(); +} +} +if(_143.length==1){ +var arg=_143.shift(); +this.next=m.bind(arg.next,arg); +return this.next(); +} +throw self.StopIteration; +}}; +},takewhile:function(pred,seq){ +var self=MochiKit.Iter; +seq=self.iter(seq); +return {repr:function(){ +return "takewhile(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +var rval=seq.next(); +if(!pred(rval)){ +this.next=function(){ +throw self.StopIteration; +}; +this.next(); +} +return rval; +}}; +},dropwhile:function(pred,seq){ +seq=MochiKit.Iter.iter(seq); +var m=MochiKit.Base; +var bind=m.bind; +return {"repr":function(){ +return "dropwhile(...)"; +},"toString":m.forward("repr"),"next":function(){ +while(true){ +var rval=seq.next(); +if(!pred(rval)){ +break; +} +} +this.next=bind(seq.next,seq); +return rval; +}}; +},_tee:function(_145,sync,_147){ +sync.pos[_145]=-1; +var m=MochiKit.Base; +var _148=m.listMin; +return {repr:function(){ +return "tee("+_145+", ...)"; +},toString:m.forward("repr"),next:function(){ +var rval; +var i=sync.pos[_145]; +if(i==sync.max){ +rval=_147.next(); +sync.deque.push(rval); +sync.max+=1; +sync.pos[_145]+=1; +}else{ +rval=sync.deque[i-sync.min]; +sync.pos[_145]+=1; +if(i==sync.min&&_148(sync.pos)!=sync.min){ +sync.min+=1; +sync.deque.shift(); +} +} +return rval; +}}; +},tee:function(_149,n){ +var rval=[]; +var sync={"pos":[],"deque":[],"max":-1,"min":-1}; +if(arguments.length==1){ +n=2; +} +var self=MochiKit.Iter; +_149=self.iter(_149); +var _tee=self._tee; +for(var i=0;i0&&_155>=stop)||(step<0&&_155<=stop)){ +throw MochiKit.Iter.StopIteration; +} +var rval=_155; +_155+=step; +return rval; +},repr:function(){ +return "range("+[_155,stop,step].join(", ")+")"; +},toString:MochiKit.Base.forward("repr")}; +},sum:function(_156,_157){ +var x=_157||0; +var self=MochiKit.Iter; +_156=self.iter(_156); +try{ +while(true){ +x+=_156.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +return x; +},exhaust:function(_158){ +var self=MochiKit.Iter; +_158=self.iter(_158); +try{ +while(true){ +_158.next(); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +},forEach:function(_159,func,self){ +var m=MochiKit.Base; +if(arguments.length>2){ +func=m.bind(func,self); +} +if(m.isArrayLike(_159)){ +for(var i=0;i<_159.length;i++){ +func(_159[i]); +} +}else{ +self=MochiKit.Iter; +self.exhaust(self.imap(func,_159)); +} +},every:function(_161,func){ +var self=MochiKit.Iter; +try{ +self.ifilterfalse(func,_161).next(); +return false; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return true; +} +},sorted:function(_162,cmp){ +var rval=MochiKit.Iter.list(_162); +if(arguments.length==1){ +cmp=MochiKit.Base.compare; +} +rval.sort(cmp); +return rval; +},reversed:function(_163){ +var rval=MochiKit.Iter.list(_163); +rval.reverse(); +return rval; +},some:function(_164,func){ +var self=MochiKit.Iter; +try{ +self.ifilter(func,_164).next(); +return true; +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +return false; +} +},iextend:function(lst,_165){ +if(MochiKit.Base.isArrayLike(_165)){ +for(var i=0;i<_165.length;i++){ +lst.push(_165[i]); +} +}else{ +var self=MochiKit.Iter; +_165=self.iter(_165); +try{ +while(true){ +lst.push(_165.next()); +} +} +catch(e){ +if(e!=self.StopIteration){ +throw e; +} +} +} +return lst; +},groupby:function(_166,_167){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_167=m.operator.identity; +} +_166=self.iter(_166); +var pk=undefined; +var k=undefined; +var v; +function fetch(){ +v=_166.next(); +k=_167(v); +} +function eat(){ +var ret=v; +v=undefined; +return ret; +} +var _170=true; +return {repr:function(){ +return "groupby(...)"; +},next:function(){ +while(k==pk){ +fetch(); +if(_170){ +_170=false; +break; +} +} +pk=k; +return [k,{next:function(){ +if(v==undefined){ +fetch(); +} +if(k!=pk){ +throw self.StopIteration; +} +return eat(); +}}]; +}}; +},groupby_as_array:function(_171,_172){ +var m=MochiKit.Base; +var self=MochiKit.Iter; +if(arguments.length<2){ +_172=m.operator.identity; +} +_171=self.iter(_171); +var _173=[]; +var _174=true; +var _175; +while(true){ +try{ +var _176=_171.next(); +var key=_172(_176); +} +catch(e){ +if(e==self.StopIteration){ +break; +} +throw e; +} +if(_174||key!=_175){ +var _177=[]; +_173.push([key,_177]); +} +_177.push(_176); +_174=false; +_175=key; +} +return _173; +},arrayLikeIter:function(_178){ +var i=0; +return {repr:function(){ +return "arrayLikeIter(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +if(i>=_178.length){ +throw MochiKit.Iter.StopIteration; +} +return _178[i++]; +}}; +},hasIterateNext:function(_179){ +return (_179&&typeof (_179.iterateNext)=="function"); +},iterateNextIter:function(_180){ +return {repr:function(){ +return "iterateNextIter(...)"; +},toString:MochiKit.Base.forward("repr"),next:function(){ +var rval=_180.iterateNext(); +if(rval===null||rval===undefined){ +throw MochiKit.Iter.StopIteration; +} +return rval; +}}; +}}); +MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",]; +MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"]; +MochiKit.Iter.__new__=function(){ +var m=MochiKit.Base; +this.StopIteration=new m.NamedError("StopIteration"); +this.iteratorRegistry=new m.AdapterRegistry(); +this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter); +this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Iter.__new__(); +reduce=MochiKit.Iter.reduce; +MochiKit.Base._exportSymbols(this,MochiKit.Iter); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Logging"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.Logging depends on MochiKit.Base!"; +} +if(typeof (MochiKit.Logging)=="undefined"){ +MochiKit.Logging={}; +} +MochiKit.Logging.NAME="MochiKit.Logging"; +MochiKit.Logging.VERSION="1.2"; +MochiKit.Logging.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Logging.toString=function(){ +return this.__repr__(); +}; +MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"]; +MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"]; +MochiKit.Logging.LogMessage=function(num,_182,info){ +this.num=num; +this.level=_182; +this.info=info; +this.timestamp=new Date(); +}; +MochiKit.Logging.LogMessage.prototype={repr:function(){ +var m=MochiKit.Base; +return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")"; +},toString:MochiKit.Base.forward("repr")}; +MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_184){ +var self=MochiKit.Logging; +if(typeof (_184)=="string"){ +_184=self.LogLevel[_184]; +} +return function(msg){ +var _186=msg.level; +if(typeof (_186)=="string"){ +_186=self.LogLevel[_186]; +} +return _186>=_184; +}; +},isLogMessage:function(){ +var _187=MochiKit.Logging.LogMessage; +for(var i=0;i=0&&this._messages.length>this.maxSize){ +this._messges.shift(); +} +},getMessages:function(_196){ +var _197=0; +if(!(typeof (_196)=="undefined"||_196==null)){ +_197=Math.max(0,this._messages.length-_196); +} +return this._messages.slice(_197); +},getMessageText:function(_198){ +if(typeof (_198)=="undefined"||_198==null){ +_198=30; +} +var _199=this.getMessages(_198); +if(_199.length){ +var lst=map(function(m){ +return "\n ["+m.num+"] "+m.level+": "+m.info.join(" "); +},_199); +lst.unshift("LAST "+_199.length+" MESSAGES:"); +return lst.join(""); +} +return ""; +},debuggingBookmarklet:function(_200){ +if(typeof (MochiKit.LoggingPane)=="undefined"){ +alert(this.getMessageText()); +}else{ +MochiKit.LoggingPane.createLoggingPane(_200||false); +} +}}; +MochiKit.Logging.__new__=function(){ +this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10}; +var m=MochiKit.Base; +m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage); +var _201=m.partial; +var _202=this.Logger; +var _203=_202.prototype.baseLog; +m.update(this.Logger.prototype,{debug:_201(_203,"DEBUG"),log:_201(_203,"INFO"),error:_201(_203,"ERROR"),fatal:_201(_203,"FATAL"),warning:_201(_203,"WARNING")}); +var self=this; +var _204=function(name){ +return function(){ +self.logger[name].apply(self.logger,arguments); +}; +}; +this.log=_204("log"); +this.logError=_204("error"); +this.logDebug=_204("debug"); +this.logFatal=_204("fatal"); +this.logWarning=_204("warning"); +this.logger=new _202(); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Logging.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Logging); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.DateTime"); +} +if(typeof (MochiKit)=="undefined"){ +MochiKit={}; +} +if(typeof (MochiKit.DateTime)=="undefined"){ +MochiKit.DateTime={}; +} +MochiKit.DateTime.NAME="MochiKit.DateTime"; +MochiKit.DateTime.VERSION="1.2"; +MochiKit.DateTime.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.DateTime.toString=function(){ +return this.__repr__(); +}; +MochiKit.DateTime.isoDate=function(str){ +str=str+""; +if(typeof (str)!="string"||str.length==0){ +return null; +} +var iso=str.split("-"); +if(iso.length==0){ +return null; +} +return new Date(iso[0],iso[1]-1,iso[2]); +}; +MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/; +MochiKit.DateTime.isoTimestamp=function(str){ +str=str+""; +if(typeof (str)!="string"||str.length==0){ +return null; +} +var res=str.match(MochiKit.DateTime._isoRegexp); +if(typeof (res)=="undefined"||res==null){ +return null; +} +var year,month,day,hour,min,sec,msec; +year=parseInt(res[1],10); +if(typeof (res[2])=="undefined"||res[2]==""){ +return new Date(year); +} +month=parseInt(res[2],10)-1; +day=parseInt(res[3],10); +if(typeof (res[4])=="undefined"||res[4]==""){ +return new Date(year,month,day); +} +hour=parseInt(res[4],10); +min=parseInt(res[5],10); +sec=(typeof (res[6])!="undefined"&&res[6]!="")?parseInt(res[6],10):0; +if(typeof (res[7])!="undefined"&&res[7]!=""){ +msec=Math.round(1000*parseFloat("0."+res[7])); +}else{ +msec=0; +} +if((typeof (res[8])=="undefined"||res[8]=="")&&(typeof (res[9])=="undefined"||res[9]=="")){ +return new Date(year,month,day,hour,min,sec,msec); +} +var ofs; +if(typeof (res[9])!="undefined"&&res[9]!=""){ +ofs=parseInt(res[10],10)*3600000; +if(typeof (res[11])!="undefined"&&res[11]!=""){ +ofs+=parseInt(res[11],10)*60000; +} +if(res[9]=="-"){ +ofs=-ofs; +} +}else{ +ofs=0; +} +return new Date(Date.UTC(year,month,day,hour,min,sec,msec)-ofs); +}; +MochiKit.DateTime.toISOTime=function(date,_210){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var hh=date.getHours(); +var mm=date.getMinutes(); +var ss=date.getSeconds(); +var lst=[((_210&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)]; +return lst.join(":"); +}; +MochiKit.DateTime.toISOTimestamp=function(date,_214){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var sep=_214?"T":" "; +var foot=_214?"Z":""; +if(_214){ +date=new Date(date.getTime()+(date.getTimezoneOffset()*60000)); +} +return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_214)+foot; +}; +MochiKit.DateTime.toISODate=function(date){ +if(typeof (date)=="undefined"||date==null){ +return null; +} +var _217=MochiKit.DateTime._padTwo; +return [date.getFullYear(),_217(date.getMonth()+1),_217(date.getDate())].join("-"); +}; +MochiKit.DateTime.americanDate=function(d){ +d=d+""; +if(typeof (d)!="string"||d.length==0){ +return null; +} +var a=d.split("/"); +return new Date(a[2],a[0]-1,a[1]); +}; +MochiKit.DateTime._padTwo=function(n){ +return (n>9)?n:"0"+n; +}; +MochiKit.DateTime.toPaddedAmericanDate=function(d){ +if(typeof (d)=="undefined"||d==null){ +return null; +} +var _219=MochiKit.DateTime._padTwo; +return [_219(d.getMonth()+1),_219(d.getDate()),d.getFullYear()].join("/"); +}; +MochiKit.DateTime.toAmericanDate=function(d){ +if(typeof (d)=="undefined"||d==null){ +return null; +} +return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/"); +}; +MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"]; +MochiKit.DateTime.EXPORT_OK=[]; +MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT}; +MochiKit.DateTime.__new__=function(){ +var base=this.NAME+"."; +for(var k in this){ +var o=this[k]; +if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ +try{ +o.NAME=base+k; +} +catch(e){ +} +} +} +}; +MochiKit.DateTime.__new__(); +(function(_220,_221){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){ +var all=_221.EXPORT_TAGS[":all"]; +for(var i=0;i_229){ +var i=_235.length-_229; +res=fmt.separator+_235.substring(i,_235.length)+res; +_235=_235.substring(0,i); +} +} +res=_235+res; +if(_227>0){ +while(frac.length<_230){ +frac=frac+"0"; +} +res=res+fmt.decimal+frac; +} +return _231+res+_232; +}; +}; +MochiKit.Format.numberFormatter=function(_237,_238,_239){ +if(typeof (_238)=="undefined"){ +_238=""; +} +var _240=_237.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/); +if(!_240){ +throw TypeError("Invalid pattern"); +} +var _241=_237.substr(0,_240.index); +var _242=_237.substr(_240.index+_240[0].length); +if(_241.search(/-/)==-1){ +_241=_241+"-"; +} +var _243=_240[1]; +var frac=(typeof (_240[2])=="string"&&_240[2]!="")?_240[2]:""; +var _244=(typeof (_240[3])=="string"&&_240[3]!=""); +var tmp=_243.split(/,/); +var _246; +if(typeof (_239)=="undefined"){ +_239="default"; +} +if(tmp.length==1){ +_246=null; +}else{ +_246=tmp[1].length; +} +var _247=_243.length-_243.replace(/0/g,"").length; +var _248=frac.length-frac.replace(/0/g,"").length; +var _249=frac.length; +var rval=MochiKit.Format._numberFormatter(_238,_241,_242,_239,_244,_249,_247,_246,_248); +var m=MochiKit.Base; +if(m){ +var fn=arguments.callee; +var args=m.concat(arguments); +rval.repr=function(){ +return [self.NAME,"(",map(m.repr,args).join(", "),")"].join(""); +}; +} +return rval; +}; +MochiKit.Format.formatLocale=function(_251){ +if(typeof (_251)=="undefined"||_251==null){ +_251="default"; +} +if(typeof (_251)=="string"){ +var rval=MochiKit.Format.LOCALE[_251]; +if(typeof (rval)=="string"){ +rval=arguments.callee(rval); +MochiKit.Format.LOCALE[_251]=rval; +} +return rval; +}else{ +return _251; +} +}; +MochiKit.Format.twoDigitAverage=function(_252,_253){ +if(_253){ +var res=_252/_253; +if(!isNaN(res)){ +return MochiKit.Format.twoDigitFloat(_252/_253); +} +} +return "0"; +}; +MochiKit.Format.twoDigitFloat=function(_254){ +var sign=(_254<0?"-":""); +var s=Math.floor(Math.abs(_254)*100).toString(); +if(s=="0"){ +return s; +} +if(s.length<3){ +while(s.charAt(s.length-1)=="0"){ +s=s.substring(0,s.length-1); +} +return sign+"0."+s; +} +var head=sign+s.substring(0,s.length-2); +var tail=s.substring(s.length-2,s.length); +if(tail=="00"){ +return head; +}else{ +if(tail.charAt(1)=="0"){ +return head+"."+tail.charAt(0); +}else{ +return head+"."+tail; +} +} +}; +MochiKit.Format.lstrip=function(str,_259){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_259){ +return str.replace(/^\s+/,""); +}else{ +return str.replace(new RegExp("^["+_259+"]+"),""); +} +}; +MochiKit.Format.rstrip=function(str,_260){ +str=str+""; +if(typeof (str)!="string"){ +return null; +} +if(!_260){ +return str.replace(/\s+$/,""); +}else{ +return str.replace(new RegExp("["+_260+"]+$"),""); +} +}; +MochiKit.Format.strip=function(str,_261){ +var self=MochiKit.Format; +return self.rstrip(self.lstrip(str,_261),_261); +}; +MochiKit.Format.truncToFixed=function(_262,_263){ +_262=Math.floor(_262*Math.pow(10,_263)); +var res=(_262*Math.pow(10,-_263)).toFixed(_263); +if(res.charAt(0)=="."){ +res="0"+res; +} +return res; +}; +MochiKit.Format.roundToFixed=function(_264,_265){ +return MochiKit.Format.truncToFixed(_264+0.5*Math.pow(10,-_265),_265); +}; +MochiKit.Format.percentFormat=function(_266){ +return MochiKit.Format.twoDigitFloat(100*_266)+"%"; +}; +MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"]; +MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"}; +MochiKit.Format.EXPORT_OK=[]; +MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT}; +MochiKit.Format.__new__=function(){ +var base=this.NAME+"."; +var k,v,o; +for(k in this.LOCALE){ +o=this.LOCALE[k]; +if(typeof (o)=="object"){ +o.repr=function(){ +return this.NAME; +}; +o.NAME=base+"LOCALE."+k; +} +} +for(k in this){ +o=this[k]; +if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ +try{ +o.NAME=base+k; +} +catch(e){ +} +} +} +}; +MochiKit.Format.__new__(); +(function(_267,_268){ +if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){ +var all=_268.EXPORT_TAGS[":all"]; +for(var i=0;i=0)){ +this._fire(); +} +},_continue:function(res){ +this._resback(res); +this._unpause(); +},_resback:function(res){ +this.fired=((res instanceof Error)?1:0); +this.results[this.fired]=res; +this._fire(); +},_check:function(){ +if(this.fired!=-1){ +if(!this.silentlyCancelled){ +throw new MochiKit.Async.AlreadyCalledError(this); +} +this.silentlyCancelled=false; +return; +} +},callback:function(res){ +this._check(); +this._resback(res); +},errback:function(res){ +this._check(); +if(!(res instanceof Error)){ +res=new MochiKit.Async.GenericError(res); +} +this._resback(res); +},addBoth:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(fn,fn); +},addCallback:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(fn,null); +},addErrback:function(fn){ +if(arguments.length>1){ +fn=MochiKit.Base.partial.apply(null,arguments); +} +return this.addCallbacks(null,fn); +},addCallbacks:function(cb,eb){ +this.chain.push([cb,eb]); +if(this.fired>=0){ +this._fire(); +} +return this; +},_fire:function(){ +var _273=this.chain; +var _274=this.fired; +var res=this.results[_274]; +var self=this; +var cb=null; +while(_273.length>0&&this.paused==0){ +var pair=_273.shift(); +var f=pair[_274]; +if(f==null){ +continue; +} +try{ +res=f(res); +_274=((res instanceof Error)?1:0); +if(res instanceof MochiKit.Async.Deferred){ +cb=function(res){ +self._continue(res); +}; +this._pause(); +} +} +catch(err){ +_274=1; +res=err; +} +} +this.fired=_274; +this.results[_274]=res; +if(cb&&this.paused){ +res.addBoth(cb); +} +}}; +MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(){ +return eval("("+arguments[0].responseText+")"); +},succeed:function(_276){ +var d=new MochiKit.Async.Deferred(); +d.callback.apply(d,arguments); +return d; +},fail:function(_277){ +var d=new MochiKit.Async.Deferred(); +d.errback.apply(d,arguments); +return d; +},getXMLHttpRequest:function(){ +var self=arguments.callee; +if(!self.XMLHttpRequest){ +var _278=[function(){ +return new XMLHttpRequest(); +},function(){ +return new ActiveXObject("Msxml2.XMLHTTP"); +},function(){ +return new ActiveXObject("Microsoft.XMLHTTP"); +},function(){ +return new ActiveXObject("Msxml2.XMLHTTP.4.0"); +},function(){ +throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest"); +}]; +for(var i=0;i<_278.length;i++){ +var func=_278[i]; +try{ +self.XMLHttpRequest=func; +return func(); +} +catch(e){ +} +} +} +return self.XMLHttpRequest(); +},sendXMLHttpRequest:function(req,_280){ +if(typeof (_280)=="undefined"){ +_280=null; +} +var _281=function(){ +try{ +req.onreadystatechange=null; +} +catch(e){ +try{ +req.onreadystatechange=function(){ +}; +} +catch(e){ +} +} +req.abort(); +}; +var self=MochiKit.Async; +var d=new self.Deferred(_281); +var _282=function(){ +if(req.readyState==4){ +try{ +req.onreadystatechange=null; +} +catch(e){ +try{ +req.onreadystatechange=function(){ +}; +} +catch(e){ +} +} +var _283=null; +try{ +_283=req.status; +if(!_283&&MochiKit.Base.isNotEmpty(req.responseText)){ +_283=304; +} +} +catch(e){ +} +if(_283==200||_283==304){ +d.callback(req); +}else{ +var err=new self.XMLHttpRequestError(req,"Request failed"); +if(err.number){ +d.errback(err); +}else{ +d.errback(err); +} +} +} +}; +try{ +req.onreadystatechange=_282; +req.send(_280); +} +catch(e){ +try{ +req.onreadystatechange=null; +} +catch(ignore){ +} +d.errback(e); +} +return d; +},doSimpleXMLHttpRequest:function(url){ +var self=MochiKit.Async; +var req=self.getXMLHttpRequest(); +if(arguments.length>1){ +var m=MochiKit.Base; +var qs=m.queryString.apply(null,m.extend(null,arguments,1)); +if(qs){ +url+="?"+qs; +} +} +req.open("GET",url,true); +return self.sendXMLHttpRequest(req); +},loadJSONDoc:function(url){ +var self=MochiKit.Async; +var d=self.doSimpleXMLHttpRequest.apply(self,arguments); +d=d.addCallback(self.evalJSONRequest); +return d; +},wait:function(_287,_288){ +var d=new MochiKit.Async.Deferred(); +var m=MochiKit.Base; +if(typeof (_288)!="undefined"){ +d.addCallback(function(){ +return _288; +}); +} +var _289=setTimeout(m.bind(d.callback,d),Math.floor(_287*1000)); +d.canceller=function(){ +try{ +clearTimeout(_289); +} +catch(e){ +} +}; +return d; +},callLater:function(_290,func){ +var m=MochiKit.Base; +var _291=m.partial.apply(m,m.extend(null,arguments,1)); +return MochiKit.Async.wait(_290).addCallback(function(res){ +return _291(); +}); +}}); +MochiKit.Async.DeferredLock=function(){ +this.waiting=[]; +this.locked=false; +this.id=this._nextId(); +}; +MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){ +d=new MochiKit.Async.Deferred(); +if(this.locked){ +this.waiting.push(d); +}else{ +this.locked=true; +d.callback(this); +} +return d; +},release:function(){ +if(!this.locked){ +throw TypeError("Tried to release an unlocked DeferredLock"); +} +this.locked=false; +if(this.waiting.length>0){ +this.locked=true; +this.waiting.shift().callback(this); +} +},_nextId:MochiKit.Base.counter(),repr:function(){ +var _292; +if(this.locked){ +_292="locked, "+this.waiting.length+" waiting"; +}else{ +_292="unlocked"; +} +return "DeferredLock("+this.id+", "+_292+")"; +},toString:MochiKit.Base.forward("repr")}; +MochiKit.Async.EXPORT=["AlreadyCalledError","CancelledError","BrowserComplianceError","GenericError","XMLHttpRequestError","Deferred","succeed","fail","getXMLHttpRequest","doSimpleXMLHttpRequest","loadJSONDoc","wait","callLater","sendXMLHttpRequest","DeferredLock"]; +MochiKit.Async.EXPORT_OK=["evalJSONRequest"]; +MochiKit.Async.__new__=function(){ +var m=MochiKit.Base; +var ne=m.partial(m._newNamedError,this); +ne("AlreadyCalledError",function(_294){ +this.deferred=_294; +}); +ne("CancelledError",function(_295){ +this.deferred=_295; +}); +ne("BrowserComplianceError",function(msg){ +this.message=msg; +}); +ne("GenericError",function(msg){ +this.message=msg; +}); +ne("XMLHttpRequestError",function(req,msg){ +this.req=req; +this.message=msg; +try{ +this.number=req.status; +} +catch(e){ +} +}); +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.Async.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.Async); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.DOM"); +dojo.require("MochiKit.Iter"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Iter",[]); +} +try{ +if(typeof (MochiKit.Iter)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.DOM depends on MochiKit.Iter!"; +} +if(typeof (MochiKit.DOM)=="undefined"){ +MochiKit.DOM={}; +} +MochiKit.DOM.NAME="MochiKit.DOM"; +MochiKit.DOM.VERSION="1.2"; +MochiKit.DOM.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.DOM.toString=function(){ +return this.__repr__(); +}; +MochiKit.DOM.EXPORT=["formContents","currentWindow","currentDocument","withWindow","withDocument","registerDOMConverter","coerceToDOM","createDOM","createDOMFunc","getNodeAttribute","setNodeAttribute","updateNodeAttributes","appendChildNodes","replaceChildNodes","removeElement","swapDOM","BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG","getElement","$","computedStyle","getElementsByTagAndClassName","addToCallStack","addLoadEvent","focusOnLoad","setElementClass","toggleElementClass","addElementClass","removeElementClass","swapElementClass","hasElementClass","escapeHTML","toHTML","emitHTML","setDisplayForElement","hideElement","showElement","scrapeText","elementPosition"]; +MochiKit.DOM.EXPORT_OK=["domConverters"]; +MochiKit.DOM.Coordinates=function(x,y){ +this.x=x; +this.y=y; +}; +MochiKit.DOM.Coordinates.prototype.repr=function(){ +var repr=MochiKit.Base.repr; +return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}"; +}; +MochiKit.DOM.elementPosition=function(elem,_298){ +var self=MochiKit.DOM; +elem=self.getElement(elem); +if(!elem){ +return undefined; +} +var x=0; +var y=0; +if(elem.offsetParent){ +while(elem.offsetParent){ +x+=elem.offsetLeft; +y+=elem.offsetTop; +elem=elem.offsetParent; +} +}else{ +x=elem.x||x; +y=elem.y||y; +} +if(_298){ +_298=arguments.callee(_298); +if(_298){ +x-=(_298.x||0); +y-=(_298.y||0); +} +} +return new self.Coordinates(x,y); +}; +MochiKit.DOM.currentWindow=function(){ +return MochiKit.DOM._window; +}; +MochiKit.DOM.currentDocument=function(){ +return MochiKit.DOM._document; +}; +MochiKit.DOM.withWindow=function(win,func){ +var self=MochiKit.DOM; +var _300=self._document; +var _301=self._win; +var rval; +try{ +self._window=win; +self._document=win.document; +rval=func(); +} +catch(e){ +self._window=_301; +self._document=_300; +throw e; +} +self._window=_301; +self._document=_300; +return rval; +}; +MochiKit.DOM.formContents=function(elem){ +var _302=[]; +var _303=[]; +var m=MochiKit.Base; +var self=MochiKit.DOM; +if(typeof (elem)=="undefined"||elem==null){ +elem=self._document; +}else{ +elem=self.getElement(elem); +} +m.nodeWalk(elem,function(elem){ +var name=elem.name; +var _304=elem.value; +if(m.isNotEmpty(name,_304)){ +if(elem.tagName=="INPUT"&&(elem.type=="radio"||elem.type=="checkbox")&&!elem.checked){ +return null; +} +_302.push(name); +_303.push(_304); +return null; +} +return elem.childNodes; +}); +return [_302,_303]; +}; +MochiKit.DOM.withDocument=function(doc,func){ +var self=MochiKit.DOM; +var _306=self._document; +var rval; +try{ +self._document=doc; +rval=func(); +} +catch(e){ +self._document=_306; +throw e; +} +self._document=_306; +return rval; +}; +MochiKit.DOM.registerDOMConverter=function(name,_307,wrap,_308){ +MochiKit.DOM.domConverters.register(name,_307,wrap,_308); +}; +MochiKit.DOM.coerceToDOM=function(node,ctx){ +var im=MochiKit.Iter; +var self=MochiKit.DOM; +var iter=im.iter; +var _313=im.repeat; +var imap=im.imap; +var _315=self.domConverters; +var _316=self.coerceToDOM; +var _317=MochiKit.Base.NotFound; +while(true){ +if(typeof (node)=="undefined"||node==null){ +return null; +} +if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){ +return node; +} +if(typeof (node)=="number"||typeof (node)=="bool"){ +node=node.toString(); +} +if(typeof (node)=="string"){ +return self._document.createTextNode(node); +} +if(typeof (node.toDOM)=="function"){ +node=node.toDOM(ctx); +continue; +} +if(typeof (node)=="function"){ +node=node(ctx); +continue; +} +var _318=null; +try{ +_318=iter(node); +} +catch(e){ +} +if(_318){ +return imap(_316,_318,_313(ctx)); +} +try{ +node=_315.match(node,ctx); +continue; +} +catch(e){ +if(e!=_317){ +throw e; +} +} +return self._document.createTextNode(node.toString()); +} +return undefined; +}; +MochiKit.DOM.setNodeAttribute=function(node,attr,_320){ +var o={}; +o[attr]=_320; +try{ +return MochiKit.DOM.updateNodeAttributes(node,o); +} +catch(e){ +} +return null; +}; +MochiKit.DOM.getNodeAttribute=function(node,attr){ +var self=MochiKit.DOM; +var _321=self.attributeArray.renames[attr]; +node=self.getElement(node); +try{ +if(_321){ +return node[_321]; +} +return node.getAttribute(attr); +} +catch(e){ +} +return null; +}; +MochiKit.DOM.updateNodeAttributes=function(node,_322){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +if(_322){ +var _323=MochiKit.Base.updatetree; +if(self.attributeArray.compliant){ +for(var k in _322){ +var v=_322[k]; +if(typeof (v)=="object"&&typeof (elem[k])=="object"){ +_323(elem[k],v); +}else{ +if(k.substring(0,2)=="on"){ +if(typeof (v)=="string"){ +v=new Function(v); +} +elem[k]=v; +}else{ +elem.setAttribute(k,v); +} +} +} +}else{ +var _324=self.attributeArray.renames; +for(k in _322){ +v=_322[k]; +var _325=_324[k]; +if(k=="style"&&typeof (v)=="string"){ +elem.style.cssText=v; +}else{ +if(typeof (_325)=="string"){ +elem[_325]=v; +}else{ +if(typeof (elem[k])=="object"&&typeof (v)=="object"){ +_323(elem[k],v); +}else{ +if(k.substring(0,2)=="on"){ +if(typeof (v)=="string"){ +v=new Function(v); +} +elem[k]=v; +}else{ +elem.setAttribute(k,v); +} +} +} +} +} +} +} +return elem; +}; +MochiKit.DOM.appendChildNodes=function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +} +var _326=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; +var _327=MochiKit.Iter.iextend; +while(_326.length){ +var n=_326.shift(); +if(typeof (n)=="undefined"||n==null){ +}else{ +if(typeof (n.nodeType)=="number"){ +elem.appendChild(n); +}else{ +_327(_326,n); +} +} +} +return elem; +}; +MochiKit.DOM.replaceChildNodes=function(node){ +var elem=node; +var self=MochiKit.DOM; +if(typeof (node)=="string"){ +elem=self.getElement(node); +arguments[0]=elem; +} +var _328; +while((_328=elem.firstChild)){ +elem.removeChild(_328); +} +if(arguments.length<2){ +return elem; +}else{ +return self.appendChildNodes.apply(this,arguments); +} +}; +MochiKit.DOM.createDOM=function(name,_329){ +var elem; +var self=MochiKit.DOM; +if(typeof (name)=="string"){ +if(_329&&"name" in _329&&!self.attributeArray.compliant){ +name="<"+name+" name=\""+self.escapeHTML(_329.name)+"\">"; +} +elem=self._document.createElement(name); +}else{ +elem=name; +} +if(_329){ +self.updateNodeAttributes(elem,_329); +} +if(arguments.length<=2){ +return elem; +}else{ +var args=MochiKit.Base.extend([elem],arguments,2); +return self.appendChildNodes.apply(this,args); +} +}; +MochiKit.DOM.createDOMFunc=function(){ +var m=MochiKit.Base; +return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments)); +}; +MochiKit.DOM.swapDOM=function(dest,src){ +var self=MochiKit.DOM; +dest=self.getElement(dest); +var _332=dest.parentNode; +if(src){ +src=self.getElement(src); +_332.replaceChild(src,dest); +}else{ +_332.removeChild(dest); +} +return src; +}; +MochiKit.DOM.getElement=function(id){ +var self=MochiKit.DOM; +if(arguments.length==1){ +return ((typeof (id)=="string")?self._document.getElementById(id):id); +}else{ +return MochiKit.Base.map(self.getElement,arguments); +} +}; +MochiKit.DOM.computedStyle=function(_334,_335,_336){ +if(arguments.length==2){ +_336=_335; +} +var self=MochiKit.DOM; +var el=self.getElement(_334); +var _338=self._document; +if(!el||el==_338){ +return undefined; +} +if(el.currentStyle){ +return el.currentStyle[_335]; +} +if(typeof (_338.defaultView)=="undefined"){ +return undefined; +} +if(_338.defaultView==null){ +return undefined; +} +var _339=_338.defaultView.getComputedStyle(el,null); +if(typeof (_339)=="undefined"||_339==null){ +return undefined; +} +return _339.getPropertyValue(_336); +}; +MochiKit.DOM.getElementsByTagAndClassName=function(_340,_341,_342){ +var self=MochiKit.DOM; +if(typeof (_340)=="undefined"||_340==null){ +_340="*"; +} +if(typeof (_342)=="undefined"||_342==null){ +_342=self._document; +} +_342=self.getElement(_342); +var _343=_342.getElementsByTagName(_340)||self._document.all; +if(typeof (_341)=="undefined"||_341==null){ +return MochiKit.Base.extend(null,_343); +} +var _344=[]; +for(var i=0;i<_343.length;i++){ +var _345=_343[i]; +var _346=_345.className.split(" "); +for(var j=0;j<_346.length;j++){ +if(_346[j]==_341){ +_344.push(_345); +break; +} +} +} +return _344; +}; +MochiKit.DOM._newCallStack=function(path,once){ +var rval=function(){ +var _349=arguments.callee.callStack; +for(var i=0;i<_349.length;i++){ +if(_349[i].apply(this,arguments)===false){ +break; +} +} +if(once){ +try{ +this[path]=null; +} +catch(e){ +} +} +}; +rval.callStack=[]; +return rval; +}; +MochiKit.DOM.addToCallStack=function(_350,path,func,once){ +var self=MochiKit.DOM; +var _351=_350[path]; +var _352=_351; +if(!(typeof (_351)=="function"&&typeof (_351.callStack)=="object"&&_351.callStack!=null)){ +_352=self._newCallStack(path,once); +if(typeof (_351)=="function"){ +_352.callStack.push(_351); +} +_350[path]=_352; +} +_352.callStack.push(func); +}; +MochiKit.DOM.addLoadEvent=function(func){ +var self=MochiKit.DOM; +self.addToCallStack(self._window,"onload",func,true); +}; +MochiKit.DOM.focusOnLoad=function(_353){ +var self=MochiKit.DOM; +self.addLoadEvent(function(){ +_353=self.getElement(_353); +if(_353){ +_353.focus(); +} +}); +}; +MochiKit.DOM.setElementClass=function(_354,_355){ +var self=MochiKit.DOM; +var obj=self.getElement(_354); +if(self.attributeArray.compliant){ +obj.setAttribute("class",_355); +}else{ +obj.setAttribute("className",_355); +} +}; +MochiKit.DOM.toggleElementClass=function(_356){ +var self=MochiKit.DOM; +for(var i=1;i/g,">"); +}; +MochiKit.DOM.toHTML=function(dom){ +return MochiKit.DOM.emitHTML(dom).join(""); +}; +MochiKit.DOM.emitHTML=function(dom,lst){ +if(typeof (lst)=="undefined"||lst==null){ +lst=[]; +} +var _372=[dom]; +var self=MochiKit.DOM; +var _373=self.escapeHTML; +var _374=self.attributeArray; +while(_372.length){ +dom=_372.pop(); +if(typeof (dom)=="string"){ +lst.push(dom); +}else{ +if(dom.nodeType==1){ +lst.push("<"+dom.nodeName.toLowerCase()); +var _375=[]; +var _376=_374(dom); +for(var i=0;i<_376.length;i++){ +var a=_376[i]; +_375.push([" ",a.name,"=\"",_373(a.value),"\""]); +} +_375.sort(); +for(i=0;i<_375.length;i++){ +var _377=_375[i]; +for(var j=0;j<_377.length;j++){ +lst.push(_377[j]); +} +} +if(dom.hasChildNodes()){ +lst.push(">"); +_372.push(""); +var _378=dom.childNodes; +for(i=_378.length-1;i>=0;i--){ +_372.push(_378[i]); +} +}else{ +lst.push("/>"); +} +}else{ +if(dom.nodeType==3){ +lst.push(_373(dom.nodeValue)); +} +} +} +} +return lst; +}; +MochiKit.DOM.setDisplayForElement=function(_379,_380){ +var m=MochiKit.Base; +var _381=m.extend(null,arguments,1); +MochiKit.Iter.forEach(m.filter(null,m.map(MochiKit.DOM.getElement,_381)),function(_380){ +_380.style.display=_379; +}); +}; +MochiKit.DOM.scrapeText=function(node,_382){ +var rval=[]; +(function(node){ +var cn=node.childNodes; +if(cn){ +for(var i=0;i0){ +var _387=m.filter; +_386=function(node){ +return _387(_386.ignoreAttrFilter,node.attributes); +}; +_386.ignoreAttr={}; +MochiKit.Iter.forEach(_385.attributes,function(a){ +_386.ignoreAttr[a.name]=a.value; +}); +_386.ignoreAttrFilter=function(a){ +return (_386.ignoreAttr[a.name]!=a.value); +}; +_386.compliant=false; +_386.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor"}; +}else{ +_386=function(node){ +return node.attributes; +}; +_386.compliant=true; +_386.renames={}; +} +this.attributeArray=_386; +var _388=this.createDOMFunc; +this.UL=_388("ul"); +this.OL=_388("ol"); +this.LI=_388("li"); +this.TD=_388("td"); +this.TR=_388("tr"); +this.TBODY=_388("tbody"); +this.THEAD=_388("thead"); +this.TFOOT=_388("tfoot"); +this.TABLE=_388("table"); +this.TH=_388("th"); +this.INPUT=_388("input"); +this.SPAN=_388("span"); +this.A=_388("a"); +this.DIV=_388("div"); +this.IMG=_388("img"); +this.BUTTON=_388("button"); +this.TT=_388("tt"); +this.PRE=_388("pre"); +this.H1=_388("h1"); +this.H2=_388("h2"); +this.H3=_388("h3"); +this.BR=_388("br"); +this.HR=_388("hr"); +this.LABEL=_388("label"); +this.TEXTAREA=_388("textarea"); +this.FORM=_388("form"); +this.P=_388("p"); +this.SELECT=_388("select"); +this.OPTION=_388("option"); +this.OPTGROUP=_388("optgroup"); +this.LEGEND=_388("legend"); +this.FIELDSET=_388("fieldset"); +this.STRONG=_388("strong"); +this.CANVAS=_388("canvas"); +this.hideElement=m.partial(this.setDisplayForElement,"none"); +this.showElement=m.partial(this.setDisplayForElement,"block"); +this.removeElement=this.swapDOM; +this.$=this.getElement; +this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; +m.nameFunctions(this); +}; +MochiKit.DOM.__new__(this); +MochiKit.Base._exportSymbols(this,MochiKit.DOM); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.LoggingPane"); +dojo.require("MochiKit.Logging"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Logging",[]); +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.Logging)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!"; +} +if(typeof (MochiKit.LoggingPane)=="undefined"){ +MochiKit.LoggingPane={}; +} +MochiKit.LoggingPane.NAME="MochiKit.LoggingPane"; +MochiKit.LoggingPane.VERSION="1.2"; +MochiKit.LoggingPane.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.LoggingPane.toString=function(){ +return this.__repr__(); +}; +MochiKit.LoggingPane.createLoggingPane=function(_389){ +var m=MochiKit.LoggingPane; +_389=!(!_389); +if(m._loggingPane&&m._loggingPane.inline!=_389){ +m._loggingPane.closePane(); +m._loggingPane=null; +} +if(!m._loggingPane||m._loggingPane.closed){ +m._loggingPane=new m.LoggingPane(_389,MochiKit.Logging.logger); +} +return m._loggingPane; +}; +MochiKit.LoggingPane.LoggingPane=function(_390,_391){ +if(typeof (_391)=="undefined"||_391==null){ +_391=MochiKit.Logging.logger; +} +this.logger=_391; +var _392=MochiKit.Base.update; +var _393=MochiKit.Base.updatetree; +var bind=MochiKit.Base.bind; +var _394=MochiKit.Base.clone; +var win=window; +if(typeof (MochiKit.DOM)!="undefined"){ +win=MochiKit.DOM.currentWindow(); +} +if(!_390){ +var url=win.location.href.split("?")[0].replace(/[:\/.><&]/g,"_"); +var name="MochiKit_LoggingPane_"+url; +var nwin=win.open("",name,"dependent,resizable,height=200"); +if(!nwin){ +alert("Not able to open debugging window due to pop-up blocking."); +return undefined; +} +nwin.document.write(""+"[MochiKit.LoggingPane]"+""); +nwin.document.close(); +nwin.document.title+=" "+win.document.title; +win=nwin; +} +var doc=win.document; +this.doc=doc; +var _396=doc.getElementById("_debugPane"); +if(_396&&typeof (_396.loggingPane)!="undefined"){ +_396.loggingPane.logger=this.logger; +_396.loggingPane.buildAndApplyFilter(); +return _396.loggingPane; +} +_396=doc.createElement("div"); +_396.id="_debugPane"; +_396.loggingPane=this; +var _397=doc.createElement("input"); +var _398=doc.createElement("input"); +var _399=doc.createElement("button"); +var _400=doc.createElement("button"); +var _401=doc.createElement("button"); +var _402=doc.createElement("button"); +var _403=doc.createElement("div"); +var _404=doc.createElement("div"); +var _405="_debugPaneListener"; +this.colorTable=_394(this.colorTable); +var _406=[]; +var _407=null; +var _408=function(msg){ +var _409=msg.level; +if(typeof (_409)=="number"){ +_409=MochiKit.Logging.LogLevel[_409]; +} +return _409; +}; +var _410=function(msg){ +return msg.info.join(" "); +}; +var _411=bind(function(msg){ +var _412=_408(msg); +var text=_410(msg); +var c=this.colorTable[_412]; +var p=doc.createElement("span"); +p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_412; +p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c; +p.appendChild(doc.createTextNode(_412+": "+text)); +_404.appendChild(p); +_404.appendChild(doc.createElement("br")); +if(_403.offsetHeight>_403.scrollHeight){ +_403.scrollTop=0; +}else{ +_403.scrollTop=_403.scrollHeight; +} +},this); +var _415=function(msg){ +_406[_406.length]=msg; +_411(msg); +}; +var _416=function(){ +var _417,infore; +try{ +_417=new RegExp(_397.value); +infore=new RegExp(_398.value); +} +catch(e){ +logDebug("Error in filter regex: "+e.message); +return null; +} +return function(msg){ +return (_417.test(_408(msg))&&infore.test(_410(msg))); +}; +}; +var _418=function(){ +while(_404.firstChild){ +_404.removeChild(_404.firstChild); +} +}; +var _419=function(){ +_406=[]; +_418(); +}; +var _420=bind(function(){ +if(this.closed){ +return; +} +this.closed=true; +if(MochiKit.LoggingPane._loggingPane==this){ +MochiKit.LoggingPane._loggingPane=null; +} +this.logger.removeListener(_405); +_396.loggingPane=null; +if(_390){ +_396.parentNode.removeChild(_396); +}else{ +this.win.close(); +} +},this); +var _421=function(){ +_418(); +for(var i=0;i<_406.length;i++){ +var msg=_406[i]; +if(_407==null||_407(msg)){ +_411(msg); +} +} +}; +this.buildAndApplyFilter=function(){ +_407=_416(); +_421(); +this.logger.removeListener(_405); +this.logger.addListener(_405,_407,_415); +}; +var _422=bind(function(){ +_406=this.logger.getMessages(); +_421(); +},this); +var _423=bind(function(_424){ +_424=_424||window.event; +key=_424.which||_424.keyCode; +if(key==13){ +this.buildAndApplyFilter(); +} +},this); +var _425="display: block; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont; +if(_390){ +_425+="; height: 10em; border-top: 2px solid black"; +}else{ +_425+="; height: 100%;"; +} +_396.style.cssText=_425; +doc.body.appendChild(_396); +_425={"cssText":"width: 33%; display: inline; font: "+this.logFont}; +_393(_397,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_423,"style":_425}); +_396.appendChild(_397); +_393(_398,{"value":".*","onkeypress":_423,"style":_425}); +_396.appendChild(_398); +_425="width: 8%; display:inline; font: "+this.logFont; +_399.appendChild(doc.createTextNode("Filter")); +_399.onclick=bind(this.buildAndApplyFilter,this); +_399.style.cssText=_425; +_396.appendChild(_399); +_400.appendChild(doc.createTextNode("Load")); +_400.onclick=_422; +_400.style.cssText=_425; +_396.appendChild(_400); +_401.appendChild(doc.createTextNode("Clear")); +_401.onclick=_419; +_401.style.cssText=_425; +_396.appendChild(_401); +_402.appendChild(doc.createTextNode("Close")); +_402.onclick=_420; +_402.style.cssText=_425; +_396.appendChild(_402); +_403.style.cssText="overflow: auto; width: 100%"; +_404.style.cssText="width: 100%; height: "+(_390?"8em":"100%"); +_403.appendChild(_404); +_396.appendChild(_403); +this.buildAndApplyFilter(); +_422(); +if(_390){ +this.win=undefined; +}else{ +this.win=win; +} +this.inline=_390; +this.closePane=_420; +this.closed=false; +return this; +}; +MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}}; +MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"]; +MochiKit.LoggingPane.EXPORT=["createLoggingPane"]; +MochiKit.LoggingPane.__new__=function(){ +this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; +MochiKit.Base.nameFunctions(this); +MochiKit.LoggingPane._loggingPane=null; +}; +MochiKit.LoggingPane.__new__(); +MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane); +if(typeof (dojo)!="undefined"){ +dojo.provide("MochiKit.Color"); +dojo.require("MochiKit.Base"); +} +if(typeof (JSAN)!="undefined"){ +JSAN.use("MochiKit.Base",[]); +} +try{ +if(typeof (MochiKit.Base)=="undefined"){ +throw ""; +} +} +catch(e){ +throw "MochiKit.Color depends on MochiKit.Base"; +} +if(typeof (MochiKit.Color)=="undefined"){ +MochiKit.Color={}; +} +MochiKit.Color.NAME="MochiKit.Color"; +MochiKit.Color.VERSION="1.2"; +MochiKit.Color.__repr__=function(){ +return "["+this.NAME+" "+this.VERSION+"]"; +}; +MochiKit.Color.toString=function(){ +return this.__repr__(); +}; +MochiKit.Color.Color=function(red,_427,blue,_429){ +if(typeof (_429)=="undefined"||_429==null){ +_429=1; +} +this.rgb={r:red,g:_427,b:blue,a:_429}; +}; +MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_430){ +var rgb=this.rgb; +var m=MochiKit.Color; +return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_430); +},colorWithHue:function(hue){ +var hsl=this.asHSL(); +hsl.h=hue; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithSaturation:function(_434){ +var hsl=this.asHSL(); +hsl.s=_434; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},colorWithLightness:function(_435){ +var hsl=this.asHSL(); +hsl.l=_435; +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},darkerColorWithLevel:function(_436){ +var hsl=this.asHSL(); +hsl.l=Math.max(hsl.l-_436,0); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},lighterColorWithLevel:function(_437){ +var hsl=this.asHSL(); +Math.min(hsl.l+_437,1); +var m=MochiKit.Color; +return m.Color.fromHSL(hsl); +},blendedColor:function(_438,_439){ +if(typeof (_439)=="undefined"||_439==null){ +_439=0.5; +} +var sf=1-_439; +var s=this.rgb; +var d=_438.rgb; +var df=_439; +return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df)); +},compareRGB:function(_442){ +var a=this.asRGB(); +var b=_442.asRGB(); +return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]); +},isLight:function(){ +return this.asHSL().b>0.5; +},isDark:function(){ +return (!this.isLight()); +},toHSLString:function(){ +var c=this.asHSL(); +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._hslString; +if(!rval){ +var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%"); +var a=c.a; +if(a>=1){ +a=1; +rval="hsl("+mid+")"; +}else{ +if(a<=0){ +a=0; +} +rval="hsla("+mid+","+a+")"; +} +this._hslString=rval; +} +return rval; +},toRGBString:function(){ +var c=this.rgb; +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._rgbString; +if(!rval){ +var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0)); +if(c.a!=1){ +rval="rgba("+mid+","+c.a+")"; +}else{ +rval="rgb("+mid+")"; +} +this._rgbString=rval; +} +return rval; +},asRGB:function(){ +return MochiKit.Base.clone(this.rgb); +},toHexString:function(){ +var m=MochiKit.Color; +var c=this.rgb; +var ccc=MochiKit.Color.clampColorComponent; +var rval=this._hexString; +if(!rval){ +rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255))); +this._hexString=rval; +} +return rval; +},asHSV:function(){ +var hsv=this.hsv; +var c=this.rgb; +if(typeof (hsv)=="undefined"||hsv==null){ +hsv=MochiKit.Color.rgbToHSV(this.rgb); +this.hsv=hsv; +} +return MochiKit.Base.clone(hsv); +},asHSL:function(){ +var hsl=this.hsl; +var c=this.rgb; +if(typeof (hsl)=="undefined"||hsl==null){ +hsl=MochiKit.Color.rgbToHSL(this.rgb); +this.hsl=hsl; +} +return MochiKit.Base.clone(hsl); +},toString:function(){ +return this.toRGBString(); +},repr:function(){ +var c=this.rgb; +var col=[c.r,c.g,c.b,c.a]; +return this.__class__.NAME+"("+col.join(", ")+")"; +}}; +MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_447,blue,_448){ +var _449=MochiKit.Color.Color; +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_447=rgb.g; +blue=rgb.b; +if(typeof (rgb.a)=="undefined"){ +_448=undefined; +}else{ +_448=rgb.a; +} +} +return new _449(red,_447,blue,_448); +},fromHSL:function(hue,_450,_451,_452){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hslToRGB.apply(m,arguments)); +},fromHSV:function(hue,_453,_454,_455){ +var m=MochiKit.Color; +return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments)); +},fromName:function(name){ +var _456=MochiKit.Color.Color; +var _457=_456._namedColors[name.toLowerCase()]; +if(typeof (_457)=="string"){ +return _456.fromHexString(_457); +}else{ +if(name=="transparent"){ +return _456.transparentColor(); +} +} +return null; +},fromString:function(_458){ +var self=MochiKit.Color.Color; +var _459=_458.substr(0,3); +if(_459=="rgb"){ +return self.fromRGBString(_458); +}else{ +if(_459=="hsl"){ +return self.fromHSLString(_458); +}else{ +if(_458.charAt(0)=="#"){ +return self.fromHexString(_458); +} +} +} +return self.fromName(_458); +},fromHexString:function(_460){ +if(_460.charAt(0)=="#"){ +_460=_460.substring(1); +} +var _461=[]; +var i,hex; +if(_460.length==3){ +for(i=0;i<3;i++){ +hex=_460.substr(i,1); +_461.push(parseInt(hex+hex,16)/255); +} +}else{ +for(i=0;i<6;i+=2){ +hex=_460.substr(i,2); +_461.push(parseInt(hex,16)/255); +} +} +var _462=MochiKit.Color.Color; +return _462.fromRGB.apply(_462,_461); +},_fromColorString:function(pre,_464,_465,_466){ +if(_466.indexOf(pre)==0){ +_466=_466.substring(_466.indexOf("(",3)+1,_466.length-1); +} +var _467=_466.split(/\s*,\s*/); +var _468=[]; +for(var i=0;i<_467.length;i++){ +var c=_467[i]; +var val; +var _469=c.substring(c.length-3); +if(c.charAt(c.length-1)=="%"){ +val=0.01*parseFloat(c.substring(0,c.length-1)); +}else{ +if(_469=="deg"){ +val=parseFloat(c)/360; +}else{ +if(_469=="rad"){ +val=parseFloat(c)/(Math.PI*2); +}else{ +val=_465[i]*parseFloat(c); +} +} +} +_468.push(val); +} +return this[_464].apply(this,_468); +},fromComputedStyle:function(elem,_470,_471){ +var d=MochiKit.DOM; +var cls=MochiKit.Color.Color; +for(elem=d.getElement(elem);elem;elem=elem.parentNode){ +var _472=d.computedStyle.apply(d,arguments); +if(!_472){ +continue; +} +var _473=cls.fromString(_472); +if(!_473){ +break; +} +if(_473.asRGB().a>0){ +return _473; +} +} +return null; +},fromBackground:function(elem){ +var cls=MochiKit.Color.Color; +return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor(); +},fromText:function(elem){ +var cls=MochiKit.Color.Color; +return cls.fromComputedStyle(elem,"color","color")||cls.blackColor(); +},namedColors:function(){ +return MochiKit.Base.clone(MochiKit.Color.Color._namedColors); +}}); +MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_474){ +v*=_474; +if(v<0){ +return 0; +}else{ +if(v>_474){ +return _474; +}else{ +return v; +} +} +},_hslValue:function(n1,n2,hue){ +if(hue>6){ +hue-=6; +}else{ +if(hue<0){ +hue+=6; +} +} +var val; +if(hue<1){ +val=n1+(n2-n1)*hue; +}else{ +if(hue<3){ +val=n2; +}else{ +if(hue<4){ +val=n1+(n2-n1)*(4-hue); +}else{ +val=n1; +} +} +} +return val; +},hsvToRGB:function(hue,_477,_478,_479){ +if(arguments.length==1){ +var hsv=hue; +hue=hsv.h; +_477=hsv.s; +_478=hsv.v; +_479=hsv.a; +} +var red; +var _480; +var blue; +if(_477==0){ +red=0; +_480=0; +blue=0; +}else{ +var i=Math.floor(hue*6); +var f=(hue*6)-i; +var p=_478*(1-_477); +var q=_478*(1-(_477*f)); +var t=_478*(1-(_477*(1-f))); +switch(i){ +case 1: +red=q; +_480=_478; +blue=p; +break; +case 2: +red=p; +_480=_478; +blue=t; +break; +case 3: +red=p; +_480=q; +blue=_478; +break; +case 4: +red=t; +_480=p; +blue=_478; +break; +case 5: +red=_478; +_480=p; +blue=q; +break; +case 6: +case 0: +red=_478; +_480=t; +blue=p; +break; +} +} +return {r:red,g:_480,b:blue,a:_479}; +},hslToRGB:function(hue,_482,_483,_484){ +if(arguments.length==1){ +var hsl=hue; +hue=hsl.h; +_482=hsl.s; +_483=hsl.l; +_484=hsl.a; +} +var red; +var _485; +var blue; +if(_482==0){ +red=_483; +_485=_483; +blue=_483; +}else{ +var m2; +if(_483<=0.5){ +m2=_483*(1+_482); +}else{ +m2=_483+_482-(_483*_482); +} +var m1=(2*_483)-m2; +var f=MochiKit.Color._hslValue; +var h6=hue*6; +red=f(m1,m2,h6+2); +_485=f(m1,m2,h6); +blue=f(m1,m2,h6-2); +} +return {r:red,g:_485,b:blue,a:_484}; +},rgbToHSV:function(red,_489,blue,_490){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_489=rgb.g; +blue=rgb.b; +_490=rgb.a; +} +var max=Math.max(Math.max(red,_489),blue); +var min=Math.min(Math.min(red,_489),blue); +var hue; +var _493; +var _494=max; +if(min==max){ +hue=0; +_493=0; +}else{ +var _495=(max-min); +_493=_495/max; +if(red==max){ +hue=(_489-blue)/_495; +}else{ +if(_489==max){ +hue=2+((blue-red)/_495); +}else{ +hue=4+((red-_489)/_495); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_493,v:_494,a:_490}; +},rgbToHSL:function(red,_496,blue,_497){ +if(arguments.length==1){ +var rgb=red; +red=rgb.r; +_496=rgb.g; +blue=rgb.b; +_497=rgb.a; +} +var max=Math.max(red,Math.max(_496,blue)); +var min=Math.min(red,Math.min(_496,blue)); +var hue; +var _498; +var _499=(max+min)/2; +var _500=max-min; +if(_500==0){ +hue=0; +_498=0; +}else{ +if(_499<=0.5){ +_498=_500/(max+min); +}else{ +_498=_500/(2-max-min); +} +if(red==max){ +hue=(_496-blue)/_500; +}else{ +if(_496==max){ +hue=2+((blue-red)/_500); +}else{ +hue=4+((red-_496)/_500); +} +} +hue/=6; +if(hue<0){ +hue+=1; +} +if(hue>1){ +hue-=1; +} +} +return {h:hue,s:_498,l:_499,a:_497}; +},toColorPart:function(num){ +var _501=Math.round(num).toString(16); +if(num<16){ +return "0"+_501; +} +return _501; +},__new__:function(){ +var m=MochiKit.Base; +this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]); +this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]); +var _502=1/3; +var _503={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_502,_502,_502],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_502,2*_502,2*_502],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]}; +var _504=function(name,r,g,b,a){ +var rval=this.fromRGB(r,g,b,a); +this[name]=function(){ +return rval; +}; +return rval; +}; +for(var k in _503){ +var name=k+"Color"; +var _507=m.concat([_504,this.Color,name],_503[k]); +this.Color[name]=m.bind.apply(null,_507); +} +var _508=function(){ +for(var i=0;i"+el.innerHTML+""; +},_roundTopCorners:function(el,_524,_525){ +var _526=this._createCorner(_525); +for(var i=0;i=0;i--){ +_529.appendChild(this._createCornerSlice(_527,_528,i,"bottom")); +} +el.style.paddingBottom=0; +el.appendChild(_529); +},_createCorner:function(_530){ +var dom=MochiKit.DOM; +return dom.DIV({style:{backgroundColor:_530.toString()}}); +},_createCornerSlice:function(_531,_532,n,_533){ +var _534=MochiKit.DOM.SPAN(); +var _535=_534.style; +_535.backgroundColor=_531.toString(); +_535.display="block"; +_535.height="1px"; +_535.overflow="hidden"; +_535.fontSize="1px"; +var _536=this._borderColor(_531,_532); +if(this.options.border&&n==0){ +_535.borderTopStyle="solid"; +_535.borderTopWidth="1px"; +_535.borderLeftWidth="0px"; +_535.borderRightWidth="0px"; +_535.borderBottomWidth="0px"; +_535.height="0px"; +_535.borderColor=_536.toString(); +}else{ +if(_536){ +_535.borderColor=_536.toString(); +_535.borderStyle="solid"; +_535.borderWidth="0px 1px"; +} +} +if(!this.options.compact&&(n==(this.options.numSlices-1))){ +_535.height="2px"; +} +this._setMargin(_534,n,_533); +this._setBorder(_534,n,_533); +return _534; +},_setOptions:function(_537){ +this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false}; +MochiKit.Base.update(this.options,_537); +this.options.numSlices=(this.options.compact?2:4); +},_whichSideTop:function(){ +var _538=this.options.corners; +if(this._hasString(_538,"all","top")){ +return ""; +} +var _539=(_538.indexOf("tl")!=-1); +var _540=(_538.indexOf("tr")!=-1); +if(_539&&_540){ +return ""; +} +if(_539){ +return "left"; +} +if(_540){ +return "right"; +} +return ""; +},_whichSideBottom:function(){ +var _541=this.options.corners; +if(this._hasString(_541,"all","bottom")){ +return ""; +} +var _542=(_541.indexOf("bl")!=-1); +var _543=(_541.indexOf("br")!=-1); +if(_542&&_543){ +return ""; +} +if(_542){ +return "left"; +} +if(_543){ +return "right"; +} +return ""; +},_borderColor:function(_544,_545){ +if(_544=="transparent"){ +return _545; +}else{ +if(this.options.border){ +return this.options.border; +}else{ +if(this.options.blend){ +return _545.blendedColor(_544); +} +} +} +return ""; +},_setMargin:function(el,n,_546){ +var _547=this._marginSize(n)+"px"; +var _548=(_546=="top"?this._whichSideTop():this._whichSideBottom()); +var _549=el.style; +if(_548=="left"){ +_549.marginLeft=_547; +_549.marginRight="0px"; +}else{ +if(_548=="right"){ +_549.marginRight=_547; +_549.marginLeft="0px"; +}else{ +_549.marginLeft=_547; +_549.marginRight=_547; +} +} +},_setBorder:function(el,n,_550){ +var _551=this._borderSize(n)+"px"; +var _552=(_550=="top"?this._whichSideTop():this._whichSideBottom()); +var _553=el.style; +if(_552=="left"){ +_553.borderLeftWidth=_551; +_553.borderRightWidth="0px"; +}else{ +if(_552=="right"){ +_553.borderRightWidth=_551; +_553.borderLeftWidth="0px"; +}else{ +_553.borderLeftWidth=_551; +_553.borderRightWidth=_551; +} +} +},_marginSize:function(n){ +if(this.isTransparent){ +return 0; +} +var o=this.options; +if(o.compact&&o.blend){ +var _554=[1,0]; +return _554[n]; +}else{ +if(o.compact){ +var _555=[2,1]; +return _555[n]; +}else{ +if(o.blend){ +var _556=[3,2,1,0]; +return _556[n]; +}else{ +var _557=[5,3,2,1]; +return _557[n]; +} +} +} +},_borderSize:function(n){ +var o=this.options; +var _558; +if(o.compact&&(o.blend||this.isTransparent)){ +return 1; +}else{ +if(o.compact){ +_558=[1,0]; +}else{ +if(o.blend){ +_558=[2,1,1,1]; +}else{ +if(o.border){ +_558=[0,2,0,0]; +}else{ +if(this.isTransparent){ +_558=[5,3,2,1]; +}else{ +return 0; +} +} +} +} +} +return _558[n]; +},_hasString:function(str){ +for(var i=1;i"+"<"+"/script"+">"; +document.write(tag); +} +} +})(); +} + + ============================================================ --- www/viewmtn/MochiKit/__package__.js 6f05da1941ddb410b89f26ea4ea4e72c235ceaed +++ www/viewmtn/MochiKit/__package__.js 6f05da1941ddb410b89f26ea4ea4e72c235ceaed @@ -0,0 +1,2 @@ +dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]}); +dojo.hostenv.moduleLoaded("MochiKit.*"); ============================================================ --- www/viewmtn/README 46ffe1ce5080c9fd0081574ae8a8da30c3df65dc +++ www/viewmtn/README 46ffe1ce5080c9fd0081574ae8a8da30c3df65dc @@ -0,0 +1,62 @@ + +ViewMTN +------- + +A web interface to monotone. See "LICENSE" for distribution terms. +ViewMTN is Copyright 2005 Grahame Bowland. + +The minimum version of monotone required is: 0.24 + +For the graphs to work you'll need dotty installed - it is a +part of GraphViz. + http://www.research.att.com/sw/tools/graphviz/ + +ViewMTN requires mod_python. + http://www.modpython.org/ + +When installing be sure to copy config.py.example to config.py, and +then edit config.py (paying attention to the comments!) + +Common issues +------------- + +If you are getting a 404 error for "getfile.py", make sure that you have +updated ".htacess" to include the following line. + PythonHandler wrapper +"wrapper.py" contains several Python functions which are called +for various pages that do not need to go through mod_python's PSP +framework. The above line causes all page requests to go through +wrapper.py, which decides which function (or PSP file) should be called +to handle the request. + +(Note: recent versions of ViewMTN will not work at all if you are missing + the line above.) + +MacOS +----- + +I run ViewMTN on MacOS by using mod_python and apache2 compiled from source +using Darwin Ports: + http://darwinports.opendarwin.org/ +You should be able to install graphviz from Darwin Ports as well. + +The graphs seem to look terrible with the default fault from config.py.example; +I set the font to "Monaco" instead and it looks pretty good. + +Bugs, suggestions, feedback +--------------------------- + +Send any bugs, suggestions or feedback to: + Grahame Bowland + PO BOX 3105, Broadway, Nedlands WA 6009 Australia + +In particular, please look at the TODO file. If you're interested in +fixing any of the issues listed (or just adding extra TODO entries) +please go ahead - perhaps let me know so I can keep track and let you +know if the item is already done but not committed. + +As monotone is a distributed version control system, feel free to +grab a copy of viewmtn, commit to your local DB, etc. If you want to +send me your commits, email me (or catch me in #monotone) and we'll +work something out. + ============================================================ --- www/viewmtn/TODO 6372d06a8696794b11e1f5dece588de4d1057896 +++ www/viewmtn/TODO 6372d06a8696794b11e1f5dece588de4d1057896 @@ -0,0 +1,89 @@ + +BUGS: + + * \n in title of the ancestry graph + +TODO: + + * support for translation, and see if we can get some people to help with that + モノトネ + + * some sort of contest for a logo (with some sort of cool prize, perhaps + con someone into putting one up on offer or pay up myself.) + + * a revision selector interface - make sure it prints out the revision + selector to the person, so that they can use it on the command line later + ** make this AJAX; show the results *as the person types* ** + ** this feature would blow people out of the water ** + + * optionally pop-up a window (or some sort of AJAX box lurking someplace) + which updates with the commands that have been run - useful for debugging, + also useful for beginners to see how to do stuff. + + * Use monotone automate graph to do the ancestry graphing, and show some + future information. Perhaps cache based on the mtime of the db viewmtn + is looking at? Might be good enough. + + Show dotted lines for propogates (with that information on the arc) + + Show lines leading off from the boxes at either end of the graph (if + applicable) to give context that the image being shown is part of a + large overall graph. + + * Show information when mousing over long hex strings. + + * Magically make http:// ftp:// etc links inside files clickable + + * In the file diff view for multiple files, show the same change set + information that is available in the revision view - this will help + explain what's happened. For bonus points, index into the diff so you + can click on a file that changed and jump to that section in the diff. + + * Improve the file listing; use data from the new rosters branch + to get a better idea of when a given file was last touched. + + * When viewing a file, give a list of revisions in which that file + was recently changed. Include in the list a link to diff with each + revision. + + * generally clean up table formatting code; things like + list_of_branches() + list_of_tags() + revision_certs() + revision_details() + use a generic method to output the tables + + * from [mrb] + support for multiple databases (perhaps some sort of dropdown to say which + database you want to look in) - perhaps also make the branches page show the + branches in each of the DBs, for ease. + +AJAX ideas: + + * When hovering over nodes in the ancestry graph, display the ChangeLog. + + * When hovering over branch links, show heads and other information + + * When hovering over revisions, show author / date + + * When hovering over diff links + + * turn line numbering in file / diff view (etc) on and off + + * file viewer; treat merges as a special case and show the side which + actually has the resultant file (no change on that edge) + + * file viewer / diff viewer / anywhere showing info related to a rev + magic box that downloads and inserts + revision stuff. + + * we need some concept of selecting two points revisions for diffs or other + comparison. This is the main strength ViewCVS seems to have over us. + +LONG TERM: + + * provide some option for people without apache2 / mod_python to run the + thing; even if it's running the program from a standalone python webserver. + Would settle for a solution that required a cgi capable webserver rather than + specifically mod_python (and thus apache2) while still supporting mod_python + acceleration if present. ============================================================ --- www/viewmtn/about.psp 92120d31e268182f68917b20874c60f8f0ef70d2 +++ www/viewmtn/about.psp 92120d31e268182f68917b20874c60f8f0ef70d2 @@ -0,0 +1,66 @@ +<% + +from authors import authors + +psp.set_error_page("error.psp") +info = { 'title' : "About" } +req.write(template.header(info)) + +%> + +

Authors and Contributers

+ +
+<%= hq (authors.strip()) %>
+
+ +

Licensing

+ +
+<%
+req.write('''\
+Copyright (C) 2005  Grahame Bowland
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA''')
+%>
+
+ +

Dependencies

+ +

+ViewMTN is written in Python and +runs under mod_python. +

+ +

+Code highlighting via +GNU Enscript. +

+ +

+Graphing via GraphViz. +

+ +

+Graph colour generation algorithm from monotone-viz with modifications from Matt Johnston. +

+ +

+AJAX funtionality uses the MochiKit Javascript library. +

+ +<% +req.write(template.footer(info)) +%> ============================================================ --- www/viewmtn/authors.py 0ed0661cf3ddf1b402a73dfc1ff0fccc64ad05f1 +++ www/viewmtn/authors.py 0ed0661cf3ddf1b402a73dfc1ff0fccc64ad05f1 @@ -0,0 +1,11 @@ +authors='''Authors: +Grahame Bowland + +Contributors: +Matt Johnston +Nathaniel Smith +Bruce Stephens +Lapo Luchini +David Reiss + +''' ============================================================ --- www/viewmtn/branch.psp 08a0e167ebb6416ca636a59ae0bb4ac020b148af +++ www/viewmtn/branch.psp 08a0e167ebb6416ca636a59ae0bb4ac020b148af @@ -0,0 +1,160 @@ +<% + +import datetime +import config +import urllib +import common +import time +from common import parse_timecert, ago_string + +# +# branch.psp +# display a log of changes on a given branch, with an +# optional offset +# + +psp.set_error_page("error.psp") + +def prettify(s): + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + +if not form.has_key('branch'): + raise Exception("No branch specified.") + +page = 1 +if form.has_key('page'): + try: page = int(form['page']) + except: pass + +if form.has_key('rss'): + output = 'rss' +else: + output = 'html' + +if page < 1: page = 1 + +display = 25 +offset = page * display + +branch = form['branch'] + +page_title = "Changes to branch %s" % (hq(branch)) +if output == 'html': + info = { + 'title' : page_title, + 'branch_rss' : [branch] + } + req.write(template.header(info)) +elif output == 'rss': + req.write('''\ + + + en-us + + %s + %s + %s''' % (page_title, config.base_url, page_title)) + +heads = mt.heads(branch) + +recent = mt.toposort(mt.ancestors(heads) + heads) + +if display > len(recent): + display = len(recent) +if offset > len(recent): + offset = len(recent) + no_next = True +else: + no_next = False + +start = -1*offset +stop = start + display +if stop == 0: stop = None +recent = recent[start:stop] +recent.reverse() + +%> + +<% +if output == 'html': + req.write('''\ +

+This page lists changes %s through %s to %s. +It may also be useful to view the %s of this branch. +

''' % (-1*(stop or 0), (-1 * start), link('branch', branch), link('headofbranch', branch, 'current head revision'))) + req.write('') +%> + +<% +for idx, id in enumerate(recent): + certs = mt.certs(id) + + if output == 'rss': + req.write('\n') + req.write('%s\n' % (urllib.basejoin(config.base_url, 'revision.psp?id=%s' % (urllib.quote(id))))) + + certdate, quicklog, certinfo = None, "", "" + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = parse_timecert(value) + if output == 'rss': req.write("\t%s\n" % hq(certdate.strftime("%a, %d %b %Y %H:%M:%S +0000"))) + elif name == "branch": + # hackish, but oh well + if value == branch: + value = None + else: + name = "other_branches" + value = link("branch", value) + else: + if name == "changelog": + quicklog = common.quicklog(value) + if output == 'rss': req.write("\t%s\n%s\n" % (quicklog, hq(value))) + elif name == "author": + if output == 'rss': req.write("\t%s\n" % (hq(value))) + value = '
'.join(map(hq, value.split('\n'))) + if name != None and value != None: + certinfo += '
\n' % (prettify(name), value) + + now = datetime.datetime.utcnow() + ago = ago_string(certdate, now) + + if output == 'html': + style = "border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: black;" + if idx != 0: + style += "border-top-style: solid; border-top-width: 1px; border-top-color: black;" + revision = mt.revision(id) + diff_links = ' | '.join([link("diff", [t[1], id], "diff") for t in revision.get('old_revision', [])]) + style = "" + req.write('\n' % (style, ago, quicklog, link("revision", id, "revision info"), link("manifest", id, "browse files"), diff_links)) + req.write(certinfo) + elif output == 'rss': + req.write('\n') + +if output == 'html': + req.write('
%s:%s
%s ago: %s
%s | %s | %s
') + + c = [] + if page != 1: + c.append('Newer changes' % (urllib.quote(branch), page-1)) + if not no_next: + c.append('Older changes' % (urllib.quote(branch), page+1)) + req.write('

' + ' | '.join(c) + '

') + +%> + +<% + +if output == 'html': + req.write(template.footer(info)) +elif output == 'rss': + req.write('''\ +
+
+''') +%> + ============================================================ --- www/viewmtn/builtpython.sh c1d18a362622ec209ea6818957a5778d5374de45 +++ www/viewmtn/builtpython.sh c1d18a362622ec209ea6818957a5778d5374de45 @@ -0,0 +1,16 @@ +#!/bin/sh + +# generate the list of enscript formatting options +LANGS=enscriptlangs.py +echo -n 'enscript_langs = [' > "$LANGS" +for i in `enscript --help-highlight | grep Name | awk {'print $2'}`; do + echo -n "'$i', " >> "$LANGS" +done; echo ']' >> "$LANGS" + +# generate the help file data +AUTHORS=authors.py +echo -n "authors='''" > "$AUTHORS" +cat AUTHORS >> "$AUTHORS" +echo "'''" >> "$AUTHORS" + + ============================================================ --- www/viewmtn/common.py 19a25a6ea138661ce01454ec58c1ea20fcd884aa +++ www/viewmtn/common.py 19a25a6ea138661ce01454ec58c1ea20fcd884aa @@ -0,0 +1,239 @@ + +import datetime +import urllib +import pydoc +import time + +escape_function = pydoc.HTMLRepr().escape + +def type_wrapper(e, x): + if x == None: + return "" + elif type(x) == type([]): + return '
'.join(map(e, x)) + else: + return e(x) + +def parse_timecert(value): + return apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + +def get_branch_links(mt, branches): + if len(branches) > 1: + branch_links = "branches " + else: + branch_links = "branch " + links = [] + for branch in branches: + links.append(link(mt, "branch", branch)) + return branch_links + ', '.join(links) + +def extract_cert_from_certs(certs, certname, as_list=False): + rv = [] + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == certname: + if not as_list: + return value + else: + rv.append(value) + return rv + +def determine_date(certs): + dateval = extract_cert_from_certs(certs, "date") + if dateval == None: + return None + else: + return parse_timecert(dateval) + +def quicklog(value): + hq = html_escape() + rv = hq(value.strip().split('\n')[0]) + if rv.startswith('*'): + rv = rv[1:].strip() + return rv + +def ago_string(event, now): + def plural(v, singular, plural): + if v == 1: + return "%d %s" % (v, singular) + else: + return "%d %s" % (v, plural) + now = datetime.datetime.utcnow() + ago = now - event + if ago.days > 0: + rv = "%s, %s" % (plural(ago.days, "day", "days"), + plural(ago.seconds / 3600, "hour", "hours")) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + rv = "%s, %s" % (plural(hours, "hour", "hours"), + plural(minutes, "minute", "minutes")) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + rv = "%s, %s" % (plural(minutes, "minute", "minutes"), + plural(seconds, "second", "seconds")) + return rv + +def link(mt, link_type, link_to, description = None, no_quote = False): + hq = html_escape() + if not no_quote and description != None: description = hq(description) + if link_type == "revision": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + elif link_type == "diff" or link_type == "download_diff": + link_to = map(urllib.quote, filter(lambda x: x != None, link_to)) + if link_type == "diff": + handler = "diff.psp" + else: + handler = "getdiff.py" + uri = '%s?id1=%s&id2=%s' % (handler, link_to[0], link_to[1]) + if len(link_to) == 3: + uri += '&fname=%s' % (link_to[2]) + rv = '' + if description != None: rv += description + else: rv += "diff" + rv += '' + elif link_type == "download": + if type(link_to) == type([]): + rv = '' % (urllib.quote(link_to[0]), + urllib.quote(link_to[1])) + link_id = link_to[0] + else: + rv = '' % (urllib.quote(link_to)) + link_id = link_to + if description != None: rv += description + "" + else: rv = "[" + rv + hq(link_id[:8]) + ".." + "]" + elif link_type == "file": + revision_id, path = link_to + rv = '' % (urllib.quote(revision_id), + urllib.quote(path)) + if description != None: rv += description + "" + else: rv = "[" + rv + hq(path + '@' + revision_id[:8]) + ".." + "]" + elif link_type == "fileinbranch": + branch, path = link_to + rv = '' % (urllib.quote(branch), + urllib.quote(path)) + if description != None: rv += description + "" + else: rv = "[" + rv + hq(path + '@' + branch) + "]" + elif link_type == "branch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to) + rv += '' + elif link_type == "tar": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv = "tar of [" + rv + hq(link_to[:8]) + "..]" + "]" + rv += '' + elif link_type == "headofbranch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += "head of " + hq(link_to) + rv += '' + elif link_type == "manifest": + if type(link_to) == type([]): + link_to, path = link_to + rv = '' % (urllib.quote(link_to), urllib.quote(path)) + else: + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + else: + rv = 'Unknown link type: %s' % (hq(link_type)) + return '%s' % (hq(link_type+'Link'), rv) + +def html_escape(): + "returns a function stolen from pydoc that can be used to escape HTML" + return lambda x: type_wrapper(escape_function, x) + +from enscriptlangs import enscript_langs +from utility import run_command +import mimetypes +import config +import pipes + +# is it binary? +def is_binary(str): + nontext_chars = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1c\x1d\x1e\x1f" + check = {} + for char in nontext_chars: + check[char] = True + for i in str: + if check.has_key(i): return True + return False + +# hm, later on might make this be some javascript that does an call back to the server. +# then could have a pull down to let people choose which enscript encoding to use, and +# just update the DOM with the new data. +def colourise_code(req, hq, path, contents, filter=None): + mime_type = mimetypes.guess_type(path)[0] + if mime_type == None: mime_type = 'text/plain' + if mime_type == 'image/png' or mime_type == 'image/jpeg' or mime_type == 'image/gif': + display_as_image = True + else: display_as_image = False + + # okay; can we guess a valid enscript filter to run this through? + tsp = mime_type.split('/', 1) + if filter == None and tsp[0] == 'text': + candidate = tsp[1] + if candidate.startswith('x-'): candidate = candidate[2:] + if candidate.endswith('src'): candidate = candidate[:-3] + if candidate.endswith('hdr'): candidate = candidate[:-3] + if candidate == 'c++': candidate = 'cpp' # ugly + if candidate in enscript_langs: filter = candidate + if filter == None: + # heh, will at least work for lua files + last_dot = path.rfind('.') + if last_dot == -1: last_dot = 0 + candidate = path[last_dot:] + if candidate in enscript_langs: filter = candidate + + # if no filter then let's check if it's binary or not; if not binary + # we'll just treat it as text; otherwise display a warning and a download + # link + if filter == None and not is_binary(contents): + filter = 'text' + + req.write('''
''') + if display_as_image: + req.write('''') + def stop_code(): + req.write('') + def text(): + start_code() + req.write(hq(contents)) + stop_code() + def enscript(): + command = config.enscript_path + ' -o - --color --language=html' + command += ' --highlight=%s' % (pipes.quote(filter)) + result = run_command(command, to_child=contents) + if result['exitcode'] != 0: + raise Exception('Error running enscript (%s) : "%s".' % (hq(command), hq(result['childerr']))) + in_contents = False + for line in result['fromchild'].split('\n'): + if line.startswith('
'):
+		    in_contents = True
+		    start_code()
+		elif line.startswith('
'): + in_contents = False + stop_code() + elif in_contents: + req.write(line + '\r\n') + if filter == "text": text() + else: enscript() + else: + req.write('''

This file seems to binary and not suitable for display in the browser. You must %s the file and use a suitable viewer.

''' % (link("download", [matching_file_id, path], "download"))) + req.write('''
''') ============================================================ --- www/viewmtn/config.py.example 7627e154e30ee6e88e547312876aa5cca8bf7c7e +++ www/viewmtn/config.py.example 7627e154e30ee6e88e547312876aa5cca8bf7c7e @@ -0,0 +1,67 @@ +# +# config.py +# +# This python script is imported by ViewMTN +# Note that changes to the script may not be +# noticed immediately by ViewMTN, as mod_python +# caches imported Python modules. +# +# If config changes are not picked up, reloading +# the web server should solve the issue. +# +# If you want to run multiple viewmtn installs from +# a single apache server, you might want to look at +# giving them seperate python interpreter instances, +# ie set PythonInterpreter viewmtn1, viewmtn2 etc +# in .htaccess. + +import sys + +# the base URL of this install +base_url = 'http://localhost/~grahame/viewmtn/' + +# the path to the 'monotone' binary +monotone = '/opt/monotone/bin/monotone' + +# the monotone database to be shared out +# obviously, everything in this database might +# become public if something goes wrong; probably +# a good idea not to leave your private key in it +dbfile = '/path/to/monotone.db' + +# where to find GNOME icons (used in manifest listing) +gnome_mimetype_icon_path = '/path/to/share/icons/gnome/' + +# and where they are on the web +gnome_mimetype_uri = 'mimetypes/' + +# where to find GNU enscript +enscript_path = '/usr/bin/enscript' + +graphopts = { + # a directory (must be writable by the web user) + # in which viewmtn can output graph files + # (you should set up a cronjob to delete old ones + # periodically) + 'directory' : '/path/to/graph/directory', + + # a URL, relative or absolute, at which the files + # in the 'graphdir' directory can be found. Should + # end in a '/' character + 'uri' : 'graph/', + + # the path to the 'dot' program + 'dot' : '/usr/bin/dot', + + # options to use for nodes in the dot input file + # we generate. + 'nodeopts' : { 'fontname' : 'Windsor', + 'fontsize' : '8', + 'shape' : 'box', + 'height' : '0.3', + 'spline' : 'true', + 'style' : 'filled', + 'fillcolor' : '#dddddd' } +} + + ============================================================ --- www/viewmtn/diff.psp 4c495def64a5c64f8da213eaa870663f859b404e +++ www/viewmtn/diff.psp 4c495def64a5c64f8da213eaa870663f859b404e @@ -0,0 +1,65 @@ +<% + +import mimetypes +import datetime +import monotone +import common +import config +import urllib +import pipes +import time +import os +from enscriptlangs import enscript_langs +from html import get_icon, TableWriter +from utility import run_command +from common import colourise_code + +# file.psp ; provide information about a file +# if possible, display it (with syntax highlighting, etc.) +# provide a download link + +psp.set_error_page("error.psp") + +if not form.has_key('id1') and not form.has_key('id2'): + raise Exception("Revision IDs not specified.") + +id1, id2 = form['id1'], form['id2'] +if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): + raise Exception("Specified revision IDs are not valid.") + +# should support diffing more than one file eventually +fname = form.get('fname', None) +if fname != None: + title = 'of file %s ' % hq(fname) + files = [fname] +else: + title = '' + files = [] + +title = 'Diff %sbetween revisions [%s..] and [%s..]' % (title, hq(id1[:8]), hq(id2[:8])) + +info = { 'title' : title } +req.write(template.header(info)) + +if fname != None: + file_str = 'of file ' + link("file", [id2, fname], fname) + ' ' +else: + file_str = '' +req.write('

The unified diff %sbetween revisions %s and %s is displayed below.

' % (file_str, + link("revision", id1), + link("revision", id2))) + +if fname != None: + req.write('

You might wish to view the %s between these revisions, without the restriction to this particular file.

' % link('diff', [id1, id2], 'diff')) + +req.write('

You can also %s this diff verbatim.

' % link("download_diff", [id1, id2, fname], "download")) + +### FIXME FIXME +### this means having _the entire file_ in memory +### which is pointless and dumb. +contents = mt.diff(id1, id2, files) +colourise_code(req, hq, 'a.diff', contents, filter='diffu') + +req.write(template.footer(info)) + +%> ============================================================ --- www/viewmtn/enscriptlangs.py 95c443cac402a883b7f523a46faba66d2c79caaf +++ www/viewmtn/enscriptlangs.py 95c443cac402a883b7f523a46faba66d2c79caaf @@ -0,0 +1,1 @@ +enscript_langs = ['ada', 'asm', 'awk', 'bash', 'changelog', 'cpp', 'csh', 'c', 'delphi', 'diffs', 'diff', 'diffu', 'dylan', 'eiffel', 'elisp', 'erlang', 'forth', 'fortran_pp', 'fortran', 'haskell', 'html', 'icon', 'idl', 'inf', 'javascript', 'java', 'ksh', 'lua', 'm4', 'mail', 'makefile', 'matlab', 'nroff', 'oberon2', 'objc', 'outline', 'oz', 'pascal', 'perl', 'postscript', 'pyrex', 'python', 'rfc', 'scheme', 'sh', 'skill', 'Smalltalk', 'sml', 'sql', 'states', 'synopsys', 'tcl', 'tcsh', 'tex', 'vba', 'verilog', 'vhdl', 'vrml', 'wmlscript', 'zsh', ] ============================================================ --- www/viewmtn/error.psp 312eeeb8ea00ab36b0b5d64eb8fa5b65e75cd418 +++ www/viewmtn/error.psp 312eeeb8ea00ab36b0b5d64eb8fa5b65e75cd418 @@ -0,0 +1,51 @@ + + +Error + + + +<% + +import common +hq = common.html_escape() + +# now; if they raised a plain Exception() then we shall display a simple error +# message without a traceback: this is an intentionally raised Exception rather than +# a program error +e_type, e_value, e_traceback = sys.exc_info() + +if (str(e_type) == "exceptions.Exceptionf"): + # this is a deliberate exception, pretty print +%> + +

+An error has occurred: +

+ +

+<%= hq(str(e_value)) %> +

+ +

+If necessary, please report this error to the system administrator. You may wish to return +to the index page. +

+ +<% +else: + # show the raw exception +%> +

Exception:

+ +

+<% + import traceback + mesg = traceback.format_exception(e_type, e_value, e_traceback) + mesg = map(hq, mesg) + req.write('

' + '
'.join(mesg) + '
') + +%> +

+ + + ============================================================ --- www/viewmtn/file.psp f19f6fefe38a97ca636705f070f4535534947a48 +++ www/viewmtn/file.psp f19f6fefe38a97ca636705f070f4535534947a48 @@ -0,0 +1,78 @@ +<% + +import mimetypes +import datetime +import monotone +import common +import config +import urllib +import pipes +import time +import os +from enscriptlangs import enscript_langs +from html import get_icon, TableWriter +from utility import run_command +from common import colourise_code, get_branch_links + +# file.psp ; provide information about a file +# if possible, display it (with syntax highlighting, etc.) +# provide a download link + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") + +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +if not form.has_key('path'): + raise Exception("No path specified.") +path = form['path'] + +certs = mt.certs(id) +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) + +manifest = [(t[3], t[1]) for t in mt.manifest_of(id)['file']] + +matching_file_id = None +for file_id, filename in manifest: + if filename == path: + matching_file_id = file_id + break +if matching_file_id == None: + raise Exception("File not found in this revision.") + +link_components = "" +components = [''] +components += path.split('/') +for idx, component in enumerate(components[:-1]): + link_components += link("manifest", [id, '/'.join(components[:idx+1])], component + '/') +link_components += components[-1] +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "File '%s' in revision %s of %s" % (link_components, + link("revision", id), + branch_links) + } +req.write(template.header(info)) + +req.write('

For more information about the revision containing this file, see its %s page. For other files contained in this revision, see the %s.

' % \ + (link("revision", id, "revision"), + link("manifest", id, "manifest"))) + +req.write('

You can also %s the file verbatim. In addition, this %s will always download the latest revision of the file.

' \ + % (link("download", [matching_file_id, path], "download"), + link("fileinbranch", [branches[0], path], "link"))) + +### FIXME FIXME +### this means having _the entire file_ in memory +### which is pointless and dumb. +contents = mt.file(matching_file_id) +colourise_code(req, hq, path, contents) + +req.write(template.footer(info)) + +%> ============================================================ --- www/viewmtn/fileinbranch.psp 4f01b7f1260ca7a9c756047ea0dcfaa5fabc4b0c +++ www/viewmtn/fileinbranch.psp 4f01b7f1260ca7a9c756047ea0dcfaa5fabc4b0c @@ -0,0 +1,69 @@ +<% + +import monotone +import urllib +import urlparse + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +if not form.has_key('path'): + raise Exception("No path specified.") +branch = form['branch'] +path = form['path'] + +# fileinbranch.psp +# +# determine the latest version of the file +# in a branch (and present a choice iff there +# are multiple heads and the files difer in them) + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") + +# build a hash with a list of all files in each head which +# match PATH and their file ID. +file_version = {} +for id in heads: + file_revisions = filter(lambda x: x[1] == path, mt.manifest_of(id)['file']) + if len(file_revisions) > 1: + raise Exception("More than one file matches path?") + elif len(file_revisions) == 1: + file_version[id] = file_revisions[0][0] +if len(file_version.values()) == 0: + raise Exception("No file such file found in this branch.") + +unique = True +last = None +values = file_version.values() +for file_revision in values: + if last != None and file_revision != last: + unique = False + break + last = file_revision +if unique: + psp.redirect(urlparse.urljoin(req.uri, "getfile.py?id=%s&path=%s" % (urllib.quote(values[0]), urllib.quote(path)))) +else: + info = {'title' : "Latest version of %s in branch %s" % (hq(path), hq(branch))} + req.write(template.header(info)) +%> +

+The branch you have selected has multiple head revisions, and the file you are attempting +to access differs between them. The head revisions are listed below, along with +links to download "<%=hq(path)%>" from that revision. +

+ + + +<% + for id in file_version.keys(): + req.write('' % (link("revision", id), link("download", [file_version[id], path], "view file"))) + +%> +
Head revisionView file
%s%s
+ +<% +req.write(template.footer(info)) +%> ============================================================ --- www/viewmtn/grapher/grapher.py 5d38fbee117ab4cf50f8d9fb90050a29d8fa3c4d +++ www/viewmtn/grapher/grapher.py 5d38fbee117ab4cf50f8d9fb90050a29d8fa3c4d @@ -0,0 +1,171 @@ +#!/usr/bin/python + +import sys +sys.path.append('..') + +import monotone +import config +import common +import string + +from StringIO import StringIO + +try: + a = set() +except: + import sets + set = sets.Set + +# algorithm: +# output a Dot file representing the graph +# use this to go to SVG +# use an arbitrary tool to go to PNG + HTML image map +# cut this up into squares +# +# caching: +# the trouble is that the graph can change arbitrarily, even +# if it's unlikely to. eg. someone could commit a rev to a +# n.v.monotone's first ever rev and then merge it with head or +# something awful... +# +# hrm. +# so, for each block in the image we should list the revs it +# contains. when updating we find the highest image (most vertical +# in the graph) that contains an affected rev. Update the row that +# image is in downwards. + +mt = monotone.Monotone(config.monotone, config.dbfile) +nodeopts = ','.join(map(lambda opt: '%s="%s"' % (opt, config.graphopts['nodeopts'][opt]), config.graphopts['nodeopts'].keys())) + +def toposorted_revs(branch): + heads = mt.heads(branch) + return mt.toposort(mt.ancestors(heads)) + +def dot_escape(s): + # kinda paranoid, should probably revise later + permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/' + return ''.join(filter(lambda x: x in permitted, s)) + +class RevisionNode: + def __init__(self, revision_id): + self.revision_id = revision_id + self.parents = mt.parents(revision_id) + self.children = mt.children(revision_id) + + def output_node(self): + opts = [nodeopts] + if len(self.parents) > 1: + opts.append('label=""') + opts.append('shape="circle"') + else: + certs = mt.certs(self.revision_id) + label = '[%s..]' % (self.revision_id[:8]) + date = common.extract_cert_from_certs(certs, "date") + if date: + date = dot_escape(date) + date = date[0:date.find("T")] + label += ' on %s' % (date) + label += '\\n' + author = common.extract_cert_from_certs(certs, "author") + if author: + label += '\\n%s' % (dot_escape(author)) + opts.append('label="%s"' % label) + opts.append('shape="box"') + return ' "%s" [' % self.revision_id + ','.join(opts) + ']\n' + + def output_edges(self, local_revisions): + edges = [] + for revision_id in self.children: + if revision_id in local_revisions: + edges.append(' "%s" -> "%s"' % (self.revision_id, revision_id)) + else: + edges.append(' "%s" ->' % (self.revision_id)) + return '\n'.join(edges) + '\n' + +class RevisionGroup: + def __init__(self): + """A non-empty group representing a direct graph of revisions. + Only the first and last (top and bottom) revisions may have + links that carry outside of the group. The bottom revision must + have at most one child.""" + self.nodes = [] + self.revisions = set() + self.references = set() + self.first, self.last = None, None + self.is_valid = False + + def append(self, node): + self.nodes.append(node) + self.revisions.add(node.revision_id) + self.last = node + if not self.first: + self.first = node + self.is_valid = True + else: + # note; we make sure that we add to self.references + # after checking validity, as the final node may have links + # that carry outside of the group (so these links shouldn't + # count wrt. validity) + unsatisfied = self.revisions - self.references + sys.stderr.write("%s : %s\n" % (node.revision_id, unsatisfied)) + self.is_valid = len(unsatisfied) == 0 + self.references = self.references.union(node.parents + node.children) + sys.stderr.write("%s : %s (after add of %s)\n" % (node.revision_id, unsatisfied, str(node.parents + node.children))) + if len(self.last.children) > 1: + self.is_valid = False + + def length(self): + return len(self.revisions) + + def output(self, fd): + fd.write(''' +digraph ancestry { + ratio=compress + nodesep=0.1 + ranksep=0.2 +''') + + map(lambda node: fd.write(node.output_node()), self.nodes) + map(lambda node: fd.write(node.output_edges(self.revisions)), self.nodes) + fd.write(''' +}''') + + +class RevisionGraph: + def __init__(self, revisions, min_group_size): + self.revisions = revisions + self.min_group_size = min_group_size + self.groups = [] + self.revision_to_group = {} + + def calculate(self): + group, size = RevisionGroup(), 0 + for revision in self.revisions: + node = RevisionNode(revision) + group.append(node) + self.revision_to_group[revision] = group + size += 1 + + if size >= self.min_group_size and group.is_valid: + self.groups.append(group) + break + group, size = RevisionGroup(), 0 + + if size >= 0: + self.groups.append(group) + + def output(self): + fd = StringIO() + for group in self.groups: + group.output(fd) + fd.read() + print fd.buf + break + +if __name__ == '__main__': + branch = sys.argv[1] + revs = toposorted_revs(branch) + graph = RevisionGraph(revs, 20) + graph.calculate() + graph.output() + ============================================================ --- www/viewmtn/headofbranch.psp f2f0d9d64ffa71086b2451fa0eb994d40fe35db6 +++ www/viewmtn/headofbranch.psp f2f0d9d64ffa71086b2451fa0eb994d40fe35db6 @@ -0,0 +1,61 @@ +<% + +import urllib +import urlparse + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +branch = form['branch'] + +# headofbranch.psp +# +# we want to redirect the user to revision.psp displaying +# the head ID of this branch. +# +# if more than one HEAD exists, prompt the user +# and allow them to choose which one to view +# otherwise, redirect to revision.psp and show that +# ID. + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") +elif len(heads) == 1: + # a single head ID; redirect to it + id = heads[0] + psp.redirect(urlparse.urljoin(req.uri, "revision.psp?id=%s" % (urllib.quote(id)))) +else: + info = {'title' : "Branch details for %s" % (hq(branch))} + req.write(template.header(info)) +%> +

+The following head IDs are available. Please select one to view. You might find the +<%= link("branch", branch, "recent changes") %> page for this branch helpful. +

+ + + +<% + for id in heads: + certs = mt.certs(id) + # find the author cert + author = None + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name =="author": + author = value + req.write('' % (link("revision", id), hq(author or ""))) + +%> +
Head revisionRevision author
%s%s
+ +<% +req.write(template.footer(info)) +%> + ============================================================ --- www/viewmtn/help.psp 02a65e870a2cfc34680ba57740ea4fe9f82a48c7 +++ www/viewmtn/help.psp 02a65e870a2cfc34680ba57740ea4fe9f82a48c7 @@ -0,0 +1,30 @@ +<% + +from authors import authors + +psp.set_error_page("error.psp") +info = { 'title' : "Help" } +req.write(template.header(info)) + +%> + +

+ViewMTN is a web interface to the Monotone revision control +system. These web pages provide various methods to access the data +controlled within a particular Monotone database. +

+ +

+To make full use of this web interface, it is recommended that you read +the Monotone +manual.

+ +

+Feature suggestions, bug reports and patches are welcome. Please go +to the ViewMTN +software page and follow the contact instructions there.

+ +<% +req.write(template.footer(info)) +%> ============================================================ --- www/viewmtn/html.py 8344c7b901ccd05b338a1322b0c0b57998985fcc +++ www/viewmtn/html.py 8344c7b901ccd05b338a1322b0c0b57998985fcc @@ -0,0 +1,99 @@ + +import mimetypes +import urllib +import common +import config +import time +import os +from version import release + +hq = common.html_escape() + +def get_icon(filename, mime_type=None, is_directory=False): + if config.gnome_mimetype_icon_path == None: + return None + if is_directory: + filename = 'gnome-fs-directory.png' + else: + if mime_type == None: + mime_type = mimetypes.guess_type(filename)[0] + if not mime_type: + mime_type = "text" + if mime_type.endswith('hdr'): + mime_type = mime_type[:-3] + 'src' + # some specific fixups + filename = 'gnome-mime-' + mime_type.replace('/', '-') + '.png' + if os.access(os.path.join(config.gnome_mimetype_icon_path, filename), os.R_OK): + return '/'.join([config.gnome_mimetype_uri, filename]) + else: + return None + +class TableWriter: + def __init__(self, req): + self.req = req + self.odd = False + def start(self): + self.req.write('') + def write(self, row, is_header=False): + if self.odd: c = 'odd' + else: c = 'even' + if is_header: self.odd = False + else: self.odd = not self.odd + self.req.write('' % (c) + row + '\n') + def stop(self): + self.req.write('
') + +def strip_html_from(t): + in_tag, rv = False, '' + for c in t: + if in_tag and c == '>': + in_tag = False + elif not in_tag and c == '<': + in_tag = True + elif not in_tag: + rv += c + return rv + +class Template: + def header(self, info): + if not info.has_key("title"): info['title'] = "untitled" + if not info.has_key("extra_header"): info['extra_header'] = '' + if not info.has_key("short_title"): + info["short_title"] = strip_html_from(info["title"]) + + if info.has_key('branch_rss'): + for branch in info['branch_rss']: + info['extra_header'] += '\n' % (urllib.quote(branch), hq(branch)) + return """\ + + + + + ViewMTN: %(short_title)s + + + + + %(extra_header)s + + + + + +

%(title)s

+ """ % (info) + def footer(self, info): + rv = """ + + ''' % (release, time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime())) + return rv ============================================================ --- www/viewmtn/index.psp 0b257820d558b3a44081ccba10279e76d9e82555 +++ www/viewmtn/index.psp 0b257820d558b3a44081ccba10279e76d9e82555 @@ -0,0 +1,54 @@ +<% + +psp.set_error_page("error.psp") +info = { 'title' : "Branches" } +req.write(template.header(info)) + +branches = mt.branches() + +%> + +<% +if len(branches) == 0: + # no branches +%> + +

+There are no branches in the provided monotone database. There is +therefore nothing for ViewMTN to display! +

+ +<% +else: + # okay, so display the branches +%> +

+There are <%=len(branches)%> branches in this +Monotone database. Each branch +might represent an individual project. If you are new to Monotone, the +Monotone tutorial +may help you understand these pages better. +

+ +

+Select a branch from the list below to view recent changes that have occured +within it. The list of tagged revisions could be +helpful if you are looking for a particular revision. +

+ + + +<% + branches.sort() + for branch in branches: + req.write('' % (link("branch", branch, branch))) + +%> +
Branch
%s
+ +<% + +req.write(template.footer(info)) + +%> + ============================================================ --- www/viewmtn/json.py 8011edc26270fc6ae7e34887279cb08479418368 +++ www/viewmtn/json.py 8011edc26270fc6ae7e34887279cb08479418368 @@ -0,0 +1,310 @@ +import string +import types + +## json.py implements a JSON (http://json.org) reader and writer. +## Copyright (C) 2005 Patrick D. Logan +## Contact mailto:address@hidden +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation; either +## version 2.1 of the License, or (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public +## License along with this library; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +class _StringGenerator(object): + def __init__(self, string): + self.string = string + self.index = -1 + def peek(self): + i = self.index + 1 + if i < len(self.string): + return self.string[i] + else: + return None + def next(self): + self.index += 1 + if self.index < len(self.string): + return self.string[self.index] + else: + raise StopIteration + def all(self): + return self.string + +class WriteException(Exception): + pass + +class ReadException(Exception): + pass + +class JsonReader(object): + hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15} + escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'} + + def read(self, s): + self._generator = _StringGenerator(s) + result = self._read() + return result + + def _read(self): + self._eatWhitespace() + peek = self._peek() + if peek is None: + raise ReadException, "Nothing to read: '%s'" % self._generator.all() + if peek == '{': + return self._readObject() + elif peek == '[': + return self._readArray() + elif peek == '"': + return self._readString() + elif peek == '-' or peek.isdigit(): + return self._readNumber() + elif peek == 't': + return self._readTrue() + elif peek == 'f': + return self._readFalse() + elif peek == 'n': + return self._readNull() + elif peek == '/': + self._readComment() + return self._read() + else: + raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all() + + def _readTrue(self): + self._assertNext('t', "true") + self._assertNext('r', "true") + self._assertNext('u', "true") + self._assertNext('e', "true") + return True + + def _readFalse(self): + self._assertNext('f', "false") + self._assertNext('a', "false") + self._assertNext('l', "false") + self._assertNext('s', "false") + self._assertNext('e', "false") + return False + + def _readNull(self): + self._assertNext('n', "null") + self._assertNext('u', "null") + self._assertNext('l', "null") + self._assertNext('l', "null") + return None + + def _assertNext(self, ch, target): + if self._next() != ch: + raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all()) + + def _readNumber(self): + isfloat = False + result = self._next() + peek = self._peek() + while peek is not None and (peek.isdigit() or peek == "."): + isfloat = isfloat or peek == "." + result = result + self._next() + peek = self._peek() + try: + if isfloat: + return float(result) + else: + return int(result) + except ValueError: + raise ReadException, "Not a valid JSON number: '%s'" % result + + def _readString(self): + result = "" + assert self._next() == '"' + try: + while self._peek() != '"': + ch = self._next() + if ch == "\\": + ch = self._next() + if ch in 'brnft': + ch = self.escapes[ch] + elif ch == "u": + ch4096 = self._next() + ch256 = self._next() + ch16 = self._next() + ch1 = self._next() + n = 4096 * self._hexDigitToInt(ch4096) + n += 256 * self._hexDigitToInt(ch256) + n += 16 * self._hexDigitToInt(ch16) + n += self._hexDigitToInt(ch1) + ch = unichr(n) + elif ch not in '"/\\': + raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all()) + result = result + ch + except StopIteration: + raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all() + assert self._next() == '"' + return result + + def _hexDigitToInt(self, ch): + try: + result = self.hex_digits[ch.upper()] + except KeyError: + try: + result = int(ch) + except ValueError: + raise ReadException, "The character %s is not a hex digit." % ch + return result + + def _readComment(self): + assert self._next() == "/" + second = self._next() + if second == "/": + self._readDoubleSolidusComment() + elif second == '*': + self._readCStyleComment() + else: + raise ReadException, "Not a valid JSON comment: %s" % self._generator.all() + + def _readCStyleComment(self): + try: + done = False + while not done: + ch = self._next() + done = (ch == "*" and self._peek() == "/") + if not done and ch == "/" and self._peek() == "*": + raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all() + self._next() + except StopIteration: + raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all() + + def _readDoubleSolidusComment(self): + try: + ch = self._next() + while ch != "\r" and ch != "\n": + ch = self._next() + except StopIteration: + pass + + def _readArray(self): + result = [] + assert self._next() == '[' + done = self._peek() == ']' + while not done: + item = self._read() + result.append(item) + self._eatWhitespace() + done = self._peek() == ']' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert ']' == self._next() + return result + + def _readObject(self): + result = {} + assert self._next() == '{' + done = self._peek() == '}' + while not done: + key = self._read() + if type(key) is not types.StringType: + raise ReadException, "Not a valid JSON object key (should be a string): %s" % key + self._eatWhitespace() + ch = self._next() + if ch != ":": + raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch) + self._eatWhitespace() + val = self._read() + result[key] = val + self._eatWhitespace() + done = self._peek() == '}' + if not done: + ch = self._next() + if ch != ",": + raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch) + assert self._next() == "}" + return result + + def _eatWhitespace(self): + p = self._peek() + while p is not None and p in string.whitespace or p == '/': + if p == '/': + self._readComment() + else: + self._next() + p = self._peek() + + def _peek(self): + return self._generator.peek() + + def _next(self): + return self._generator.next() + +class JsonWriter(object): + + def _append(self, s): + self._results.append(s) + + def write(self, obj, escaped_forward_slash=False): + self._escaped_forward_slash = escaped_forward_slash + self._results = [] + self._write(obj) + return "".join(self._results) + + def _write(self, obj): + ty = type(obj) + if ty is types.DictType: + n = len(obj) + self._append("{") + for k, v in obj.items(): + self._write(k) + self._append(":") + self._write(v) + n = n - 1 + if n > 0: + self._append(",") + self._append("}") + elif ty is types.ListType or ty is types.TupleType: + n = len(obj) + self._append("[") + for item in obj: + self._write(item) + n = n - 1 + if n > 0: + self._append(",") + self._append("]") + elif ty is types.StringType or ty is types.UnicodeType: + self._append('"') + obj = obj.replace('\\', r'\\') + if self._escaped_forward_slash: + obj = obj.replace('/', r'\/') + obj = obj.replace('"', r'\"') + obj = obj.replace('\b', r'\b') + obj = obj.replace('\f', r'\f') + obj = obj.replace('\n', r'\n') + obj = obj.replace('\r', r'\r') + obj = obj.replace('\t', r'\t') + self._append(obj) + self._append('"') + elif ty is types.IntType or ty is types.LongType: + self._append(str(obj)) + elif ty is types.FloatType: + self._append("%f" % obj) + elif obj is True: + self._append("true") + elif obj is False: + self._append("false") + elif obj is None: + self._append("null") + else: + raise WriteException, "Cannot write in JSON: %s" % repr(obj) + +def write(obj, escaped_forward_slash=False): + return JsonWriter().write(obj, escaped_forward_slash) + +def read(s): + return JsonReader().read(s) ============================================================ --- www/viewmtn/manifest.psp 722b15a5c3eeca08a8415264c691f2f1966b0cbc +++ www/viewmtn/manifest.psp 722b15a5c3eeca08a8415264c691f2f1966b0cbc @@ -0,0 +1,201 @@ +<% + +import datetime +import monotone +import common +import config +import urllib +import time +from html import get_icon, TableWriter +from common import get_branch_links + +# +# manifest.psp +# display all information we can about a particular manifest +# + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") + +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +if not form.has_key('path'): + path = '' +else: + path = form['path'] +if path.startswith('/'): path = path[1:] +if path.endswith('/'): path = path[-1:] + +certs = mt.certs(id) +manifest = [(t[3], t[1]) for t in mt.manifest_of(id)['file']] + +link_components = "" +components = [''] +components += path.split('/') +for idx, component in enumerate(components[:-1]): + link_components += link("manifest", [id, '/'.join(components[:idx+1])], component + '/') +link_components += components[-1] +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "Dir '%s' in revision %s of %s" % (link_components, + link("revision", id), + branch_links) + } +req.write(template.header(info)) +%> + +

+This page shows files contained within a particular revision; for more information +about that revision see its page: <%= link("revision", id) %>. +

+ +

+All <%=len(manifest)%> files in this manifest can be downloaded together in a <%=link("tar", id, "tar archive")%>. +

+ +<% + +# filter this list of files into two lists; +# subdirectories of 'path' +# files in 'path' +# each will contain entries relative to 'path' +# +# NB: the following code will break (perhaps) if monotone ever supports +# empty subdirectories. +subdirs, files, last_dir = [], [], None +# root dir is depth 0, subdir of that 1, .. +if path == '': + sp = [] +else: + sp = path.split('/') +depth = len(sp) + +for file_id, filename in manifest: + parts = filename.split('/') + + if len(parts) < depth or parts[:depth] != sp[:depth]: + continue + + if len(parts) > depth + 1 and parts[depth] != last_dir: + subdirs.append((parts[depth], '/'.join(parts[:depth+1]))) + last_dir = parts[depth] + elif len(parts) == depth + 1: + files.append((parts[-1], file_id, filename)) + +# hopefully this will eventually be less ugly +# +# we need to find out the revision in which each of the objects in +# 'files' was last touched. so iterate; get the certs for each ancestor +# of the current revision, until we have that information for every +# file in 'files' + +ancestors = mt.toposort(mt.ancestors([id])) +ancestors.append(id) +ancestors.reverse() # so we can iterate back through time.. +file_to_rev = {} + +for name, file_id, file in files: + file_to_rev[file] = None + +found = 0 +for ancestor in ancestors: + rev = mt.revision(ancestor) + if rev.has_key('old_revision') and len(rev['old_revision']) > 1: + # this is a merge, exclude from our search + continue + for type in rev.keys(): + for stanza in rev[type]: + affected = None + if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": + affected = stanza[1] + elif type == "rename_file" or type == "rename_dir": affected = stanza[3] + if affected != None and file_to_rev.has_key(affected) and file_to_rev[affected] == None: + file_to_rev[affected] = ancestor + found += 1 + if found >= len(files): break + +revinfo = {} +now = datetime.datetime.utcnow() +for filename in file_to_rev.keys(): + rev = file_to_rev[filename] + + if rev == None: + certs = [] + else: + certs = mt.certs(rev) + + ago, author, certdate, quicklog = None, None, None, None + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + elif name == "changelog": + quicklog = value.strip().split('\n')[0] + if quicklog.startswith('*'): quicklog = quicklog[1:].strip() + elif name == "author": + author = value + if certdate: + ago = now - certdate + if ago.days > 0: + ago = "%d days" % (ago.days) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + ago = "%d hours" % (hours) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + ago = "%d minutes" % (minutes) + revinfo[rev] = (ago, author, quicklog) + +if depth != 0: + subdirs = [('..', '/'.join(sp[:-1]))] + subdirs + +tr = TableWriter(req) +tr.start() +tr.write('NameAgeAuthorLast log entry', is_header=True) +icon_uri = get_icon('', is_directory=True) +for name, subdir in subdirs: + tr.write('%s' % (icon_uri, + link("manifest", + [id, subdir], name))) +for name, file_id, file in files: + icon_uri = get_icon(file) + if icon_uri != None: + icon_code = '' % (icon_uri) + else: + icon_code = '' + rev = file_to_rev.get(file, None) + if not rev: + tr.write('%s %s' % (icon_code, link("file", [id, file], name))) + else: + age, author, quicklog = revinfo[rev] + author = hq(author or "").replace(' ', ' ') + quicklog = hq(quicklog or "") + tr.write('%s %s %s%s%s' % (icon_code, \ + link("file", [id, file], name), \ + link("revision", rev, (age or "").replace(' ', ' '), no_quote=True), + author, \ + quicklog)) +tr.stop() + +%> + + + +<% + +req.write(template.footer(info)) + +%> + ============================================================ # www/viewmtn/mimetypes/gnome-fs-directory.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-library.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-magicpoint.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-msword.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-ogg.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-par.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-pdf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-pgp-encrypted.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-pgp-keys.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-pgp.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-postscript.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-qif.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-effect.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-rhythmbox-playlist.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-rtf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-smil.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.lotus-1-2-3.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-excel.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-powerpoint.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.ms-word.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.calc.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.impress.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.stardivision.writer.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.calc.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.draw.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.impress.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-vnd.sun.xml.writer.template.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-wordperfect.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-abiword.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-applix-spreadsheet.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-applix-word.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-archive.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-arj.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-bittorrent.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-bla.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-blender.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-blf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-blv.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-bzip-compressed-tar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-bzip.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-cd-image.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-class-file.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-compress.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-compressed-tar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-core.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-cpio-compressed.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-cpio.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-dc-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-deb.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-desktop.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-dia-diagram.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-dv.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-dvi.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-e-theme.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-executable.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-extension-nfo.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-extension-par2.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-afm.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-bdf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-linux-psf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-pcf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-sunos-news.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-font-ttf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-gameboy-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-genesis-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-glade.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-gnucash.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-gnumeric.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-gtktalog.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-gzip.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-ipod-firmware.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-jar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-killustrator.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-kpresenter.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-kspread.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-kword.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-lha.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-lhz.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-mrproject.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-msx-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-n64-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-nes-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-object.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-perl.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-php.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-python-bytecode.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-python.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-qw.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-rar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-reject.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-rpm.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-ruby.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-sharedlib.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-shellscript.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-shockwave-flash.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-sms-rom.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-sql.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-stuffit.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-tar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-tex.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-trash.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-x-x509-ca-cert.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application-zip.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-application.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-ac3.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-basic.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-midi.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-aiff.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-it.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-midi.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-mod.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-mp3.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-s3m.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-stm.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-ulaw.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-voc.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-wav.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-xi.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio-x-xm.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-audio.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-bmp.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-gif.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-jpeg.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-png.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-svg+xml.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-svg.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-tiff.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-wmf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-3ds.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-applix-graphic.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-cmu-raster.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-lwo.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-lws.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image-x-xcf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-image.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-css.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-html.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-authors.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-c++src.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-c-header.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-c.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-chdr.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-copying.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-credits.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-csh.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-csharp.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-csrc.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-haskell.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-install.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-java.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-literate-haskell.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-lyx.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-makefile.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-objcsrc.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-patch.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-readme.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-scheme.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-sql.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-tex.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-troff-man.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-txt.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-vcalendar.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-vcard.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-x-zsh.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text-xml.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-text.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video-mpeg.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video-quicktime.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video-x-ms-asf.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video-x-ms-wmv.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video-x-msvideo.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-video.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-x-directory-nfs-server.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-x-directory-smb-server.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-x-directory-smb-share.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-x-directory-smb-workgroup.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-mime-x-font-afm.png is binary ============================================================ # www/viewmtn/mimetypes/gnome-package.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-database.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-drawing-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-drawing.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-formula.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-master-document.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-database.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-drawing.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-formula.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-master-document.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-presentation.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-spreadsheet.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-text-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-text.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-oasis-web-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-presentation-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-presentation.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-spreadsheet-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-spreadsheet.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-text-template.png is binary ============================================================ # www/viewmtn/mimetypes/openofficeorg-19-text.png is binary ============================================================ --- www/viewmtn/monotone.py f8211dca24b2b5e48c89d5e88d95388a683282b7 +++ www/viewmtn/monotone.py f8211dca24b2b5e48c89d5e88d95388a683282b7 @@ -0,0 +1,414 @@ + +import utility +import urllib +import signal +import string +import syslog +import pipes +import sets +import sha +import re +import os +from colorsys import hls_to_rgb + +# +# a python wrapper for the "monotone" command +# +# should really use the "automate" interface as much as possible +# + +id_re = re.compile(r'^[0-9a-f]+$') +dash_re = re.compile(r'^-+$') +cert_value_re = re.compile(r'^(\S*) *: (.*)$') +basic_io_re = re.compile(r'^ *(\S+) [\"\[](.*)[\"\]]$') +basic_io_hex_re = re.compile(r'^ *(\S+) (\[[0-9A-Fa-f]*\])$') +basic_io_string_re = re.compile(r'^ *(\S+) (\".*)$') +manifest_entry_re = re.compile(r'^(\S+) *(.*)$') +log_entry_re = re.compile(r'^(\S+): (.*)$') + +def colour_from_string(str): + def f(off): + return ord(hashval[off]) / 256.0 + hashval = sha.new(str).digest() + hue = f(5) + li = f(1) * 0.15 + 0.55 + sat = f(2) * 0.5 + .5 + return ''.join(map(lambda x: "%.2x" % int(x * 256), hls_to_rgb(hue, li, sat))) + +class Automation: + def __init__(self, base_command): + self.command = "%s automate stdio" % (base_command) + self.process = None + self.debug = False + def __del__(self): + self.stop() + self.process = None + def start(self): + self.process = popen2.Popen3(self.command) + set_nonblocking(self.process.fromchild) + if self.debug: syslog.syslog("AUTOMATE; %s starting %s" % (os.getpid(), self.process.pid)) + def stop(self): + if not self.process: return + if self.debug: syslog.syslog("AUTOMATE: %s stopping %s" % (os.getpid(), self.process.pid)) + try: + self.process.tochild.close() + self.process.fromchild.close() + if self.process.poll() == -1: + # the process is still running, so kill it. + os.kill(self.process.pid, signal.SIGKILL) + self.process.wait() + except: + if self.debug: syslog.syslog("AUTOMATE: %s failed_to_stop %s" % (os.getpid(), self.process.pid)) + pass + def run(self, command, args): + if self.process == None: self.start() + if self.debug: + syslog.syslog("AUTOMATE: %s run_via %s %s %s" % (os.getpid(), self.process.pid, str(command), str(args))) + enc = "l%d:%s" % (len(command), command) + enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_send %s %s %s" % (os.getpid(), self.process.pid, str(command), enc)) + # note that fromchild is nonblocking, but tochild is blocking + self.process.tochild.write(enc) + self.process.tochild.flush() + r = RecvPacket() + complete = False + result_string = "" + result_code = None + while not complete: + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_select %s" % (os.getpid(), self.process.pid)) + ro, rw, re = select.select([self.process.fromchild], [], [], None) + if not ro and not rw and not re: + break + if self.process.fromchild in ro: + recv = self.process.fromchild.read() + if recv == "": break + if self.debug: + syslog.syslog("AUTOMATE: %s run_via_recv %s %s %s" % (os.getpid(), self.process.pid, str(command), recv)) + + data_to_parse = recv + while not complete: + tv = r.process_data(data_to_parse) + if tv == None: + # this data did not result in a complete packet; we need more data from the client + break + else: + cmdnum, error, length, is_last, result = tv + if result_code == None: result_code = int(error) + result_string += result + if is_last: + complete = True + else: + # any left-over bytes must be parsed in case we have another complete packet + data_to_parse = r.buffer + r = RecvPacket() + return result_code, result_string + +class Monotone: + def __init__(self, mt, dbfile): + self.mt = mt + self.dbfile = dbfile + self.base_command = "%s --db=%s" % (self.mt, pipes.quote(self.dbfile)) + self.automate = Automation(self.base_command) + def branches(self): + result = utility.run_command(self.base_command + " ls branches") + if result['exitcode'] != 0: + raise Exception("Unable to list branches: %s" % (result['childerr'])) + else: + return filter(None, result['fromchild'].split('\n')) + def tags(self): + result = utility.run_command(self.base_command + " ls tags") + if result['exitcode'] != 0: + raise Exception("Unable to list tags: %s" % (result['childerr'])) + else: + return map(lambda x: x.split(' ', 2), filter(None, result['fromchild'].split('\n'))) + def heads(self, branch): + error, result = self.automate.run('heads', [branch]) + if error != 0: + raise Exception("Unable to get list of heads for %s: %s" % (branch, result)) + else: + return filter(None, result.split('\n')) + def basic_io_parser(self, data): + """returns a list of lists of (key, value) tuples. hashes are returned with []s around + them; strings are returned raw.""" + + ### fixme, really needs a rewrite + + def unescape_string_value(str): + rv = "" + is_terminated = False + in_escape = False + if str[0] != '"': + raise Exception("basic_io parse error; not a string: ;%s; (%d)" % (str, len(str))) + for idx, c in enumerate(str[1:]): + if in_escape: + if c != '\\' and c != '\"': + raise Exception(r'basic_io parse error; expected \" or \\') + rv += c + in_escape = False + else: + if c == '\\': + in_escape = True + elif c == '"': + is_terminated = True + return is_terminated, str[idx+2:], rv + else: + rv += c + return is_terminated, '', rv + + # 14:46 < tbrownaw> list>>, with the outer list divided according to + # what item starts a stanza? + + rv = {} + + stanza = [] + ongoing_string = None + + for line in data.split('\n'): + if ongoing_string != None: + ongoing_string += '\n' + line + is_terminated, value_buffer, e_value = unescape_string_value(ongoing_string) + if is_terminated: + stanza.append(e_value) + ongoing_string = None + continue + + if line == '' and len(stanza) != 0: + rv.setdefault(stanza[0], []).append(stanza) + stanza = [] + continue + + m = basic_io_hex_re.match(line) + if m: + key, value = m.groups() + stanza += [key, value[1:-1]] + continue + + m = basic_io_string_re.match(line) + if m: + key, value = m.groups() + # consume values from the buffer until none remain, + # or we're stuck waiting for a multi-line value to + # be concluded + + stanza.append(key) + value_buffer = value + while value_buffer: + is_terminated, value_buffer, e_value = unescape_string_value(value_buffer) + if is_terminated == True: + stanza.append(e_value) + else: + break + value_buffer = value_buffer.strip() + + if not is_terminated: + ongoing_string = value + continue + return rv + def certs(self, id): + "returns a list of certs, each a list of tuples (attribute,value)" + error, data = self.automate.run('certs', [id]) + if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) + parsed = self.basic_io_parser(data) + if len(parsed.keys()) != 1 or parsed.keys()[0] != 'key': + raise Exception("basic_io format for certs has changed: unknown cert types '%s' found" % (str(parsed.keys()))) + certs = parsed['key'] + rv = [] + for cert in certs: + rv.append([(cert[t], cert[t+1]) for t in range(0, len(cert), 2)]) + return rv + def ancestors(self, ids): + if type(ids) == type(""): ids = [ids] + error, result = self.automate.run('ancestors', ids) + if error != 0: + raise Exception("Unable to get ancestors of %s: %s" % (str(ids), + result)) + else: + return filter(None, result.split('\n')) + def toposort(self, ids): + if type(ids) == type(""): ids = [ids] + error, result = self.automate.run('toposort', ids) + if error != 0: + raise Exception("Unable to toposort: %s" % (result)) + else: + return filter(None, result.split('\n')) + def revision(self, id): + error, result = self.automate.run('get_revision', [id]) + if error != 0: + raise Exception("Unable to get revision %s: %s" % (id, result)) + return self.basic_io_parser(result) + def parents(self, id): + error, result = self.automate.run('parents', [id]) + if error != 0: + raise Exception("Unable to get parents of %s: %s" % (id, result)) + else: + return filter(None, result.split('\n')) + def children(self, id): + error, result = self.automate.run('children', [id]) + if error != 0: + raise Exception("Unable to get children of %s: %s" % (id, result)) + else: + return filter(None, result.split('\n')) + def manifest_of(self, id): + error, result = self.automate.run('get_manifest_of', [id]) + if error != 0: + raise Exception("Unable to get manifest %s: %s" % (id, result)) + + stanzas = self.basic_io_parser(result) + return stanzas + def file(self, id): + error, result = self.automate.run('get_file', [id]) + if error != 0: + raise Exception("Unable to get file %s: %s" % (id, result)) + else: + return result + + def annotate(self, id, file): + result = utility.run_command(self.base_command + " annotate --revision=%s %s" % (pipes.quote(id), + pipes.quote(file))) + if result['exitcode'] != 0: + raise Exception("Unable to annotate file: %s using command '%s'" % (result['childerr'], + result['run_command'])) + else: + return result['fromchild'] + + def diff(self, rev_from, rev_to, files=None): + command = self.base_command + " diff -r %s -r %s" % (pipes.quote(rev_from), pipes.quote(rev_to)) + if files != None: command += ' ' + ' '.join(map(pipes.quote, files)) + syslog.syslog(command) + result = utility.run_command(command) + if result['exitcode'] != 0: + raise Exception("Unable to calculate diff: %s" % (result['childerr'])) + else: + return result['fromchild'] + def log(self, ids, limit=0): + rv = [] + entry = None + command = self.base_command + " --merges log " + ' '.join(map(lambda x: '-r ' + pipes.quote(x), ids)) + if limit > 0: command += " --last=%d" % (limit) + iterator = utility.iter_command(command) + for line in iterator: + if dash_re.match(line): + entry = {} + if entry == None: continue + if not line: + rv.append(entry) + entry = None + continue + m = log_entry_re.match(line) + if m: + attr, value = m.groups() + if not entry.has_key(attr): entry[attr] = [] + entry[attr].append(value) + # clean up; otherwise we'll leak filehandlers + map(None, iterator) + if entry: rv.append(entry) + return rv + def ancestry_graph(self, graphopts, id, limit=0): + def dot_escape(s): + # kinda paranoid, should probably revise later + permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/' + return ''.join(filter(lambda x: x in permitted, s)) + graphdir = graphopts['directory'] + graphuri = graphopts['uri'] + graph_id = "%s.%d" % (id, limit) + rv = { + 'dot_file' : os.path.join(graphdir, graph_id + ".dot"), + 'image_file' : os.path.join(graphdir, graph_id + ".png"), + 'imagemap_file' : os.path.join(graphdir, graph_id + ".html"), + 'dot_uri' : "%s/%s.dot" % (graphuri, urllib.quote(graph_id)), + 'image_uri' : "%s/%s.png" % (graphuri, urllib.quote(graph_id)), + 'imagemap_uri' : "%s/%s.html" % (graphuri, urllib.quote(graph_id)), + } + need_access = ['dot_file', 'image_file', 'imagemap_file'] + missing = filter(lambda x: x != True, map(lambda x: os.access(rv[x], os.R_OK), need_access)) + if len(missing) == 0: + rv['cached'] = True + return rv + contents = 'digraph ancestry {\nratio=compress\nnodesep=0.1\nranksep=0.2\nedge [dir=back];\n' + revisions = {} + for attrs in self.log([id], limit): + if not attrs.has_key("Revision") or not attrs.has_key("Ancestor"): + continue + revision = attrs['Revision'][0] + revisions[revision] = attrs + for ancestor in attrs['Ancestor']: + if len(ancestor) == 0: continue + if not revisions.has_key(ancestor): revisions[ancestor] = None + contents += '"%s"->"%s"[href="getdiff.py?id1=%s&id2=%s"]\n' % (urllib.quote(revision), + urllib.quote(ancestor), + urllib.quote(ancestor), + urllib.quote(revision)) + for revision in revisions.keys(): + label = "%s..." % (revision[0:8]) + attrs = revisions[revision] + if attrs == None: + # fill in the gaps; would be nice to clean this up. + # shouldn't take long, anyway. + attrs = self.log([revision], 1)[0] + if attrs.has_key('Date'): + d = dot_escape(attrs['Date'][0]) + d = d[0:d.find("T")] + label += " on %s" % d + if attrs.has_key('Author'): + label += "\\n%s" % (dot_escape(attrs['Author'][0])) + override_fillcolor = colour_from_string(attrs['Author'][0]) + else: override_fillcolor = None + opts = 'label="%s"' % label #revision[0:8] + for opt in graphopts['nodeopts']: + if opt == 'fillcolor' and override_fillcolor != None: continue + opts += ',%s="%s"' % (opt, graphopts['nodeopts'][opt]) + if revision == id: opts += ",color=blue" + opts += ',href="revision.psp?id=%s"' % urllib.quote(revision) + if override_fillcolor != None: opts += ',fillcolor="#%s"' % (override_fillcolor) + contents += '"%s" [%s]\n' % (revision, opts) + contents += "}\n" + open(rv['dot_file'], 'w').write(contents) + os.system("%s -Tcmapx -o %s -Tpng -o %s %s" % (graphopts['dot'], + pipes.quote(rv['imagemap_file']), + rv['image_file'], + rv['dot_file'])) + rv['cached'] = False + return rv + +def is_valid_id(s): + return len(s) == 40 and id_re.match(s) != None + +import popen2 +import select +from utility import set_nonblocking + +packet_header_re = re.compile(r'(\d+):(\d+):([lm]):(\d+):') + +class RecvPacket: + "A packet received from monotone automate stdio" + def __init__(self, init_buffer=""): + self.buffer = init_buffer + self.cmdnum = None + self.error = None + self.length = None + self.is_last = None + self.result = "" + def process_data(self, data): + self.buffer += data + if self.length == None: + # we have not yet read a complete header line for this packet + m = packet_header_re.match(self.buffer) + if m: + self.cmdnum, self.error, pstate, self.length = m.groups() + self.length = int(self.length) + self.is_last = pstate == "l" + self.buffer = self.buffer[m.end(m.lastindex)+1:] + if self.length != None and len(self.result) < self.length: + needed = self.length - len(self.result) + if len(self.buffer) >= needed: + available = needed + else: + available = len(self.buffer) + self.result += self.buffer[:available] + self.buffer = self.buffer[available:] + if len(self.result) == self.length: + return (self.cmdnum, self.error, self.length, self.is_last, self.result) + else: return None + ============================================================ --- www/viewmtn/mtn.py 9aacc29cd3af6277ef411f7e582a450f58c727ee +++ www/viewmtn/mtn.py 9aacc29cd3af6277ef411f7e582a450f58c727ee @@ -0,0 +1,264 @@ + +import os +import re +import pipes +import select +import threading + +# regular expressions that are of general use when +# validating monotone output +def group_compile(r): + return re.compile('('+r+')') + +revision_re = r'[A-Fa-f0-9]{40}' +revision_re_c = group_compile(revision_re) +name_re = r'^[\S]+' +name_re_c = group_compile(name_re) + +class MonotoneException(Exception): + pass + +class Revision(str): + def __init__(self, v): + str.__init__(v) + if not revision_re_c.match(self): + raise MonotoneException("Not a valid revision ID") + def abbrev(self): + return '[' + self[:8] + '..]' + +class Runner: + def __init__(self, monotone, database): + self.base_command = [monotone, "--db=%s" % pipes.quote(database)] + +packet_header_re = re.compile(r'^(\d+):(\d+):([lm]):(\d+):') + +class Automate(Runner): + """Runs commands via a particular monotone process. This + process is started the first time run() is called, and + stopped when this class instance is deleted. + + If an error occurs, the monotone process may need to be + stopped and a new one created. + """ + def __init__(self, *args, **kwargs): + Runner.__init__(*[self] + list(args), **kwargs) + self.lock = threading.Lock() + self.process = None + + def __process_required(self): + if self.process != None: + return + from utility import set_nonblocking + to_run = self.base_command + ['automate', 'stdio'] + self.child_stdin, self.child_stdout, self.child_stderr = os.popen3(to_run) + map (set_nonblocking, [ self.child_stdin, + self.child_stdout, + self.child_stderr ]) + + def run(self, command, args): + if not self.lock.acquire(False): + raise MonotoneException("Automate process can't be called: it is already locked.") + self.__process_required() + + enc = "l%d:%s" % (len(command), command) + enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' + self.child_stdin.write(enc) + self.child_stdin.flush() + + import sys + def read_result_packets(): + buffer = "" + while True: + r_stdin, r_stdout, r_stderr = select.select([self.child_stdout], [], [], None) + if not r_stdin and not r_stdout and not r_stderr: + break + + if self.child_stdout in r_stdin: + data = self.child_stdout.read() + if data == "": + break + buffer += data + + # loop, trying to get complete packets out of our buffer + complete, in_packet = False, False + while not complete and buffer != '': + if not in_packet: + m = packet_header_re.match(buffer) + if not m: + break + in_packet = True + cmdnum, errnum, pstate, length = m.groups() + errnum = int(cmdnum) + length = int(length) + header_length = m.end(m.lastindex) + 1 # the '1' is the colon + + if len(buffer) < length + header_length: + # not enough data read from client yet; go round + break + else: + result = buffer[header_length:header_length+length] + buffer = buffer[header_length+length:] + complete = pstate == 'l' + in_packet = False + yield errnum, complete, result + + if complete: + break + + # get our response, and yield() it back one line at a time + code_max = -1 + for code, is_last, data in read_result_packets(): + if code and code > code_max: + code_max = code + for line in data.split('\n'): + yield line + '\n' + if code_max > 0: + raise MonotoneException("error code %d in automate packet." % code_max) + self.lock.release() + +class Standalone(Runner): + """Runs commands by running monotone. One monotone process + per command""" + + def run(self, command, args): + # as we pass popen3 as sequence, it executes monotone with these + # arguments - and does not pass them through the shell according + # to help(os.popen3) + to_run = self.base_command + [command] + args + child_stdin, child_stdout, child_stderr = os.popen3(to_run) + for line in child_stdout: + yield line + stderr_data = child_stderr.read() + if len(stderr_data) > 0: + raise MonotoneException("data on stderr for command '%s': %s" % (command, + stderr_data)) +class MtnObject: + def __init__(self, obj_type): + self.obj_type = obj_type + +class Tag(MtnObject): + def __init__(self, name, revision, author): + MtnObject.__init__(self, "tag") + self.name, self.revision, self.author = name, Revision(revision), author + +class Branch(MtnObject): + def __init__(self, name): + MtnObject.__init__(self, "branch") + self.name = name + +basic_io_name_tok = re.compile(r'^(\S+)') + +def basic_io_from_stream(gen): + # all of these x_consume functions return parsed string + # token to add to stanza, name of next consume function to call + # new value of line (eg. with consumed tokens removed) + + def hex_consume(line): + m = revision_re_c.match(line[1:]) + if line[0] != '[' or not m: + raise MonotoneException("This is not a hex token: %s" % line) + end_of_match = m.end(m.lastindex) + if line[end_of_match+1] != ']': + raise MonotoneException("Hex token ends in character other than ']': %s" % line) + return Revision(m.groups()[0]), choose_consume, line[end_of_match+2:] + + def name_consume(line): + m = name_re_c.match(line) + if not m: + raise MonotoneException("Not a name: %s" % line) + end_of_match = m.end(m.lastindex) + return m.groups()[0], choose_consume, line[end_of_match:] + + def choose_consume(line): + line = line.lstrip() + if line == '': + consumer = choose_consume + elif line[0] == '[': + consumer = hex_consume + elif line[0] == '"': + consumer = string_consume + else: + consumer = name_consume + return None, consumer, line + + class StringState: + def __init__(self): + self.in_escape = False + self.has_started = False + self.has_ended = False + self.value = '' + + def string_consume(line, state=None): + if not state: + state = StringState() + + if not state.has_started: + if line[0] != '"': + raise MonotoneException("Not a string: %s" % line) + line = line[1:] + state.has_started = True + + for idx, c in enumerate(line): + if state.in_escape: + if c != '\\' or c != '\"': + raise MonotoneException("Invalid escape code: %s\n" % line) + state.value += c + state.in_escape = False + else: + if c == '\\': + state.in_escape = True + elif c == '"': + state.has_ended = True + break + else: + state.value += c + + if state.has_ended: + return state.value, choose_consume, line[idx+1:] + else: + return (None, + lambda s: string_consume(s, state), + line[idx+1:]) + + consumer = choose_consume + current_stanza = [] + for line in gen: +# print "read line:", [line] + if (line == '' or line == '\n') and current_stanza: + yield current_stanza + current_stanza = [] + continue + + while line != '' and line != '\n': + new_token, consumer, line = consumer(line) + if new_token != None: + current_stanza.append(new_token) + +class Operations: + def __init__(self, runner_args): + self.standalone = apply(Standalone, runner_args) + self.automate = apply(Automate, runner_args) + + def tags(self): + for line in (t.strip() for t in self.standalone.run('ls', ['tags'])): + if not line: + continue + yield apply(Tag, line.split(' ', 2)) + + def branches(self): + for line in (t.strip() for t in self.standalone.run('ls', ['branches'])): + if not line: + continue + yield apply(Branch, (line,)) + + def graph(self): + for line in self.automate.run('graph', []): + yield line + + def get_revision(self, revision): + for stanza in basic_io_from_stream(self.automate.run('get_revision', [revision])): + yield stanza + + def certs(self, revision): + for stanza in basic_io_from_stream(self.automate.run('certs', [revision])): + yield stanza ============================================================ --- www/viewmtn/revision.psp b5b23a31a00ed137eaa2d46681966ad758efc90b +++ www/viewmtn/revision.psp b5b23a31a00ed137eaa2d46681966ad758efc90b @@ -0,0 +1,172 @@ +<% + +import config +import monotone +import datetime +import common +import urllib +from common import parse_timecert, ago_string, get_branch_links + +# +# revision.psp +# display all information we can about a particular revision +# + +def prettify(s): + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + +psp.set_error_page("error.psp") + +if not form.has_key('id'): + raise Exception("No revision ID specified.") +id = form['id'] +if not monotone.is_valid_id(id): + raise Exception("Specified revision ID is not valid.") + +# read certificates +certs = mt.certs(id) +branches, cert_table = [], "" +for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + event = parse_timecert(value) + now = datetime.datetime.utcnow() + ago = ago_string(event, now) + value += '\n' + ago + ' ago' + if name == "branch": + branches.append(value) + value = link("branch", value) + else: + value = '
'.join(map(hq, value.split('\n'))) + cert_table += '%s%s' % (prettify(name), value) + +certs = mt.certs(id) +branches = common.extract_cert_from_certs(certs, "branch", as_list=True) +branch_links = get_branch_links(mt, branches) + +info = { + 'title' : "Revision '%s' for %s" % (link("revision", id), + branch_links), + 'branch_rss' : branches + } +req.write(template.header(info)) + +# generate the revision graph +ancestry_limit = 10 +ancestry_maximum = 100 +try: + if form.has_key('ancestry_limit'): + ancestry_limit = int(form['ancestry_limit']) +except: pass +if ancestry_limit == 0 or ancestry_limit > ancestry_maximum: + ancestry_limit = ancestry_maximum + +ancestry_graph = mt.ancestry_graph(config.graphopts, id, ancestry_limit) +if os.access(ancestry_graph['imagemap_file'], os.R_OK): + req.write(open(ancestry_graph['imagemap_file']).read()) +else: + req.write('
Unable to access ancestry graph; perhaps graphviz could not be found?
') + +revision = mt.revision(id) + +%> + +
+ +<% +if not revision.has_key('new_manifest'): +%> +

Note: no manifest is associated with this revision and hence it contains no files.

+<% +else: + req.write('''

You might wish to %s.

''' % (link("manifest", id, "browse this revision's contents"))) + +%> + +

Certificates

+ + +<%= cert_table %> +
+ +

Revision details

+ + +<% +old_revisions = [] +for type in revision.keys(): + value = "" + for stanza in revision[type]: + if type == "patch": + fname, from_id, to_id = stanza[1], stanza[3], stanza[5] + # if from_id == None, then this is a new file; we're showing + # that information under "Add file" anyway. + if from_id: + diff_links = ','.join([link("diff", [t, id, fname]) for t in old_revisions]) + value += 'Patch file %s (%s)
' % \ + (link("file", [id, fname], fname), + diff_links) + elif type == "old_revision": + old_revision = stanza[1] + old_revisions.append(old_revision) + value += 'Old revision is: %s (%s)
' % \ + (link("revision", old_revision), + link("diff", [old_revision, id])) + elif type == "new_manifest": + # swallow this, it's not useful + pass + # new_manifest = stanza[1] + # value += 'New manifest is: %s
' % (link("manifest", id)) + elif type == "add_file": + fname = stanza[1] + value += "Add file: %s
" % (link("file", [id, fname], fname)) + elif type == "delete_file": + fname = stanza[1] + value += "Delete file: %s
" % (link("file", [id, fname], fname)) + elif type == "delete_dir": + fname = stanza[1] + value += "Delete directory: %s
" % (link("file", [id, fname], fname)) + elif type == "rename_file": + old_name, new_name = stanza[1], stanza[3] + value += "Rename file %s as %s
" % (hq(old_name), link("file", [id, new_name], new_name)) + elif type == "rename_dir": + old_name, new_name = stanza[1], stanza[3] + value += "Rename directory %s as %s
" % (hq(old_name), hq(new_name)) + if value: + req.write('' % (prettify(type), value)) + +%> +
%s%s
+
+ +
+ +Ancestry of <%= hq(id) %>
+<% +increment = 5 +zoom_out = ancestry_limit + increment +zoom_in = ancestry_limit - increment +if zoom_in <= 0: zoom_in = 1 +%> + + +ancestry: last <%=ancestry_limit%> log entries ( +">fewer, +">more, or +">all) + + +
+ + + +<% + +req.write(template.footer(info)) + +%> + ============================================================ # www/viewmtn/rss_feed.gif is binary ============================================================ --- www/viewmtn/tags.psp 7b81023a3fbf60ce0ed258d7c9b9086b233e4e8d +++ www/viewmtn/tags.psp 7b81023a3fbf60ce0ed258d7c9b9086b233e4e8d @@ -0,0 +1,59 @@ +<% + +from common import parse_timecert, ago_string, determine_date +import datetime + +psp.set_error_page("error.psp") +info = { 'title' : "Tags" } +req.write(template.header(info)) + +tags = mt.tags() + +%> + +<% +if len(tags) == 0: + # no tags +%> +

+There are no tags in the provided monotone database. +

+<% +else: + # okay, so display the tags +%> +

+A tag marks a particular revision that is in some way significant. A +common use of tags is to mark public release of a piece of software. +To view a particular tag, select it from the list below. +

+ + + +<% + now = datetime.datetime.utcnow() + tags.sort(lambda x,y: cmp(x[0], y[0])) + for tag in tags: + tag_name, tag_id, signed_by = tag + certs = mt.certs(tag_id) + ago = determine_date(certs) + if ago != None: + ago = ago_string(ago, now) + " ago" + else: + ago = "" + req.write('' % \ + (link("revision", tag_id, tag_name), + hq(signed_by), + hq(ago or ""))) + +%> +
TagSigned byWhen
%s%s%s
+ +<% + +%> + +<% +req.write(template.footer(info)) +%> + ============================================================ --- www/viewmtn/tarofbranch.psp 08acdaaf0dbe9111f0053d02b91b4e814c62c798 +++ www/viewmtn/tarofbranch.psp 08acdaaf0dbe9111f0053d02b91b4e814c62c798 @@ -0,0 +1,48 @@ +<% + +import urllib +import urlparse + +psp.set_error_page("error.psp") + +if not form.has_key('branch'): + raise Exception("No branch specified.") +branch = form['branch'] + +# tarofbranch.psp +# +# allow a tar file to be retrieved of the latest +# HEAD of a branch. If there are multiple heads, +# present a choice. + +heads = mt.heads(branch) +if len(heads) == 0: + raise Exception("No head ID can be determined for this branch.") +elif len(heads) == 1: + revision = mt.revision(heads[0]) + manifest_id = revision['new_manifest'][0][1] + psp.redirect(urlparse.urljoin(req.uri, "gettar.py?id=%s" % (urllib.quote(manifest_id)))) +else: + info = {'title' : "Latest tar file of branch %s" % (hq(branch))} + req.write(template.header(info)) +%> +

+The branch you have selected has multiple head revisions The head +revisions are listed below, along with links to download a tar file of +each revision. +

+ + + +<% + for id in heads: + revision = mt.revision(id) + manifest_id = revision['new_manifest'][0][1] + req.write('' % (link("revision", id), link("tar", manifest_id, "download tar archive"))) + +%> +
Head revisionTar file
%s%s
+ +<% +req.write(template.footer(info)) +%> ============================================================ --- www/viewmtn/templates/about.html 6b1c55564ae50e2de59d5eda418bfad86f0b596d +++ www/viewmtn/templates/about.html 6b1c55564ae50e2de59d5eda418bfad86f0b596d @@ -0,0 +1,67 @@ +#extends base + +#def body + +

Authors and Contributors

+ +
    +
  • Grahame Bowland - address@hidden
  • +
  • Matt Johnston - address@hidden
  • +
  • Nathaniel Smith - address@hidden
  • +
  • Bruce Stephens - address@hidden
  • +
  • Lapo Luchini - address@hidden
  • +
  • David Reiss - address@hidden
  • +
+ +

Licensing

+ +

+Copyright (C) 2005 Grahame Bowland +

+ +

+This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +

+ +

+This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +

+ +

+You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +

+ +

Dependencies

+ +

+ViewMTN is written in Python and +runs under mod_python. +

+ +

+Code highlighting via +GNU Enscript. +

+ +

+Graphing via GraphViz. +

+ +

+Graph colour generation algorithm from monotone-viz with modifications from Matt Johnston. +

+ +

+AJAX funtionality uses the MochiKit Javascript library. +

+ + +#end def ============================================================ --- www/viewmtn/templates/base.html 828ac669dcf846b15ebedc558c7da907079dd685 +++ www/viewmtn/templates/base.html 828ac669dcf846b15ebedc558c7da907079dd685 @@ -0,0 +1,38 @@ + + + +ViewMTN: $(page_title) + + + +#block extraheaders +#end block + + + + + + +#block body +#end block + + + + + + + ============================================================ --- www/viewmtn/templates/index.html 08145fdb8457c0f05ead0cdec4717e0f7f0de864 +++ www/viewmtn/templates/index.html 08145fdb8457c0f05ead0cdec4717e0f7f0de864 @@ -0,0 +1,29 @@ +#extends base + +#def body +

+Welcome to this ViewMTN installation. +The list below shows all branches served within this Monotone +database. +

+ +

+Select one of the branches and you will be shown a list of recent changes that have occurred within it. +If you are looking for a particular revision (for example, a release) the list of tags +might be useful. +

+ + + +#for branch in $branches + + + +#end for +
Branch
+ #filter Filter + $link($branch).html() + #filter WebSafe +
+ +#end def ============================================================ --- www/viewmtn/tests/automate.py f04942987a6f0f4c71b14d9be69256d5dffa2bfa +++ www/viewmtn/tests/automate.py f04942987a6f0f4c71b14d9be69256d5dffa2bfa @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import sys +sys.path.append("..") +import time +import monotone +import config +import traceback + +def test_branches(mt): + """Test the mt.branches method""" + result = mt.branches() + if len(result) == 0: + raise Exception("No branches found") + +def test_tags(mt): + """Test the mt.tags method""" + result = mt.tags() + if len(result) == 0: + raise Exception("No tags found") + +def test_heads(mt): + """Test the mt.heads method""" + result = mt.heads(config.test_branch) + if len(result) == 0: + raise Exception("No heads found") + +def test_certs(mt): + """Test the mt.certs method""" + result = mt.certs(config.test_revision) + print result + if len(result) == 0: + raise Exception("No certs") + +def test_ancestors(mt): + """Test the mt.ancestors method""" + result = mt.ancestors(config.test_revision) + if len(result) == 0: + raise Exception("No certs") + +def test_toposort(mt): + """Test the mt.toposort method""" + tosort = mt.heads(config.test_branch) + result = mt.toposort(tosort) + if len(result) == 0: + raise Exception("No result to toposort") + +def test_revision(mt): + """Test the mt.revision method""" + rev = mt.revision(config.test_revision) + if rev == None: + raise Exception("Unable to retrieve revision") + +def test_manifest(mt): + """Test the mt.manifest method""" + rev = mt.revision(config.test_revision) + print rev + return + if not rev.has_key('new_manifest'): + raise Exception("Couldn't find manifest.") + print rev + return + manifest = rev['new_manifest'][0][1] + print manifest + +def test_file(mt): + pass + +def test_crazy_toposort(mt): + """Test huge (silly) arguments""" + #ancestors = mt.heads(config.test_branch) + ancestors = mt.ancestors(mt.heads(config.test_branch)) + while len(ancestors) < 1000: + result = mt.toposort(ancestors) + open('command.txt', 'w').write(config.monotone + 'automate ancestors ' + ' '.join(ancestors)) + ancestors *= 2 + +def test_error_string(mt): + """Test what happens when we do something wrong resulting in an error""" + try: + heads = mt.certs('0000000000000000000000000000000000000000') + raise Exception("No error occurred.") + except Exception: pass + +def test_invalid_data(mt): + """Test what happens when we do something which is invalid""" + try: + mt.certs('0000') # too short + raise Exception("No error occurred.") + except Exception: pass + +tests = [ + +# disabled as they don't use automate +# test_branches, +# test_tags, + + test_ancestors, + test_toposort, + test_revision, + test_manifest, + test_file, + + test_heads, + test_certs, + + test_crazy_toposort, + test_error_string, + test_invalid_data, +] + +def test(): + for idx, test in enumerate(tests): + def log(s): + print "#%-3d %s" % (idx, s) + log(test.__doc__) + try: + test(mt) + except KeyboardInterrupt: + break + except: + log ("Test FAILED; traceback follows") + traceback.print_exc() + print + +if __name__ == "__main__": + mt = monotone.Monotone(config.monotone, config.dbfile) + if len(sys.argv) == 2: + attempts = int(sys.argv[1]) + else: + attempts = 1 + + while attempts > 0: + test() + time.sleep(10) + attempts -= 1 + mt.automate.stop() ============================================================ --- www/viewmtn/utility.py 78cd2c53af05e63bb76c097dc832b6f75394e40d +++ www/viewmtn/utility.py 78cd2c53af05e63bb76c097dc832b6f75394e40d @@ -0,0 +1,100 @@ + +import popen2 +import select +import fcntl +import os + +def set_nonblocking(fd): + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) + +def run_command(command, timeout=None, to_child=None): + "returns a tuple of (was_timeout, exit_code, data_read)" + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + if to_child != None: + p.tochild.write(to_child) + p.tochild.close() + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + # if there wasn't a timeout, the program should have exited; in which case we should wait() for it + # otherwise, it might be hung, so the parent should wait for it. + # (wrap in a try: except: just in case some other thread happens to wait() and grab ours; god wrapping + # python around UNIX is horrible sometimes) + exitcode = None + try: + if not was_timeout: exitcode = p.wait() >> 8 + except: pass + return { 'run_command' : command, + 'timeout' : was_timeout, + 'exitcode' : exitcode, + 'fromchild' : fromchild_read, + 'childerr' : childerr_read } + +def iter_command(command, timeout=None): + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + while 1: + nl = fromchild_read.find('\n') + if nl == -1: break + yield fromchild_read[:nl] + fromchild_read = fromchild_read[nl+1:] + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + p.tochild.close() + # yield anything left over + to_yield = fromchild_read.split('\n') + while len(to_yield): yield to_yield.pop() + # call wait() + try: + if not was_timeout: p.wait() + except: pass + if len(childerr_read): raise Exception("data on stderr (command is %s)" % command, childerr_read) + if was_timeout: raise Exception("command timeout") + ============================================================ --- www/viewmtn/version.py a071b2192e2092ec222ab0d59ef95efd1c9c81e6 +++ www/viewmtn/version.py a071b2192e2092ec222ab0d59ef95efd1c9c81e6 @@ -0,0 +1,3 @@ + +# the latest release; make sure to update this (note for Grahame) +release = "0.05" ============================================================ --- www/viewmtn/viewmtn.css 6efcadac0d56fb3d77a22786f477ba621f4af33d +++ www/viewmtn/viewmtn.css 6efcadac0d56fb3d77a22786f477ba621f4af33d @@ -0,0 +1,93 @@ + +BODY { + background-color: white; + font-family: "Trebuchet MS", Verdana, Sans-Serif; + font-size : 100%; +} + +DIV#menuBar { + font-family: "Trebuchet MS", Verdana, Sans-Serif; + font-size: 80%; + padding-top: 1em; + text-align: center; +} + +A { + text-decoration: none; + background-color: transparent; +} + +A:hover { + background-color: #DDF; + text-decoration: none; +} + +DIV#footer { + clear: both; + border-top-width: 1px; + border-top-style: solid; + border-top-color: black; + margin-top: 2em; + text-align: right; +} + +TABLE.pretty { + border-width: 1px; + border-style: solid; + border-color: black; +} + +TABLE.pretty TD { + padding-left: 0.5em; + padding-right: 0.5em; +} + +TABLE.pretty TH { + padding-left: 0.5em; + padding-right: 0.5em; +} + +TR.odd { + background-color: #eeeeee; +} + +TR.even { + background-color: #ffffff; +} + +TD.branchInfoHeader { + border-width: 3px; + border-style: solid; + border-color: blue; + text-align: center; + +} + +TABLE.pretty TH { + color: blue; + background-color: #dfe7f3; +} + +H1#pageTitle { + font-size: 120%; + font-family: sans-serif; +} + +H2 { + font-size: 120%; + font-family: sans-serif; +} + +.invisible { + display: none; +} + +DIV#popupBox { + position: absolute; + border-width: 1px; + border-style: solid; + border-color: black; + background-color: #d0d0f0; + padding: 2px; + z-index: 10; +} ============================================================ --- www/viewmtn/viewmtn.js b36f4e737e9b054f563c320becbc74d7a4c1df48 +++ www/viewmtn/viewmtn.js b36f4e737e9b054f563c320becbc74d7a4c1df48 @@ -0,0 +1,105 @@ + +var theBox; +var callbacksInstalled = false; +var pendingDeferred = null; + +function installCallbacks() +{ + if (callbacksInstalled != false) { + return; + } + callbacksInstalled = true; + + cbinst = function (e) { + updateNodeAttributes(e, { "onmouseover" : partial(mouseOverHandler, e), + 'onmouseout' : partial(mouseOutHandler, e) } ); + } + + var elems = getElementsByTagAndClassName(null, "branchLink"); + map(cbinst, elems); + + var elems = getElementsByTagAndClassName(null, "revisionLink"); + map(cbinst, elems); + + var elems = getElementsByTagAndClassName(null, "manifestLink"); + map(cbinst, elems); + + theBox = $("popupBox"); +} + +function updatePopup(boundTo, className) +{ + jsonData = boundTo.jsonData; + + var pos = elementPosition(boundTo); + var newBox; + + info = null; + if (jsonData.type == "branch") { + info = "branch last updated " + jsonData.ago + " by " + jsonData.author; + } else if (jsonData.type == "revision") { + info = jsonData.ago + " ago by " + jsonData.author; + } else if (jsonData.type == "manifest") { + info = "manifest contains " + jsonData.file_count + " files in " + jsonData.directory_count + " directories."; + } else { + info = "unknown type: " + jsonData.type; + } + + newBox = DIV({ 'id' : 'popupBox'}, + info); + + if (boundTo.offsetHeight) { + offset_height = boundTo.offsetHeight; + } else { + offset_height = 24; // yick + } + + newY = pos.y + offset_height + 20; + newX = pos.x + 20; + + newBox.style.top = newY + 'px'; + newBox.style.left = newX + 'px'; + swapDOM(theBox, newBox); + theBox = newBox; +} + +function jsonLoadComplete(boundTo, className, jsonData) +{ + boundTo.jsonData = jsonData; + updatePopup(boundTo, className); + pendingDeferred = null; +} + +function mouseOverHandler(boundTo, evt) +{ + var className = getNodeAttribute(boundTo, "class"); + + if (boundTo.jsonData) { + return updatePopup(boundTo, className); + } + + links = getElementsByTagAndClassName('a', null, boundTo); + + if (links.length > 0) { + linkHref = links[0].href; + } else { + return; + } + + var uri = "getjson.py?className=" + encodeURIComponent(className) + "&linkUri=" + encodeURIComponent(linkHref); + var d = loadJSONDoc(uri); + + d.addCallback(jsonLoadComplete, boundTo, className); + pendingDeferred = d; +} + +function mouseOutHandler(boundTo, evt) +{ + if (pendingDeferred != null) { + pendingDeferred.cancel(); + pendingDeferred = null; + } + var newBox = DIV({'id' : 'popupBox', 'class' : 'invisible'}); + swapDOM(theBox, newBox); + theBox = newBox; +} ============================================================ --- www/viewmtn/viewmtn.py 574970773ca270f6282d13161905dae81274170b +++ www/viewmtn/viewmtn.py 574970773ca270f6282d13161905dae81274170b @@ -0,0 +1,176 @@ +#!/usr/bin/env python + +import cgi +import mtn +import web +import config +import urlparse +hq = cgi.escape + +# /about.psp -> /about + +# /branch.psp -> /branch/{branch}/ +# /fileinbranch.psp -> /branch/{branch}/file/path (redir) +# /headofbranch.psp -> /branch/{branch}/head +# /tarofbranch.psp -> /branch/{branch}/tar + +# /revision.psp -> /revision/{id} +# /diff.psp -> /revision/{id}/diff/{id}[/{fname}] +# /file.psp -> /revision/{id}/file/{path} +# /manifest.psp -> /revision/{id}/browse/{subdir} +# /getfile.py -> /revision/{id}/file/{path}&download #??? +# /getdiff.py -> /revision/{id}/diff/{id}[/{fname}]&download #??? +# /gettar.py -> /revision/{id}/tar + +# /error.psp -> /error (perhaps not needed) +# /help.psp -> /help +# /index.psp -> / +# /tags.psp -> /tags + +# /getjson.py -> /json[...] (private) + +class Link: + def __init__(self, description=None): + self.relative_uri = None + self.description = description + def html(self): + return '%s' % (self.relative_uri, + self.description) + +class RevisionLink(Link): + def __init__(self, revision, **kwargs): + Link.__init__(*(self, ), **kwargs) + self.relative_uri = 'revision/info/%s' % (revision) + self.description = revision.abbrev() + +class TagLink(Link): + def __init__(self, tag, **kwargs): + Link.__init__(*(self, ), **kwargs) + self.relative_uri = 'revision/info/%s' % (tag.revision) + self.description = tag.name + +class BranchLink(Link): + def __init__(self, branch, **kwargs): + Link.__init__(*(self, ), **kwargs) + self.relative_uri = 'branch/changes/' + hq(branch.name) + self.description = hq(branch.name) + +type_to_link_class = { + 'tag' : TagLink, + 'branch' : BranchLink +} + +def link(obj): + link_class = type_to_link_class.get(obj.obj_type) + if not link_class: + raise LinkException("Unable to link to objects of type: '%s'" % (obj.obj_type)) + return link_class(obj) + +class Renderer: + def __init__(self): + # any templates that can be inherited from, should be added to the list here + self.templates = [ ('base.html', 'base'), + ('revision.html', 'revision'), ] + self._templates_loaded = False + + # these variables will be available to any template + self.terms = { + 'context' : web.context, # fugly + 'dynamic_uri_path' : config.dynamic_uri_path, + 'dynamic_join' : lambda path: urlparse.urljoin(config.dynamic_uri_path, path), + 'link' : link, + 'static_uri_path' : config.static_uri_path, + 'static_join' : lambda path: urlparse.urljoin(config.static_uri_path, path), + } + + def load_templates(self): + if self._templates_loaded: return + for template, mod_name in self.templates: + web.render(template, None, True, mod_name) + self._templates_loaded = True + + def render(self, template, **kwargs): + self.load_templates() + terms = self.terms.copy() + terms.update(kwargs) + web.render(template, terms) + +renderer = Renderer() +ops = mtn.Operations([config.monotone, config.dbfile]) + +class Index: + def GET(self): + renderer.render('index.html', page_title="Branches", branches=ops.branches()) + +class About: + def GET(self): + renderer.render('about.html', page_title="About") + +class Tags: + def GET(self): + renderer.render('tags.html', page_title="Tags", tags=ops.tags()) + +class Help: + def GET(self): + renderer.render('help.html', page_title="Help") + +class RevisionInfo: + def GET(self, revision): + revision = mtn.Revision(revision) + renderer.render('revisioninfo.html', + page_title="Revision %s" % revision.abbrev(), + revision=revision) + +class RevisionDiff: + def GET(self, revision_from, revision_to): + revision_from = mtn.Revision(revision_from) + revision_to = mtn.Revision(revision_to) + renderer.render('revisiondiff.html', + page_title="Diff from %s to %s" % (revision_from.abbrev(), revision_to.abbrev()), + revision_from=revision_from, + revision_to=revision_to) + +class RevisionFile: + def GET(self, revision, file): + revision = mtn.Revision(revision) + print "file %s from revision %s" % (file, revision) + +class RevisionBrowse: + def GET(self, revision, path): + revision = mtn.Revision(revision) + renderer.render('revisionpath.html', + page_title=revision, + path=path) + +class RevisionTar: + def GET(self, revision): + revision = mtn.Revision(revision) + print "not implemented" + +class Json: + def GET(self, method, data): + print "Bah." + +branch_re = r'' +urls = ( + '/', 'Index', + '/about', 'About', + '/tags', 'Tags', + '/help', 'Help', + '/json/(A-Za-z)/(.*)', 'Json', + + '/revision/browse/('+mtn.revision_re+')/(.*)', 'RevisionBrowse', + '/revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')', 'RevisionDiff', + '/revision/file/('+mtn.revision_re+')/(.*)', 'RevisionFile', + '/revision/info/('+mtn.revision_re+')', 'RevisionInfo', + '/revision/tar/('+mtn.revision_re+')', 'RevisionTar', + + '/branch/changes/(.*)', 'BranchChanges', + '/branch/head/(.*)', 'BranchHead', + '/branch/tar/(.*)', 'BranchTar' + +) + +if __name__ == '__main__': + web.internalerror = web.debugerror + web.run(urls, web.reloader) ============================================================ --- www/viewmtn/web.py 4c396c690078ecd159e98a016dd3c9de67d63f42 +++ www/viewmtn/web.py 4c396c690078ecd159e98a016dd3c9de67d63f42 @@ -0,0 +1,1270 @@ +#!/usr/bin/env python +"""web.py: makes web apps (http://webpy.org)""" +__version__ = "0.13" +__license__ = "Affero General Public License, Version 1" +__author__ = "Aaron Swartz " + +from __future__ import generators + +# long term todo: +# - new form system +# - new templating system +# - unit tests? + +# todo: +# - add db select, delete function +# - fix that silly nonce stuff in web.update +# - provide an option to use .write() +# - add ip:port support +# - allow people to do $self.id from inside a reparam +# - add sqlite support +# - make storage a subclass of dictionary +# - convert datetimes, floats in WebSafe +# - locks around memoize +# - fix memoize to use cacheify style techniques +# - merge curval query with the insert +# - figure out how to handle squid, etc. for web.ctx.ip + +import os, os.path, sys, time, types, traceback +import cgi, re, urllib, urlparse, Cookie, pprint +from threading import currentThread +try: import datetime +except ImportError: pass +try: + from Cheetah.Compiler import Compiler + from Cheetah.Filters import Filter + _hasTemplating = True +except ImportError: + _hasTemplating = False + +try: + from DBUtils.PooledDB import PooledDB + _hasPooling = True +except ImportError: + _hasPooling = False + +# hack for compatibility with Python 2.3: +if not hasattr(traceback, 'format_exc'): + from cStringIO import StringIO + def format_exc(limit=None): + s = StringIO() + traceback.print_exc(limit, s) + return s.getvalue() + traceback.format_exc = format_exc + +## general utils + +def _strips(direction, text, remove): + if direction == 'l': + if text.startswith(remove): return text[len(remove):] + elif direction == 'r': + if text.endswith(remove): return text[:-len(remove)] + else: + raise "WrongDirection", "Needs to be r or l." + return text + +def rstrips(a, b): return _strips('r', a, b) +def lstrips(a, b): return _strips('l', a, b) +def strips(a, b): return rstrips(lstrips(a,b),b) + +def autoassign(): + locals = sys._getframe(1).f_locals + self = locals['self'] + for (k, v) in locals.iteritems(): + if k == 'self': continue + setattr(self, k, v) + +class Storage: + def __init__(self, initial=None): + if initial: + for k in initial.keys(): setattr(self, k, initial[k]) + + def __getattr__(self, k): + if hasattr(self.__dict__, k) or ( + k.startswith('__') and k.endswith('__')): # special keyword + return getattr(self.__dict__, k) + raise AttributeError, repr(k) + + def __repr__(self): return '' + +storage = Storage + +def storify(f, *requireds, **defaults): + stor = Storage() + + for k in requireds + tuple(f.keys()): + v = f[k] + if isinstance(k, list): v = v[-1] + if hasattr(v, 'value'): v = v.value + setattr(stor, k, v) + + for (k,v) in defaults.iteritems(): + result = v + if hasattr(stor, k): result = stor[k] + if v == () and not isinstance(result, tuple): result = (result,) + setattr(stor, k, result) + + return stor + +class memoize: + def __init__(self, func): self.func = func; self.cache = {} + def __call__(self, *a, **k): + key = (a, tuple(k.items())) + if key not in self.cache: self.cache[key] = self.func(*a, **k) + return self.cache[key] + +re_compile = memoize(re.compile) #@@ threadsafe? + +class _re_subm_proxy: + def __init__(self): self.match = None + def __call__(self, match): self.match = match; return '' + +def re_subm(pat, repl, string): + """like re.sub, but returns the replacement and the match object""" + r = re_compile(pat) + proxy = _re_subm_proxy() + r.sub(proxy.__call__, string) + return r.sub(repl, string), proxy.match + +def group(seq, size): + """Breaks 'seq' into a generator of lists with length 'size'.""" + if not hasattr(seq, 'next'): seq = iter(seq) + while True: yield [seq.next() for i in xrange(size)] + +class iterbetter: + def __init__(self, iterator): self.i, self.c = iterator, 0 + def __iter__(self): + while 1: yield self.i.next(); self.c += 1 + def __getitem__(self, i): + #todo: slices + if i > self.c: raise KeyError, "already passed "+str(i) + try: + while i < self.c: self.i.next(); self.c += 1 + # now self.c == i + self.c += 1; return self.i.next() + except StopIteration: raise KeyError, repr(i) + +def dictfind(d, elt): + for (k,v) in d.iteritems(): + if elt is v: return k + +def dictincr(d, e): + d.setdefault(e, 0) + d[e] += 1 + return d[e] + +def dictadd(a, b): + result = {} + result.update(a) + result.update(b) + return result + +sumdicts = dictadd # deprecated + +def listget(l, n, v=None): + if len(l)-1 < n: return v + return l[n] + +def upvars(n=2): + return dictadd( + sys._getframe(n).f_globals, + sys._getframe(n).f_locals) + +class capturestdout: + def __init__(self, func): self.func = func + def __call__(self, *args, **kw): + from cStringIO import StringIO + # Not threadsafe! + out = StringIO() + oldstdout = sys.stdout + sys.stdout = out + try: self.func(*args, **kw) + finally: sys.stdout = oldstdout + return out.getvalue() + +class profile: + def __init__(self, func): self.func = func + def __call__(self, *args, **kw): + import hotshot, hotshot.stats, tempfile, time + temp = tempfile.NamedTemporaryFile() + prof = hotshot.Profile(temp.name) + + stime = time.time() + result = prof.runcall(self.func, *args) + stime = time.time() - stime + + prof.close() + stats = hotshot.stats.load(temp.name) + stats.strip_dirs() + stats.sort_stats('time', 'calls') + x = '\n\ntook '+ str(stime) + ' seconds\n' + x += capturestdout(stats.print_stats)(40) + x += capturestdout(stats.print_callers)() + return result, x + +def tryall(context): + context = context.copy() # vars() would update + results = {} + for (k, v) in context.iteritems(): + if not hasattr(v, '__call__'): continue + print k+':', + try: + r = v() + dictincr(results, r) + print r + except: + print 'ERROR' + dictincr(results, 'ERROR') + print ' '+'\n '.join(traceback.format_exc().split('\n')) + + print '-'*40 + print 'results:' + for (k, v) in results.iteritems(): + print ' '*2, str(k)+':', v + +class threadeddict: + def __init__(self, d): self.__dict__['_threadeddict__d'] = d + def __getattr__(self, a): return getattr(self.__d[currentThread()], a) + def __getitem__(self, i): return self.__d[currentThread()][i] + def __setattr__(self, a, v): return setattr(self.__d[currentThread()], a, v) + def __setitem__(self, i, v): self.__d[currentThread()][i] = v + def __hash__(self): return hash(self.__d[currentThread()]) + +## url utils + +def base(base=''): #when would you use a default base? + url = context.path.lstrip('/') + for i in xrange(url.count('/')): base += '../' + if not base: base = './' + return base + +## formatting + +try: + from markdown import markdown # http://webpy.org/markdown.py +except ImportError: pass + +r_url = re_compile('(?', text) + text = markdown(text) + return text + +## db api + +class UnknownParamstyle(Exception): pass +r_dbvar = re_compile(r'\B\$(\w+)') +def reparam(q): + p = ctx.db_module.paramstyle + if p == "pyformat": + return r_dbvar.sub(r'%(\1)s', q) + elif p == "named": + return r_dbvar.sub(r':\1', q) + raise UnknownParamstyle, p + +def aparam(): + p = ctx.db_module.paramstyle + if p == 'qmark': + return '?' + elif p == 'numeric': + return ':1' + elif p == 'format': + return '%s' + elif p == 'pyformat': + return '%s' + raise UnknownParamstyle, p + +class UnknownDB(Exception): pass +def connect(dbn, **kw): + if dbn == "postgres": + try: import psycopg2 as db + except ImportError: + try: import psycopg as db + except ImportError: import pgdb as db + kw['password'] = kw['pw'] + del kw['pw'] + kw['database'] = kw['db'] + del kw['db'] + elif dbn == "mysql": + import MySQLdb as db + kw['passwd'] = kw['pw'] + del kw['pw'] + db.paramstyle = 'pyformat' # it's both, like psycopg + else: raise UnknownDB, dbn + ctx.db_name = dbn + ctx.db_module = db + ctx.db_transaction = False + if _hasPooling: + if 'db' not in globals(): globals()['db'] = PooledDB(dbapi=db, **kw) + ctx.db = globals()['db'].connection() + else: + ctx.db = db.connect(**kw) + ctx.dbq_count = 0 + if globals().get('db_printing'): + def db_execute(cur, q, d=None): + ctx.dbq_count += 1 + try: outq = q % d + except: outq = q + print>>debug, str(ctx.dbq_count)+':', outq + a = time.time() + out = cur.execute(q, d) + b = time.time() + print>>debug, '(%s)' % round(b-a, 2) + return out + ctx.db_execute = db_execute + else: + ctx.db_execute = lambda cur, q, d=None: cur.execute(q, d) + return ctx.db + +def transact(): + """Start a transaction.""" + # commit everything up to now, so we don't rollback it later + ctx.db.commit() + ctx.db_transaction = True + +def commit(): + ctx.db.commit() + ctx.db_transaction = False + +def rollback(): + ctx.db.rollback() + ctx.db_transaction = False + +def query(q, v=None): + d = ctx.db.cursor() + if v is None: v = upvars() + + ctx.db_execute(d, reparam(q), v) + if d.description: + names = [x[0] for x in d.description] + def iterwrapper(): + x = d.fetchone() + while x: + yield Storage(dict(zip(names, x))) + x = d.fetchone() + out = iterbetter(iterwrapper()) + out.__len__ = lambda: d.rowcount + else: + out = None + out = d.rowcount + + if not ctx.db_transaction: ctx.db.commit() + return out + +def select(tablename, what='*', where=None, vars=None, **wheres): + if vars is None: vars = upvars() + + q = "SELECT "+what+" FROM "+tablename+" WHERE " + if where: q += where + for (k, v) in wheres.iteritems(): + where += k + '=' + nonce(v, vars) + return query(q, vvars) + +def insert(tablename, seqname=None, **values): + d = ctx.db.cursor() + + if values: + ctx.db_execute(d, "INSERT INTO %s (%s) VALUES (%s)" % ( + tablename, + ", ".join(values.keys()), + ', '.join([aparam() for x in values]) #@@ use nonce + ), values.values()) + else: + ctx.db_execute(d, "INSERT INTO %s DEFAULT VALUES" % tablename) + + if ctx.db_name == "postgres" and seqname != False: + if seqname is None: seqname = tablename + "_id_seq" + ctx.db_execute(d, "SELECT currval('%s')" % seqname) + out = d.fetchone()[0] + elif ctx.db_name == "mysql": + ctx.db_execute(d, "SELECT last_insert_id()") + out = d.fetchone()[0] + elif ctx.db_name == "sqlite": + # not really the same... + ctx.db_execute(d, "SELECT last_insert_rowid()") + out = d.fetchone()[0] + else: + out = None + + if not ctx.db_transaction: ctx.db.commit() + return out + +def update(tablename, where, pvars=(), **values): + pvars = list(pvars) + if isinstance(where, int): + pvars.append(where) + where = "id = "+aparam() + else: + where = where #@@ need to figure out positional params + + d = ctx.db.cursor() + ctx.db_execute(d, "UPDATE %s SET %s WHERE %s" % ( + tablename, + ', '.join([k+'='+aparam() for k in values.keys()]), + where), + values.values()+pvars) + + if not ctx.db_transaction: ctx.db.commit() + return d.rowcount + +## request handlers + +def handle(mapping, fvars=None): + for url, ofno in group(mapping, 2): + if isinstance(ofno, tuple): ofn, fna = ofno[0], list(ofno[1:]) + else: ofn, fna = ofno, [] + fn, result = re_subm('^'+url+'$', ofn, context.path) + if result: # it's a match + if fn.split(' ', 1)[0] == "redirect": + url = fn.split(' ', 1)[1] + if context.method == "GET": + x = context.environ.get('QUERY_STRING', '') + if x: url += '?'+x + return redirect(url) + elif '.' in fn: + x = fn.split('.') + mod, cls = '.'.join(x[:-1]), x[-1] + mod = __import__(mod, globals(), locals(), [""]) + cls = getattr(mod, cls) + else: + cls = fn + mod = fvars or upvars() + if isinstance(mod, types.ModuleType): mod = vars(mod) + try: cls = mod[cls] + except KeyError: return notfound() + + meth = context.method + if meth == "HEAD": + if not hasattr(cls, meth): meth = "GET" + if not hasattr(cls, meth): return nomethod(cls) + tocall = getattr(cls(), meth) + args = list(result.groups()) + for d in re.findall(r'\\(\d+)', ofn): + args.pop(int(d)-1) + return tocall(*([urllib.unquote(x) for x in args]+fna)) + + return notfound() + +def autodelegate(prefix=''): + def internal(self, arg): + func = prefix+arg + if hasattr(self, func): return getattr(self, func)() + else: return notfound() + return internal + +## http defaults + +def redirect(url, status='301 Moved Permanently'): + newloc = urlparse.urljoin(context.home + context.path, url) + context.status = status + header('Content-Type', 'text/html') + header('Location', newloc) + # seems to add a three-second delay for some reason: + # output('moved permanently') + +def found(url): return redirect(url, '302 Found') +def seeother(url): return redirect(url, '303 See Other') +def tempredirect(url): return redirect(url, '307 Temporary Redirect') + +def badrequest(): + context.status = '400 Bad Request' + header('Content-Type', 'text/html') + return output('bad request') + +def notfound(): + context.status = '404 Not Found' + header('Content-Type', 'text/html') + return output('not found') + +def nomethod(cls): + context.status = '405 Method Not Allowed' + header('Content-Type', 'text/html') + header("Allow", ', '.join([x for x in ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] if hasattr(cls, x)])) + return output('method not allowed') + +def gone(): + context.status = '410 Gone' + header('Content-Type', 'text/html') + return output("gone") + +def expires(delta): + try: datetime + except NameError: raise Exception, "this function requires at least python2.3" + if isinstance(delta, (int, long)): + delta = datetime.timedelta(seconds=delta) + o = datetime.datetime.utcnow() + delta + header('Expires', o.strftime("%a, %d %b %Y %T GMT")) + +def lastmodified(d): + header('Last-Modified', d.strftime("%a, %d %b %Y %T GMT")) + +# adapted from Django +# Copyright (c) 2005, the Lawrence Journal-World +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + +DJANGO_500_PAGE = """#import inspect + + + + + + $exception_type at $context.path + + + + + +
+

$exception_type at $context.path

+

$exception_value

+ + + + + + +
Python$lastframe.filename in $lastframe.function, line $lastframe.lineno
Web$context.method $context.home$context.path
+
+
+

Traceback (innermost first)

+
    + #for frame in $frames +
  • + $frame.filename in $frame.function + + #if $frame.context_line +
    + #if $frame.pre_context +
      #for line in $frame.pre_context#
    1. $line
    2. #end for#
    + #end if +
    1. $frame.context_line ...
    + #if $frame.post_context +
      #for line in $frame.post_context#
    1. $line
    2. #end for#
    + #end if +
    + #end if + + #if $frame.vars +
    + Local vars## $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame)) +
    + + + + + + + + + #set frameitems = $frame.vars + #silent frameitems.sort(lambda x,y: cmp(x[0], y[0])) + #for (key, val) in frameitems + + + + + #end for + +
    VariableValue
    $key
    $prettify(val)
    + #end if +
  • + #end for +
+
+ +
+ #if $context_.output or $context_.headers +

Response so far

+

HEADERS

+ #if $context.headers +

+ #for (k, v) in $context_.headers + $k: $v
+ #end for + +

+ #else +

No headers.

+ #end if +

BODY

+

+ $context_.output +

+ #end if + +

Request information

+ +

INPUT

+ #if $input_ + + + + + + + + + #set myitems = $input_.items() + #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) + #for (key, val) in myitems + + + + + #end for + +
VariableValue
$key
$val
+ #else +

No input data.

+ #end if + + + #if $cookies_ + + + + + + + + + #for (key, val) in $cookies_.items() + + + + + #end for + +
VariableValue
$key
$val
+ #else +

No cookie data

+ #end if + +

META

+ + + + + + + + + #set myitems = $context_.items() + #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) + #for (key, val) in $myitems + #if not $key.startswith('_') and $key not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute'] + + + + + #end if + #end for + +
VariableValue
$key
$prettify($val)
+ +

ENVIRONMENT

+ + + + + + + + + #set myitems = $context_.environ.items() + #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) + #for (key, val) in $myitems + + + + + #end for + +
VariableValue
$key
$prettify($val)
+ +
+ +
+

+ You're seeing this error because you have web.internalerror + set to web.debugerror. Change that if you want a different one. +

+
+ + +""" + +def djangoerror(): + def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + + exception_type, exception_value, tb = sys.exc_info() + frames = [] + while tb is not None: + filename = tb.tb_frame.f_code.co_filename + function = tb.tb_frame.f_code.co_name + lineno = tb.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) + frames.append({ + 'tb': tb, + 'filename': filename, + 'function': function, + 'lineno': lineno, + 'vars': tb.tb_frame.f_locals.items(), + 'id': id(tb), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno, + }) + tb = tb.tb_next + lastframe = frames[-1] + frames.reverse() + urljoin = urlparse.urljoin + input_ = input() + cookies_ = cookies() + context_ = context + def prettify(x): + try: out = pprint.pformat(x) + except Exception, e: out = '[could not display: <'+e.__class__.__name__+': '+str(e)+'>]' + return out + return render(DJANGO_500_PAGE, asTemplate=True, isString=True) + +def internalerror(): + context.status = "500 Internal Server Error" + context.headers = [('Content-Type', 'text/html')] + context.output = "internal server error" + +def debugerror(): + # need to do django first, so it can get the old stuff + if _hasTemplating: + out = str(djangoerror()) + else: + # Cheetah isn't installed + out = """

You've set web.py to use the fancier debugerror error messages, +but these messages require you install the Cheetah template +system. For more information, see +the web.py website.

+ +

In the meantime, here's a plain old error message:

+ +
%s
+ +

(If it says something about 'Compiler', then it's probably +because you're trying to use templates and you haven't +installed Cheetah. See above.)

+""" % htmlquote(traceback.format_exc()) + context.status = "500 Internal Server Error" + context.headers = [('Content-Type', 'text/html')] + context.output = out + + +## rendering + +r_include = re_compile(r'(?!\\)#include \"(.*?)\"($|#)', re.M) +def __compiletemplate(template, base=None, isString=False): + if isString: text = template + else: text = open('templates/'+template).read() + # implement #include at compile-time + def do_include(match): + text = open('templates/'+match.groups()[0]).read() + return text + while r_include.findall(text): text = r_include.sub(do_include, text) + + execspace = _compiletemplate.bases.copy() + c = Compiler(source=text, mainClassName='GenTemplate') + c.addImportedVarNames(execspace.keys()) + exec str(c) in execspace + if base: _compiletemplate.bases[base] = execspace['GenTemplate'] + + return execspace['GenTemplate'] + +_compiletemplate = memoize(__compiletemplate) +_compiletemplate.bases = {} + +def htmlquote(s): + s = s.replace("&", "&") # Must be done first! + s = s.replace("<", "<") + s = s.replace(">", ">") + s = s.replace("'", "'") + s = s.replace('"', """) + return s + +urlquote = urllib.quote + +if _hasTemplating: + class WebSafe(Filter): + def filter(selv, val, **kw): + if val is None: return '' + return htmlquote(str(val)) + +def render(template, terms=None, asTemplate=False, base=None, isString=False): + # terms=['var1', 'var2'] means grab those variables + if isinstance(terms, list): + new = {}; old = upvars() + for k in terms: new[k] = old[k] + terms = new + # default: grab all locals + elif terms is None: + terms = {'context': context} + terms.update(sys._getframe(1).f_locals) + # terms=d means use d as the searchList + if not isinstance(terms, tuple): + terms = (terms,) + + if not isString and template.endswith('.html'): header('Content-Type','text/html; charset=utf-8') + + t = _compiletemplate(template, base=base, isString=isString) + t = t(searchList=terms, filter=WebSafe) + if asTemplate: return t + else: return output(str(t)) + +## input forms + +def input(*requireds, **defaults): + if not hasattr(context, '_inputfs'): context._inputfs = cgi.FieldStorage(fp = context.environ['wsgi.input'],environ=context.environ, keep_blank_values=1) + return storify(context._inputfs, *requireds, **defaults) + +## cookies + +def setcookie(name, value, expires="", domain=None): + if expires < 0: expires = -1000000000 + kargs = {'expires': expires, 'path':'/'} + if domain: kargs['domain'] = domain + # @@ should we limit cookies to a different path? + c = Cookie.SimpleCookie() + c[name] = value + for key, val in kargs.iteritems(): c[name][key] = val + header('Set-Cookie', c.items()[0][1].OutputString()) + +def cookies(*requireds, **defaults): + c = Cookie.SimpleCookie() + c.load(context.environ.get('HTTP_COOKIE', '')) + return storify(c, *requireds, **defaults) + +## WSGI Sugar + +def header(h, v): context.headers.append((h, v)) +def output(t): context.output += str(t) + +def write(t): + t = str(t) + t.replace('\r\n', '\n') + head, body = t.split('\n\n', 1) + lines = head.split('\n') + + for line in lines: + if line.isspace(): continue + h, v = line.split(":", 1) + v = v.strip() + if h.lower() == "status": context.status = v + else: header(h, v) + + output(body) + +def webpyfunc(inp, fvars=None, autoreload=False): + if not fvars: fvars = upvars() + if not hasattr(inp, '__call__'): + if autoreload: + # black magic to make autoreload work: + mod = __import__(fvars['__file__'].split(os.path.sep).pop().split('.')[0]) + #@@probably should replace this with some inspect magic + name = dictfind(fvars, inp) + func = lambda: handle(getattr(mod, name), mod) + else: + func = lambda: handle(inp, fvars) + else: + func = inp + return func + +def wsgifunc(func, *middleware): + middleware = list(middleware) + if reloader in middleware: + relr = reloader(None) + relrcheck = relr.check + middleware.remove(reloader) + else: + relr = None + relrcheck = lambda: None + + def wsgifunc(e, r): + _load(e) + relrcheck() + result = func() + is_generator = result and hasattr(result, 'next') + if is_generator: + # we need to give wsgi back the headers first, + # so we need to do at iteration + try: firstchunk = handler_result.next() + except StopIteration: firstchunk = '' + status, headers, output = ctx.status, ctx.headers, ctx.output + _unload() + r(status, headers) + if is_generator: return itertools.chain([firstchunk], result) + elif isinstance(output, str): return [output] #@@ other stringlikes? + elif hasattr(output, 'next'): return output + else: raise Exception, "Invalid web.context.output" + + for x in middleware: wsgifunc = x(wsgifunc) + + if relr: + relr.func = wsgifunc + return wsgifunc + return wsgifunc + +def run(inp, *middleware): + autoreload = reloader in middleware + fvars = upvars() + return runwsgi(wsgifunc(webpyfunc(inp, fvars, autoreload), *middleware)) + +def runwsgi(func): + #@@ improve detection + if os.environ.has_key('SERVER_SOFTWARE'): # cgi + os.environ['FCGI_FORCE_CGI'] = 'Y' + + if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi + or os.environ.has_key('SERVER_SOFTWARE')): + import flup.server.fcgi + return runfcgi(func) + + if 'scgi' in sys.argv: + import flup.server.scgi + return runscgi(func) + + # command line: + return runsimple(func, listget(sys.argv, 1, 8080)) + +def runsimple(func, port=8080): + # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + # Modified somewhat for simplicity + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + + import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse + import sys, socket, errno + import traceback + + class WSGIHandler (SimpleHTTPServer.SimpleHTTPRequestHandler): + def runWSGIApp(self): + protocol, host, path, parameters, query, fragment = urlparse.urlparse ('http://dummyhost%s' % self.path) + # we only use path, query + env = {'wsgi.version': (1,0) + ,'wsgi.url_scheme': 'http' + ,'wsgi.input': self.rfile + ,'wsgi.errors': sys.stderr + ,'wsgi.multithread': 1 + ,'wsgi.multiprocess': 0 + ,'wsgi.run_once': 0 + ,'REQUEST_METHOD': self.command + ,'REQUEST_URI': self.path + ,'PATH_INFO': path + ,'QUERY_STRING': query + ,'CONTENT_TYPE': self.headers.get ('Content-Type', '') + ,'CONTENT_LENGTH': self.headers.get ('Content-Length', '') + ,'REMOTE_ADDR': self.client_address[0] + ,'SERVER_NAME': self.server.server_address [0] + ,'SERVER_PORT': str (self.server.server_address [1]) + ,'SERVER_PROTOCOL': self.request_version + } + + for httpHeader, httpValue in self.headers.items(): + env ['HTTP_%s' % httpHeader.replace ('-', '_').upper()] = httpValue + + # Setup the state + self.wsgiSentHeaders = 0 + self.wsgiHeaders = [] + + try: + # We have there environment, now invoke the application + result = self.server.app(env, self.wsgiStartResponse) + try: + try: + for data in result: + if data: self.wsgiWriteData (data) + finally: + if hasattr(result, 'close'): result.close() + except socket.error, socketErr: + # Catch common network errors and suppress them + if (socketErr.args[0] in (errno.ECONNABORTED, errno.EPIPE)): return + except socket.timeout, socketTimeout: return + except: + print >> debug, traceback.format_exc(), + internalerror() + if not self.wsgiSentHeaders: + self.wsgiStartResponse(ctx.status, ctx.headers) + self.wsgiWriteData(ctx.output) + + if (not self.wsgiSentHeaders): + # We must write out something! + self.wsgiWriteData(" ") + return + + do_POST = runWSGIApp + + def do_GET(self): + if self.path.startswith('/static/'): + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + else: + self.runWSGIApp() + + def wsgiStartResponse (self, response_status, response_headers, exc_info=None): + if (self.wsgiSentHeaders): + raise Exception ("Headers already sent and start_response called again!") + # Should really take a copy to avoid changes in the application.... + self.wsgiHeaders = (response_status, response_headers) + return self.wsgiWriteData + + def wsgiWriteData (self, data): + if (not self.wsgiSentHeaders): + status, headers = self.wsgiHeaders + # Need to send header prior to data + statusCode = status [:status.find (' ')] + statusMsg = status [status.find (' ') + 1:] + self.send_response (int (statusCode), statusMsg) + for header, value in headers: + self.send_header (header, value) + self.end_headers() + self.wsgiSentHeaders = 1 + # Send the data + self.wfile.write (data) + + class WSGIServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + def __init__(self, func): + BaseHTTPServer.HTTPServer.__init__(self, ("0.0.0.0", int(port)), WSGIHandler) + self.app = func + self.serverShuttingDown = 0 + + print "Launching server: http://0.0.0.0:"+str(port)+"/" + WSGIServer(func).serve_forever() + + +def makeserver(WSGIServer): + class MyServer(WSGIServer): + def error(self, req): + w = req.stdout.write + internalerror() + w('Status: '+context.status+'\r\n') + for (h, v) in context.headers: + w(h+': '+v+'\r\n') + w('\r\n'+context.output) + + return MyServer + +def runfcgi(func): + from flup.server.fcgi import WSGIServer + return makeserver(WSGIServer)(func, multiplexed=True).run() + +def runscgi(func): + from flup.server.scgi import WSGIServer + MyServer = makeserver(WSGIServer) + if len(sys.argv) > 2: # progname, scgi + args = sys.argv[:] + args.remove('scgi') + hostport = args[1] + hostport = hostport.split(':',1) + if len(hostport) == 2: hostport = (hostport[0], int(hostport[1])) + else: hostport = ('localhost',int(hostport[0])) + else: hostport = ('localhost',4000) + return MyServer(func, bindAddress=hostport).run() + +## debug + +def debug(*args): + try: out = context.environ['wsgi.errors'] + except: out = sys.stderr + for x in args: + print >> out, pprint.pformat(x) + return '' + +def debugwrite(x): + try: out = context.environ['wsgi.errors'] + except: out = sys.stderr + out.write(x) +debug.write = debugwrite + +class reloader: + def __init__(self, func, tocheck=None): + self.func = func + self.mtimes = {} + global _compiletemplate + b = _compiletemplate.bases + _compiletemplate = globals()['__compiletemplate'] + _compiletemplate.bases = b + + def check(self): + for mod in sys.modules.values(): + try: mtime = os.stat(mod.__file__).st_mtime + except (AttributeError, OSError, IOError): continue + if mod.__file__.endswith('.pyc') and os.path.exists(mod.__file__[:-1]): + mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime) + if mod not in self.mtimes: + self.mtimes[mod] = mtime + elif self.mtimes[mod] < mtime: + try: reload(mod) + except ImportError: pass + return True + + def __call__(self, e, o): + self.check() + return self.func(e, o) + +def profiler(app): + def profile_internal(e, o): + out, result = profile(app)(e, o) + return out + ['
'+result+'
'] #@@encode + return profile_internal + +## setting up the context + +class _outputter: + def write(self, x): + if hasattr(ctx, 'output'): return output(x) + else: _oldstdout.write(x) + def flush(self): return _oldstdout.flush() + def close(self): return _oldstdout.close() + +_context = {currentThread():Storage()} +ctx = context = threadeddict(_context) + +if not '_oldstdout' in globals(): + _oldstdout = sys.stdout + sys.stdout = _outputter() + +def _load(env): + _context[currentThread()] = Storage() + ctx.environ = ctx.env = env + ctx.host = env.get('HTTP_HOST') + ctx.home = 'http://' + env.get('HTTP_HOST', '[unknown]') + env.get('SCRIPT_NAME', '') + ctx.ip = env.get('REMOTE_ADDR') + ctx.method = env.get('REQUEST_METHOD') + ctx.path = env.get('PATH_INFO') + # http://trac.lighttpd.net/trac/ticket/406 requires: + if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): + ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], env.get('SCRIPT_NAME')) + + ctx.fullpath = ctx.path + if dict(input()): ctx.fullpath+='?'+urllib.urlencode(dict(input())) + ctx.status = '200 OK' + ctx.headers = [] + ctx.output = '' + if 'db_parameters' in globals(): + connect(**db_parameters) + +def _unload(): + # ensures db cursors and such are GCed promptly + del _context[currentThread()] + +if __name__ == "__main__": + urls = ('/web.py', 'source') + class source: + def GET(self): + header('Content-Type', 'text/python') + print open(sys.argv[0]).read() + run(urls) ============================================================ --- www/viewmtn/wrapper.py dbbc871353ddfec1b56bf57b743a6b5cd70688c2 +++ www/viewmtn/wrapper.py dbbc871353ddfec1b56bf57b743a6b5cd70688c2 @@ -0,0 +1,212 @@ + +from mod_python import apache,psp,util +from common import parse_timecert, ago_string, determine_date +import mimetypes +import monotone +import urlparse +import datetime +import os.path +import tarfile +import common +import config +import urllib +import json +import os +import re + +from monotone import Monotone +from html import Template + +# paranoid +sane_uri_re = re.compile('^\w+$') + +def get_file(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + if not monotone.is_valid_id(id): + return apache.HTTP_BAD_REQUEST + if form.has_key('plain'): + mime_type = "text/plain" + else: + mime_type = None + if form.has_key('path'): + if mime_type == None: mime_type = mimetypes.guess_type(form['path'])[0] + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % urllib.quote(os.path.split(form['path'])[-1]) + if mime_type == None: mime_type = "text/plain" + req.content_type = mime_type + "; charset=utf-8" + req.write(mt.file(id)) + return apache.OK + +def get_diff(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id1') or not form.has_key('id2'): + return apache.HTTP_BAD_REQUEST + if form.has_key('fname'): files = [form['fname']] + else: files = None + id1, id2 = form['id1'], form['id2'] + if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): + return apache.HTTP_BAD_REQUEST + req.content_type = "text/plain; charset=utf-8" + req.write(mt.diff(id1, id2, files)) + return apache.OK + +def get_json(req, vars): + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('className') or not form.has_key('linkUri'): + return apache.HTTP_BAD_REQUEST + class_name, link_uri = form['className'], form['linkUri'] + req.content_type = "text/plain; charset=utf-8" + writer = json.JsonWriter() + query = {} + for key, value in [t.split('=', 1) for t in urlparse.urlparse(link_uri)[4].split('&')]: + query[key] = value + + now = datetime.datetime.utcnow() + rv = {} + if class_name == "revisionLink": + rv['type'] = 'revision' + certs = mt.certs(query['id']) + rv['author'] = common.extract_cert_from_certs(certs, 'author') + change_date = determine_date(certs) + if change_date != None: + ago = ago_string(change_date, now) + else: + ago = '' + rv['ago'] = ago + elif class_name == "branchLink": + rv['type'] = 'branch' + heads = mt.heads(query['branch']) + most_recent_change = None + for head in heads: + certs = mt.certs(head) + this_change = determine_date(certs) + if this_change == None: + continue + if most_recent_change == None or this_change > most_recent_change: + most_recent_change = this_change + most_recent_change_certs = certs + if most_recent_change != None: + ago = ago_string(most_recent_change, now) + ' ago' + last_author = common.extract_cert_from_certs(most_recent_change_certs, + "author") or '' + else: + ago = '' + rv['ago'] = ago + rv['author'] = last_author + elif class_name == "manifestLink": + rv['type'] = 'manifest' + dir_seen = {} # would use a set, but need python2.4 really + rv['file_count'] = 0 + for file_id, filename in ((t[3], t[1]) for t in mt.manifest_of(query['id'])['file']): + fsp = filename.rsplit('/', 1) + if len(fsp) == 2 and not dir_seen.has_key(fsp[1]): + dir_seen[fsp[1]] = True + rv['file_count'] += 1 + rv['directory_count'] = len(dir_seen.keys()) + 1 # root dir + + req.write(writer.write(rv)) + return apache.OK + +def get_tar(req, vars): + "make a tar file out of a given manifest ID" + class DummyFile: + def __init__(self, buf): + self.buf = buf + def seek(offset, whence=None): + # blah + return + def read(self, size): + rv, nb = self.buf[:size], self.buf[size:] + self.buf = nb + return rv + def write(self, s): + self.buf += s + mt = vars['mt'] + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + tar_file = DummyFile("") + tar_file_name = "%s.tar" % (id) + req.content_type = 'application/x-tar; charset=utf-8' + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % tar_file_name + tf = tarfile.open(mode="w", fileobj=tar_file) + for fileid, filename in ((t[3], t[1]) for t in mt.manifest_of(id)['file']): + data = mt.file(fileid) + ti = tarfile.TarInfo() + ti.mode = 00700 + ti.mtime = 0 + ti.type = tarfile.REGTYPE + ti.uid = 0 + ti.gid = 0 + ti.name = os.path.join(id, filename) + ti.size = len(data) + tf.addfile(ti, DummyFile(data)) + tf.close() + req.write(tar_file.buf) + return apache.OK + +handlers = { + 'getfile.py' : get_file, + 'getdiff.py' : get_diff, + 'gettar.py' : get_tar, + 'getjson.py' : get_json +} + +def cleanup(req, vars): + mt = vars['mt'] + mt.automate.stop() + del mt + +def handler(req): + uri = req.uri + slash = uri.rfind('/') + if slash <> -1: uri = uri[slash+1:] + + # most monotone output is utf8 + req.content_type = "text/html; charset=utf-8" + + # common variables which all handlers (PSP or not) + # should have access to; ensure that these variables + # are cleaned up + mt = Monotone(config.monotone, config.dbfile) + def our_link (link_type, link_to, description=None, no_quote=False): + return common.link(mt, link_type, link_to, description, no_quote) + vars = { + 'mt' : mt, + 'link' : our_link, + 'hq' : common.html_escape(), + 'template' : Template() + } + req.register_cleanup(cleanup, (req, vars)) + + # + # these handlers don't use PSP, for example if they need + # to return binary or otherwise pristine data to the user + # agent + # + if handlers.has_key(uri): + if req.header_only: + return apache.OK + return handlers[uri](req, vars) + + # + # PSP or 404 + # + try: + if uri.endswith('.psp') and sane_uri_re.match(uri[:-4]): + if req.header_only: + return apache.OK + instance = psp.PSP(req, filename=uri, vars=vars) + instance.run() + return apache.OK + except ValueError: + return apache.HTTP_NOT_FOUND + + return apache.HTTP_NOT_FOUND +