Mercurial > piecrust2
diff piecrust/routing.py @ 3:f485ba500df3
Gigantic change to basically make PieCrust 2 vaguely functional.
- Serving works, with debug window.
- Baking works, multi-threading, with dependency handling.
- Various things not implemented yet.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 10 Aug 2014 23:43:16 -0700 |
parents | |
children | ab6e7e0e9d44 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/routing.py Sun Aug 10 23:43:16 2014 -0700 @@ -0,0 +1,177 @@ +import re +import logging + + +logger = logging.getLogger(__name__) + + +route_re = re.compile(r'%((?P<qual>path):)?(?P<name>\w+)%') +template_func_re = re.compile(r'^(?P<name>\w+)\((?P<first_arg>\w+)(?P<other_args>.*)\)\s*$') +template_func_arg_re = re.compile(r',\s*(?P<arg>\w+)') + + +class Route(object): + """ Information about a route for a PieCrust application. + Each route defines the "shape" of an URL and how it maps to + sources and taxonomies. + """ + def __init__(self, app, cfg): + self.app = app + uri = cfg['url'] + self.uri_root = app.config.get('site/root').rstrip('/') + '/' + self.uri_pattern = uri.lstrip('/') + self.uri_format = route_re.sub(self._uriFormatRepl, self.uri_pattern) + p = route_re.sub(self._uriPatternRepl, self.uri_pattern) + self.uri_re = re.compile(p) + self.source_name = cfg['source'] + self.taxonomy = cfg.get('taxonomy') + self.required_source_metadata = [] + for m in route_re.finditer(uri): + self.required_source_metadata.append(m.group('name')) + self.template_func = None + self.template_func_name = None + self.template_func_args = [] + self._createTemplateFunc(cfg.get('func')) + + @property + def source(self): + for src in self.app.sources: + if src.name == self.source_name: + return src + raise Exception("Can't find source '%s' for route '%'." % ( + self.source_name, self.uri)) + + @property + def source_realm(self): + return self.source.realm + + def isMatch(self, source_metadata): + return True + + def getUri(self, source_metadata): + #TODO: fix this hard-coded shit + for key in ['year', 'month', 'day']: + if key in source_metadata and isinstance(source_metadata[key], str): + source_metadata[key] = int(source_metadata[key]) + return self.uri_root + (self.uri_format % source_metadata) + + def _uriFormatRepl(self, m): + name = m.group('name') + #TODO: fix this hard-coded shit + if name == 'year': + return '%(year)04d' + if name == 'month': + return '%(month)02d' + if name == 'day': + return '%(day)02d' + return '%(' + name + ')s' + + def _uriPatternRepl(self, m): + name = m.group('name') + qualifier = m.group('qual') + if qualifier == 'path': + return r'(?P<%s>.*)' % name + return r'(?P<%s>[^/\?]+)' % name + + def _createTemplateFunc(self, func_def): + if func_def is None: + return + + m = template_func_re.match(func_def) + if m is None: + raise Exception("Template function definition for route '%s' " + "has invalid syntax: %s" % + (self.uri_pattern, func_def)) + + self.template_func_name = m.group('name') + self.template_func_args.append(m.group('first_arg')) + arg_list = m.group('other_args') + if arg_list: + self.template_func_args += template_func_arg_re.findall(arg_list) + + if self.taxonomy: + # This will be a taxonomy route function... this means we can + # have a variable number of parameters, but only one parameter + # definition, which is the value. + if len(self.template_func_args) != 1: + raise Exception("Route '%s' is a taxonomy route and must have " + "only one argument, which is the term value." % + self.uri_pattern) + + def template_func(*args): + if len(args) == 0: + raise Exception( + "Route function '%s' expected at least one " + "argument." % func_def) + + # Term combinations can be passed as an array, or as multiple + # arguments. + values = args + if len(args) == 1 and isinstance(args[0], list): + values = args[0] + + # We need to register this use of a taxonomy term. + if len(values) == 1: + registered_values = values[0] + else: + registered_values = tuple(values) + eis = self.app.env.exec_info_stack + eis.current_page_info.render_ctx.used_taxonomy_terms.add( + (self.source_name, self.taxonomy, registered_values)) + + if len(values) == 1: + str_values = values[0] + else: + str_values = '/'.join(values) + term_name = self.template_func_args[0] + metadata = {term_name: str_values} + + return self.getUri(metadata) + + else: + # Normal route function. + def template_func(*args): + if len(args) != len(self.template_func_args): + raise Exception( + "Route function '%s' expected %d arguments, " + "got %d." % + (func_def, len(self.template_func_args), + len(args))) + metadata = {} + for arg_name, arg_val in zip(self.template_func_args, args): + metadata[arg_name] = arg_val + return self.getUri(metadata) + + self.template_func = template_func + + +class CompositeRouteFunction(object): + def __init__(self): + self._funcs = [] + self._arg_names = None + + def addFunc(self, route): + if self._arg_names is None: + self._arg_names = sorted(route.template_func_args) + + if sorted(route.template_func_args) != self._arg_names: + raise Exception("Cannot merge route function with arguments '%s' " + "with route function with arguments '%s'." % + (route.template_func_args, self._arg_names)) + self._funcs.append((route, route.template_func)) + + def __call__(self, *args, **kwargs): + if len(args) == len(self._arg_names): + f = self._funcs[0][1] + return f(*args, **kwargs) + + if len(args) == len(self._arg_names) + 1: + f_args = args[:-1] + for r, f in self._funcs: + if r.source_name == args[-1]: + return f(f_args, **kwargs) + raise Exception("No such source: %s" % args[-1]) + + raise Exception("Incorrect number of arguments for route function. " + "Expected '%s', got '%s'" % (self._arg_names, args)) +