# # # patch "viewmtn.py" # from [657d61af6a6a853bd10a1e38aaf5eb7f53588f9f] # to [937a0bd5668fef8dee7cb44f7c1584d7052812f3] # ============================================================ --- viewmtn.py 657d61af6a6a853bd10a1e38aaf5eb7f53588f9f +++ viewmtn.py 937a0bd5668fef8dee7cb44f7c1584d7052812f3 @@ -34,10 +34,16 @@ import binascii hq = cgi.escape import heapq import binascii +from itertools import izip, chain, repeat import web 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) + # /about.psp -> /about # /branch.psp -> /branch/{branch}/ @@ -359,7 +365,31 @@ class Renderer: terms.update(kwargs) web.render(template, terms) +class OperationsFactory: + def __init__ (self): + # has the user specified a dbfiles hash? if so, use it + self.ops_instances = {} + self.default = None + if hasattr (config, "dbfiles"): + for name, dbfile in config.dbfiles: + self.ops_instances[name] = mtn.Operations([config.monotone, dbfile]) + if hasattr (config, "defaultdb"): + self.default = config.defaultdb + else: + self.ops_instances["legacy"] = mtn.Operations([config.monotone, config.dbfile]) + self.default = "legacy" + + def get_ops(self, name): + if not (name is None): + return self.ops.get (name, None) + else: + return self.ops.get (self.default, None) + +# +# TODO: try and wrap these globals up in a single global object +# renderer = Renderer() +op_fact = OperationsFactory() ops = mtn.Operations([config.monotone, config.dbfile]) mimehelp = sharedmimeinfo.LookupHelper(getattr(config, "mime_map", None)) if config.icon_theme: @@ -1266,43 +1296,46 @@ class RobotsTxt: for access_method in ['/revision/', '/branch/head/', '/branch/anyhead/', '/branch/changes/from/', '/json/', '/mimeicon/']: print "Disallow:", access_method + revision_page -urls = ( - r'/', 'Index', #done - r'/about', 'About', #done - r'/tags', 'Tags', #done - r'/help', 'Help', #done - r'/json/([A-Za-z]+)/(.*)', 'Json', +common_urls = ( +# these don't care about multiple databases specified via the URL + r'', 'Index', + 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', +) - r'/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/rawdiff/('+mtn.revision_re+')/with/('+mtn.revision_re+')', 'RevisionRawDiff', - r'/revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')'+'/(.*)', 'RevisionDiff', - 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', +perdb_urls = ( + r'tags', 'Tags', + r'json/([A-Za-z]+)/(.*)', 'Json', - 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', + 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', - - r'/robots.txt', 'RobotsTxt', - r'/mimeicon/([A-Za-z0-9][a-z0-9\-\+\.]*)/([A-Za-z0-9][a-z0-9\-\+\.]*)', 'MimeIcon', + r'branch/(head)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', + r'branch/(anyhead)/([A-Za-z]+)/([^/]+)(.*)', 'BranchHead', ) def runfcgi_apache(func): @@ -1317,7 +1350,32 @@ if __name__ == '__main__': web.wsgi.runwsgi = runfcgi_apache if hasattr(config, "debug") and config.debug: web.webapi.internalerror = web.debugerror - func = lambda : per_request_wrapper(web.webpyfunc(urls, fvars=globals())) + + def assemble_urls(): + fvars = {} + urls = () + for url, fn in grouper (2, common_urls): + url = r'^/' + url + urls += (url, fn) + fvars[fn] = globals()[fn] + + def get_db_closure (fn): + the_cls = globals()[fn]() + class PerDBClosure: + def GET (self, *args, **kwargs): + db, other_args = args[0], args[1:] + the_cls.GET (*other_args, **kwargs) + return PerDBClosure + + for url, fn in grouper (2, perdb_urls): + url = r'^/([A-Za-z]+/)?' + url + urls += (url, fn) + fvars[fn] = get_db_closure (fn) + print "fvars::", fvars['Index'] + print "globals:", globals()['Index'] + return urls, fvars + urls, fvars = assemble_urls() + func = lambda : per_request_wrapper(web.webpyfunc(urls, fvars=fvars)) web.run(func, globals(), web.reloader) ###