# # # add_file "urls.py" # content [16a510f175f983e3853bc0d61029a95e59ae0bbe] # # patch "branchdiv.py" # from [5fbaaeb9d3050ab0f510c34b4ccfe18e7fda89a1] # to [49a419788c7b54eed260b770c7a89066da499c1f] # # patch "handlers.py" # from [59c9ba315b35d9ffe2667872266876b511188802] # to [fa13ff0c027a94aeec104c8b07f63d605ad78db4] # # patch "render.py" # from [032026fe3b80f63a098218b7c77c90c4d7a2e518] # to [c1223ddbb5396f63101b981838cd8296d0cc82ba] # # patch "viewmtn.py" # from [a3a3446ad7ea43e0ff0ceb6daa04dac0b7719bae] # to [6629b45724cc639d2555c41248ccadf35838b83e] # ============================================================ --- urls.py 16a510f175f983e3853bc0d61029a95e59ae0bbe +++ urls.py 16a510f175f983e3853bc0d61029a95e59ae0bbe @@ -0,0 +1,60 @@ +# Copyright (C) 2005 Grahame Bowland +# +# This program is made available under the GNU GPL version 2.0 or +# greater. See the accompanying file COPYING for details. +# +# This program is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. + +from mtn import revision_re + +# +# these are the same, regardless of which database the user wishes +# to view. These handles do not receive a RequestContext instance +# as their first argument. +# +common_urls = ( + r'about', 'About', + r'help', 'Help', + r'robots.txt', 'RobotsTxt', ## FIXME needs o exclude per-db paths + r'mimeicon/([A-Za-z0-9][a-z0-9\-\+\.]*)/([A-Za-z0-9][a-z0-9\-\+\.]*)', 'MimeIcon', +) + +# +# all of these should have GET handler that takes a per-DB context as an argument. +# They get a RequestContext instance as their first argument to methods, eg. GET +# +perdb_urls = ( + r'', 'Index', + r'tags', 'Tags', + r'json/([A-Za-z]+)/(.*)', 'Json', + + r'([a-zA-Z]/)?revision/browse/('+revision_re+')/(.*)', 'RevisionBrowse', + r'revision/browse/('+revision_re+')()', 'RevisionBrowse', + r'revision/diff/('+revision_re+')/with/('+revision_re+')', 'RevisionDiff', + r'revision/diff/('+revision_re+')/with/('+revision_re+')'+'/(.*)', 'RevisionDiff', + r'revision/rawdiff/('+revision_re+')/with/('+revision_re+')', 'RevisionRawDiff', + r'revision/rawdiff/('+revision_re+')/with/('+revision_re+')'+'/(.*)', 'RevisionRawDiff', + r'revision/file/('+revision_re+')/(.*)', 'RevisionFile', + r'revision/filechanges/()()('+revision_re+')/(.*)', 'RevisionFileChanges', + r'revision/filechanges/from/(\d+)/to/(\d+)/('+revision_re+')/(.*)', 'RevisionFileChanges', + r'revision/filechanges/rss/()()('+revision_re+')/(.*)', 'RevisionFileChangesRSS', + r'revision/filechanges/rss/from/(\d+)/to/(\d+)/('+revision_re+')/(.*)', 'RevisionFileChangesRSS', + r'revision/downloadfile/('+revision_re+')/(.*)', 'RevisionDownloadFile', + r'revision/info/('+revision_re+')', 'RevisionInfo', + r'revision/tar/('+revision_re+')', 'RevisionTar', + r'revision/graph/('+revision_re+')', 'RevisionGraph', + + r'branch/changes/(.*)/from/(\d+)/to/(\d+)', 'HTMLBranchChanges', + r'branch/changes/([^/]+)()()', 'HTMLBranchChanges', + r'branch/changes/(.*)/from/(\d+)/to/(\d+)/rss', 'RSSBranchChanges', + r'branch/changes/([^/]+)()()/rss', 'RSSBranchChanges', + r'branch/tags/([^/]+)', 'Tags', + + # let's make it possible to access any function on the head revision + # through this proxy method; it'll return a redirect to the head revision + # with the specified function + r'branch/(head)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', + r'branch/(anyhead)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', +) ============================================================ --- branchdiv.py 5fbaaeb9d3050ab0f510c34b4ccfe18e7fda89a1 +++ branchdiv.py 49a419788c7b54eed260b770c7a89066da499c1f @@ -8,14 +8,21 @@ from mk2 import MarkovChain # PURPOSE. from mk2 import MarkovChain +import sys +# it's hardly worth doing anything in this case.. +min_to_divide = 20 + class BranchDivisions(object): def __init__ (self): self.divisions = None def calculate_divisions (self, branches): - if self.divisions != None: + if not self.divisions is None: return + if len(branches) < 20: + self.divisions = [] + return chain = MarkovChain (2, join_token='.', cutoff_func=MarkovChain.log_chunkable) for branch in branches: chain.update (branch.name.split ('.')) ============================================================ --- handlers.py 59c9ba315b35d9ffe2667872266876b511188802 +++ handlers.py fa13ff0c027a94aeec104c8b07f63d605ad78db4 @@ -17,9 +17,8 @@ from fdo import sharedmimeinfo, iconthem # FreeDesktop.org share mime info and icon theme specification from fdo import sharedmimeinfo, icontheme # Other bits of ViewMTN -import mtn, common, syntax, release +import mtn, common, syntax, release, branchdiv from links import link, dynamic_join, static_join -from branchdiv import BranchDivisions from ancestry import ancestry_graph from render import * hq = cgi.escape @@ -38,30 +37,15 @@ else: raise e else: mimeicon = None + # Renderer, sort out template inheritance, etc, etc. renderer = Renderer() # Figure out branch divisions -divisions = BranchDivisions () +divisions = branchdiv.BranchDivisions () -class ComparisonRev: - def __init__(self, ops, revision): - self.revision = revision - self.certs = list(ops.certs(self.revision)) - self.date = None - for cert in self.certs: - if cert[4] == 'name' and cert[5] == 'date': - self.date = common.parse_timecert(cert[7]) - def __repr__(self): - return "ComparisonRev <%s>" % (repr(self.revision)) - def __cmp__(self, other): - # irritating edge-case, heapq compares us to empty string if - # there's only one thing in the list - if not other: return 1 - return cmp(other.date, self.date) - class Index(object): - def GET(self, ops): - branches = list(ops.branches ()) + def GET(self, ctxt): + branches = list(ctxt.ops.branches ()) divisions.calculate_divisions (branches) def division_iter(): bitter = iter(branches) @@ -99,9 +83,9 @@ class Tags(object): renderer.render('about.html', page_title="About") class Tags(object): - def GET(self, ops, restrict_branch=None): + def GET(self, ctxt, restrict_branch=None): # otherwise we couldn't use automate again.. - tags = list(ops.tags()) + tags = list(ctxt.ops.tags()) if restrict_branch != None: restrict_branch = mtn.Branch(restrict_branch) def tag_in(tag): @@ -116,7 +100,7 @@ class Tags(object): tags.sort(lambda t1, t2: cmp(t1.name, t2.name)) def revision_ago(rev): rv = "" - for cert in ops.certs(rev): + for cert in ctxt.ops.certs(rev): if cert[4] == 'name' and cert[5] == 'date': revdate = common.parse_timecert(cert[7]) rv = common.ago(revdate) @@ -128,6 +112,24 @@ class Changes(object): renderer.render('help.html', page_title="Help") class Changes(object): + class ComparisonRev: + def __init__(self, ops, revision): + self.revision = revision + self.certs = list(ops.certs(self.revision)) + self.date = None + for cert in self.certs: + if cert[4] == 'name' and cert[5] == 'date': + self.date = common.parse_timecert(cert[7]) + def __repr__(self): + return "ComparisonRev <%s>" % (repr(self.revision)) + def __cmp__(self, other): + # irritating edge-case, heapq compares us to empty string if + # there's only one thing in the list + if not other: + return 1 + return cmp(other.date, self.date) + + def __get_last_changes(self, ops, start_from, parent_func, selection_func, n): """returns at least n revisions that are parents of the revisions in start_from, ordered by time (descending). selection_func is called for each revision, and @@ -146,7 +148,7 @@ class Changes(object): result = [] revq = [] for rev in start_from: - heapq.heappush(revq, ComparisonRev(ops, rev)) + heapq.heappush(revq, Changes.ComparisonRev(ops, rev)) while len(result) < n: # print >>sys.stderr, "start_revq state:", map(lambda x: (x.revision, x.date), revq) # update based on the last result we output @@ -155,7 +157,7 @@ class Changes(object): for parent_rev in parents: if parent_rev == None: continue - heapq.heappush(revq, ComparisonRev(ops, parent_rev)) + heapq.heappush(revq, Changes.ComparisonRev(ops, parent_rev)) # try and find something we haven't already output in the heap last_result = None @@ -245,10 +247,10 @@ class Changes(object): to_change) return changed, new_starting_point - def Branch_GET(self, ops, branch, from_change, to_change, template_name): + def Branch_GET(self, ctxt, branch, from_change, to_change, template_name): branch = mtn.Branch(branch) from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change) - changed, new_starting_point = self.branch_get_last_changes(ops, branch, from_change, to_change) + changed, new_starting_point = self.branch_get_last_changes(ctxt.ops, branch, from_change, to_change) changed = changed[from_change:to_change] if len(changed) != to_change - from_change: next_from, next_to = None, None @@ -263,7 +265,7 @@ class Changes(object): previous_to=previous_to, next_from=next_from, next_to=next_to, - display_revs=self.for_template(ops, changed)) + display_revs=self.for_template(ctxt.ops, changed)) def file_get_last_changes(self, ops, from_change, to_change, revision, path): def content_changed_fn(start_revision, start_path, in_revision, pathinfo): @@ -291,9 +293,9 @@ class Changes(object): to_change) return changed, new_starting_point, pathinfo - def File_GET(self, ops, from_change, to_change, revision, path, template_name): + def File_GET(self, ctxt, from_change, to_change, revision, path, template_name): from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change) - changed, new_starting_point, pathinfo = self.file_get_last_changes(ops, from_change, to_change, revision, path) + changed, new_starting_point, pathinfo = self.file_get_last_changes(ctxt.ops, from_change, to_change, revision, path) changed = changed[from_change:to_change] if len(changed) != to_change - from_change: next_from, next_to = None, None @@ -309,15 +311,15 @@ class Changes(object): previous_to=previous_to, next_from=next_from, next_to=next_to, - display_revs=self.for_template(ops, changed, pathinfo=pathinfo, constrain_diff_to=revision)) + display_revs=self.for_template(ctxt.ops, changed, pathinfo=pathinfo, constrain_diff_to=revision)) class HTMLBranchChanges(Changes): - def GET(self, ops, branch, from_change, to_change): - Changes.Branch_GET(self, ops, branch, from_change, to_change, "branchchanges.html") + def GET(self, ctxt, branch, from_change, to_change): + Changes.Branch_GET(self, ctxt, branch, from_change, to_change, "branchchanges.html") class RSSBranchChanges(Changes): - def GET(self, ops, branch, from_change, to_change): - Changes.Branch_GET(self, ops, branch, from_change, to_change, "branchchangesrss.html") + def GET(self, ctxt, branch, from_change, to_change): + Changes.Branch_GET(self, ctxt, branch, from_change, to_change, "branchchangesrss.html") class RevisionPage(object): def get_fileid(self, ops, revision, filename): @@ -344,27 +346,27 @@ class RevisionFileChanges(Changes, Revis return rv class RevisionFileChanges(Changes, RevisionPage): - def GET(self, ops, from_change, to_change, revision, path): + def GET(self, ctxt, from_change, to_change, revision, path): revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() - Changes.File_GET(self, ops, from_change, to_change, revision, path, "revisionfilechanges.html") + Changes.File_GET(self, ctxt, from_change, to_change, revision, path, "revisionfilechanges.html") class RevisionFileChangesRSS(Changes, RevisionPage): - def GET(self, ops, from_change, to_change, revision, path): + def GET(self, ctxt, from_change, to_change, revision, path): revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() - Changes.File_GET(self, ops, from_change, to_change, revision, path, "revisionfilechangesrss.html") + Changes.File_GET(self, ctxt, from_change, to_change, revision, path, "revisionfilechangesrss.html") class RevisionInfo(RevisionPage): - def GET(self, ops, revision): + def GET(self, ctxt, revision): revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() - certs = ops.certs(revision) - revisions = ops.get_revision(revision) - output_png, output_imagemap = ancestry_graph(ops, revision) + certs = ctxt.ops.certs(revision) + revisions = ctxt.ops.get_revision(revision) + output_png, output_imagemap = ancestry_graph(ctxt.ops, revision) if os.access(output_imagemap, os.R_OK): imagemap = open(output_imagemap).read().replace('\\n', ' by ') imageuri = dynamic_join('revision/graph/' + revision) @@ -379,18 +381,18 @@ class RevisionDiff(RevisionPage): revisions=revisions_for_template(revision, revisions)) class RevisionDiff(RevisionPage): - def GET(self, ops, revision_from, revision_to, filename=None): + def GET(self, ctxt, revision_from, revision_to, filename=None): revision_from = mtn.Revision(revision_from) revision_to = mtn.Revision(revision_to) - if not self.exists(ops, revision_from): + if not self.exists(ctxt.ops, revision_from): return web.notfound() - if not self.exists(ops, revision_to): + if not self.exists(ctxt.ops, revision_to): return web.notfound() if filename != None: files = [filename] else: files = [] - diff = ops.diff(revision_from, revision_to, files) + diff = ctxt.ops.diff(revision_from, revision_to, files) diff_obj = Diff(revision_from, revision_to, files) renderer.render('revisiondiff.html', page_title="Diff from %s to %s" % (revision_from.abbrev(), revision_to.abbrev()), @@ -402,33 +404,33 @@ class RevisionRawDiff(RevisionPage): files=files) class RevisionRawDiff(RevisionPage): - def GET(self, ops, revision_from, revision_to, filename=None): + def GET(self, ctxt, revision_from, revision_to, filename=None): revision_from = mtn.Revision(revision_from) revision_to = mtn.Revision(revision_to) - if not self.exists(ops, revision_from): + if not self.exists(ctxt.ops, revision_from): return web.notfound() - if not self.exists(ops, revision_to): + if not self.exists(ctxt.ops, revision_to): return web.notfound() if filename != None: files = [filename] else: files = [] - diff = ops.diff(revision_from, revision_to, files) + diff = ctxt.ops.diff(revision_from, revision_to, files) web.header('Content-Type', 'text/x-diff') for line in diff: sys.stdout.write (line) sys.stdout.flush() class RevisionFile(RevisionPage): - def GET(self, ops, revision, filename): + def GET(self, ctxt, revision, filename): revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() language = filename.rsplit('.', 1)[-1] - fileid = RevisionPage.get_fileid(self, ops, revision, filename) + fileid = RevisionPage.get_fileid(self, ctxt.ops, revision, filename) if not fileid: return web.notfound() - contents = ops.get_file(fileid) + contents = ctxt.ops.get_file(fileid) mimetype = mimehelp.lookup(filename, '') mime_to_template = { 'image/jpeg' : 'revisionfileimg.html', @@ -453,15 +455,15 @@ class RevisionDownloadFile(RevisionPage) contents=syntax.highlight(contents, language)) class RevisionDownloadFile(RevisionPage): - def GET(self, ops, revision, filename): + def GET(self, ctxt, revision, filename): web.header('Content-Disposition', 'attachment; filename=%s' % filename) revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() - fileid = RevisionPage.get_fileid(self, ops, revision, filename) + fileid = RevisionPage.get_fileid(self, ctxt.ops, revision, filename) if not fileid: return web.notfound() - for idx, data in enumerate(ops.get_file(fileid)): + for idx, data in enumerate(ctxt.ops.get_file(fileid)): if idx == 0: mimetype = mimehelp.lookup(filename, data) web.header('Content-Type', mimetype) @@ -469,16 +471,16 @@ class RevisionTar(RevisionPage): sys.stdout.flush() class RevisionTar(RevisionPage): - def GET(self, ops, revision): + def GET(self, ctxt, revision): # we'll output in the USTAR tar format; documentation taken from: # http://en.wikipedia.org/wiki/Tar_%28file_format%29 revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() filename = "%s.tar" % revision web.header('Content-Disposition', 'attachment; filename=%s' % filename) web.header('Content-Type', 'application/x-tar') - manifest = [stanza for stanza in ops.get_manifest_of(revision)] + manifest = [stanza for stanza in ctxt.ops.get_manifest_of(revision)] # for now; we might want to come up with something more interesting; # maybe the branch name (but there might be multiple branches?) basedirname = revision @@ -492,7 +494,7 @@ class RevisionTar(RevisionPage): filename, fileid = stanza[1], stanza[3] filecontents = cStringIO.StringIO() filesize = 0 - for data in ops.get_file(fileid): + for data in ctxt.ops.get_file(fileid): filesize += len(data) filecontents.write(data) ti = tarfile.TarInfo() @@ -500,11 +502,11 @@ class RevisionTar(RevisionPage): ti.mode, ti.type = 00600, tarfile.REGTYPE ti.uid = ti.gid = 0 # determine the most recent of the content marks - content_marks = [t[1] for t in ops.get_content_changed(revision, filename)] + content_marks = [t[1] for t in ctxt.ops.get_content_changed(revision, filename)] if len(content_marks) > 0: # just pick one to make this faster content_mark = content_marks[0] - since_epoch = timecert(ops.certs(content_mark)) - datetime.datetime.fromtimestamp(0) + since_epoch = timecert(ctxt.ops.certs(content_mark)) - datetime.datetime.fromtimestamp(0) ti.mtime = since_epoch.days * 24 * 60 * 60 + since_epoch.seconds else: ti.mtime = 0 @@ -513,12 +515,12 @@ class RevisionBrowse(RevisionPage): tarobj.addfile(ti, filecontents) class RevisionBrowse(RevisionPage): - def GET(self, ops, revision, path): + def GET(self, ctxt, revision, path): revision = mtn.Revision(revision) - if not self.exists(ops, revision): + if not self.exists(ctxt.ops, revision): return web.notfound() - branches = RevisionPage.branches_for_rev(self, ops, revision) - revisions = ops.get_revision(revision) + branches = RevisionPage.branches_for_rev(self, ctxt.ops, revision) + revisions = ctxt.ops.get_revision(revision) def components(path): # NB: mtn internally uses '/' for paths, so we shouldn't use os.path.join() @@ -553,7 +555,7 @@ class RevisionBrowse(RevisionPage): page_title += " of %s %s" % (branch_plural, ', '.join(branches)) def cut_manifest_to_subdir(): - manifest = list(ops.get_manifest_of(revision)) + manifest = list(ctxt.ops.get_manifest_of(revision)) in_the_dir = False for stanza in manifest: stanza_type = stanza[0] @@ -593,7 +595,7 @@ class RevisionBrowse(RevisionPage): if not certs.has_key(revision): # subtle bug slipped in here; ops.cert() is a generator # so we can't just store it in a cache! - certs[revision] = list(ops.certs(revision)) + certs[revision] = list(ctxt.ops.certs(revision)) return certs[revision] def _get_certinfo(revision): @@ -619,7 +621,7 @@ class RevisionBrowse(RevisionPage): for stanza_type, this_path in entry_iter: # determine the most recent of the content marks - content_marks = [t[1] for t in ops.get_content_changed(revision, this_path)] + content_marks = [t[1] for t in ctxt.ops.get_content_changed(revision, this_path)] for mark in content_marks: get_cert(mark) if len(content_marks): @@ -664,8 +666,8 @@ class RevisionGraph(object): entries=info_for_manifest(cut_manifest_to_subdir())) class RevisionGraph(object): - def GET(self, ops, revision): - output_png, output_imagemap = ancestry_graph(ops, revision) + def GET(self, ctxt, revision): + output_png, output_imagemap = ancestry_graph(ctxt.ops, revision) if os.access(output_png, os.R_OK): web.header('Content-Type', 'image/png') sys.stdout.write(open(output_png).read()) @@ -683,13 +685,13 @@ class Json(object): revdate = common.parse_timecert(cert[7]) rv['ago'] = common.ago(revdate) - def BranchLink(self, ops, for_branch): + def BranchLink(self, ctxt, for_branch): rv = { 'type' : 'branch', 'branch' : for_branch, } branch = mtn.Branch(for_branch) - changes, new_starting_point = Changes().branch_get_last_changes(ops, branch, 0, 1) + changes, new_starting_point = Changes().branch_get_last_changes(ctxt.ops, branch, 0, 1) if len(changes) < 1: return web.notfound() if not changes: @@ -699,34 +701,34 @@ class Json(object): self.fill_from_certs(rv, certs) return rv - def RevisionLink(self, ops, revision_id): + def RevisionLink(self, ctxt, revision_id): rv = { 'type' : 'revision', 'revision_id' : revision_id, } rev = mtn.Revision(revision_id) - certs = ops.certs(rev) + certs = ctxt.ops.certs(rev) self.fill_from_certs(rv, certs) return rv - def GET(self, ops, method, encoded_args): + def GET(self, ctxt, method, encoded_args): writer = json.JsonWriter() if not encoded_args.startswith('js_'): return web.notfound() args = json.read(binascii.unhexlify((encoded_args[3:]))) if hasattr(self, method): - rv = getattr(self, method)(ops, *args) + rv = getattr(self, method)(ctxt, *args) else: return web.notfound() print writer.write(rv) class BranchHead(object): - def GET(self, ops, head_method, proxy_to, branch, extra_path): + def GET(self, ctxt, head_method, proxy_to, branch, extra_path): branch = mtn.Branch(branch) valid = ('browse', 'file', 'downloadfile', 'info', 'tar', 'graph') if not proxy_to in valid: return web.notfound() - heads = [head for head in ops.heads(branch.name)] + heads = [head for head in ctxt.ops.heads(branch.name)] if len(heads) == 0: return web.notfound() def proxyurl(revision): @@ -740,7 +742,7 @@ class BranchHead(object): head_links = [] for revision in heads: author, date = '', '' - for cert in ops.certs(revision): + for cert in ctxt.ops.certs(revision): if cert[4] == 'name' and cert[5] == 'date': date = cert[7] elif cert[4] == 'name' and cert[5] == 'author': ============================================================ --- render.py 032026fe3b80f63a098218b7c77c90c4d7a2e518 +++ render.py c1223ddbb5396f63101b981838cd8296d0cc82ba @@ -8,7 +8,7 @@ import cgi, urllib, web # PURPOSE. import cgi, urllib, web -import mtn, release, config +import mtn, release, config, common from links import link, dynamic_join, static_join hq = cgi.escape ============================================================ --- viewmtn.py a3a3446ad7ea43e0ff0ceb6daa04dac0b7719bae +++ viewmtn.py 6629b45724cc639d2555c41248ccadf35838b83e @@ -11,21 +11,24 @@ from itertools import izip, chain, repea import os, sys from itertools import izip, chain, repeat -# web.py import web -# Other bits of ViewMTN import mtn, handlers -# The user configuration file +from urls import common_urls, perdb_urls import config -debug = web.debug - # purloined from: http://docs.python.org/lib/itertools-recipes.html def grouper(n, iterable, padvalue=None): "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')" return izip(*[chain(iterable, repeat(padvalue, n-1))]*n) -class OperationsFactory(object): +class RequestContext(object): + def __init__ (self, dbname, ops): + self.dbname, self.ops = dbname, ops + # make sure that any unread automate output is flushed away + if not ops is None: + ops.per_request() + +class RequestContextFactory(object): def __init__ (self): # has the user specified a dbfiles hash? if so, use it self.ops_instances = {} @@ -39,64 +42,14 @@ class OperationsFactory(object): self.ops_instances["legacy"] = mtn.Operations([config.monotone, config.dbfile]) self.default = "legacy" - def get_ops(self, name): - ops = None + def __getitem__(self, name): if name is None: ops = self.ops_instances.get (self.default, None) else: name = name.rstrip('/') ops = self.ops_instances.get (name, None) - # technically it'd be better to do this before serving the - # request, however this is about the only per-request - # spot that runs for every handler.. - if not ops is None: - ops.per_request() - return ops + return RequestContext(name, ops) -op_fact = OperationsFactory() - -common_urls = ( -# these don't care about multiple databases specified via the URL - r'about', 'About', - r'help', 'Help', - r'robots.txt', 'RobotsTxt', ## FIXME needs o exclude per-db paths - r'mimeicon/([A-Za-z0-9][a-z0-9\-\+\.]*)/([A-Za-z0-9][a-z0-9\-\+\.]*)', 'MimeIcon', -) - -perdb_urls = ( - r'', 'Index', - r'tags', 'Tags', - r'json/([A-Za-z]+)/(.*)', 'Json', - - r'([a-zA-Z]/)?revision/browse/('+mtn.revision_re+')/(.*)', 'RevisionBrowse', - r'revision/browse/('+mtn.revision_re+')()', 'RevisionBrowse', - r'revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')', 'RevisionDiff', - r'revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')'+'/(.*)', 'RevisionDiff', - r'revision/rawdiff/('+mtn.revision_re+')/with/('+mtn.revision_re+')', 'RevisionRawDiff', - r'revision/rawdiff/('+mtn.revision_re+')/with/('+mtn.revision_re+')'+'/(.*)', 'RevisionRawDiff', - r'revision/file/('+mtn.revision_re+')/(.*)', 'RevisionFile', - r'revision/filechanges/()()('+mtn.revision_re+')/(.*)', 'RevisionFileChanges', - r'revision/filechanges/from/(\d+)/to/(\d+)/('+mtn.revision_re+')/(.*)', 'RevisionFileChanges', - r'revision/filechanges/rss/()()('+mtn.revision_re+')/(.*)', 'RevisionFileChangesRSS', - r'revision/filechanges/rss/from/(\d+)/to/(\d+)/('+mtn.revision_re+')/(.*)', 'RevisionFileChangesRSS', - r'revision/downloadfile/('+mtn.revision_re+')/(.*)', 'RevisionDownloadFile', - r'revision/info/('+mtn.revision_re+')', 'RevisionInfo', - r'revision/tar/('+mtn.revision_re+')', 'RevisionTar', - r'revision/graph/('+mtn.revision_re+')', 'RevisionGraph', - - r'branch/changes/(.*)/from/(\d+)/to/(\d+)', 'HTMLBranchChanges', - r'branch/changes/([^/]+)()()', 'HTMLBranchChanges', - r'branch/changes/(.*)/from/(\d+)/to/(\d+)/rss', 'RSSBranchChanges', - r'branch/changes/([^/]+)()()/rss', 'RSSBranchChanges', - r'branch/tags/([^/]+)', 'Tags', - - # let's make it possible to access any function on the head revision - # through this proxy method; it'll return a redirect to the head revision - # with the specified function - r'branch/(head)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', - r'branch/(anyhead)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', -) - def runfcgi_apache(func): web.wsgi.runfcgi(func, None) @@ -112,7 +65,9 @@ if __name__ == '__main__': def assemble_urls(): fvars = {} urls = () - + + factory = RequestContextFactory() + for url, fn in grouper (2, common_urls): url = r'^/' + url if hasattr(handlers, fn): @@ -125,7 +80,7 @@ if __name__ == '__main__': class PerDBClosure(object): def GET (self, *args, **kwargs): db, other_args = args[0], args[1:] - ops = op_fact.get_ops (db) + ops = factory[db] if ops is None: return web.notfound() return handler.GET (ops, *other_args, **kwargs)