Mercurial > piecrust2
diff piecrust/routing.py @ 711:ab5c6a8ae90a
bake: Replace hard-coded taxonomy support with "generator" system.
* Taxonomies are now implemented one or more `TaxonomyGenerator`s.
* A `BlogArchivesGenerator` stub is there but non-functional.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 26 May 2016 19:52:47 -0700 |
parents | 2f780b191541 |
children | 606f6d57b5df |
line wrap: on
line diff
--- a/piecrust/routing.py Thu May 26 19:46:28 2016 -0700 +++ b/piecrust/routing.py Thu May 26 19:52:47 2016 -0700 @@ -3,7 +3,7 @@ import copy import logging import urllib.parse -import unidecode +from werkzeug.utils import cached_property logger = logging.getLogger(__name__) @@ -11,9 +11,8 @@ route_re = re.compile(r'%((?P<qual>path):)?(?P<name>\w+)%') route_esc_re = re.compile(r'\\%((?P<qual>path)\\:)?(?P<name>\w+)\\%') -template_func_re = re.compile(r'^(?P<name>\w+)\((?P<first_arg>\w+)' - r'(?P<other_args>.*)\)\s*$') -template_func_arg_re = re.compile(r',\s*(?P<arg>\w+)') +template_func_re = re.compile(r'^(?P<name>\w+)\((?P<args>.*)\)\s*$') +template_func_arg_re = re.compile(r'(?P<arg>\+?\w+)') ugly_url_cleaner = re.compile(r'\.html$') @@ -21,6 +20,10 @@ pass +class InvalidRouteError(Exception): + pass + + def create_route_metadata(page): route_metadata = copy.deepcopy(page.source_metadata) route_metadata.update(page.getRouteMetadata()) @@ -38,53 +41,23 @@ raise NotImplementedError() -SLUGIFY_ENCODE = 1 -SLUGIFY_TRANSLITERATE = 2 -SLUGIFY_LOWERCASE = 4 -SLUGIFY_DOT_TO_DASH = 8 -SLUGIFY_SPACE_TO_DASH = 16 - - -re_first_dot_to_dash = re.compile(r'^\.+') -re_dot_to_dash = re.compile(r'\.+') -re_space_to_dash = re.compile(r'\s+') - - -def _parse_slugify_mode(value): - mapping = { - 'encode': SLUGIFY_ENCODE, - 'transliterate': SLUGIFY_TRANSLITERATE, - 'lowercase': SLUGIFY_LOWERCASE, - 'dot_to_dash': SLUGIFY_DOT_TO_DASH, - 'space_to_dash': SLUGIFY_SPACE_TO_DASH} - mode = 0 - for v in value.split(','): - f = mapping.get(v.strip()) - if f is None: - if v == 'iconv': - raise Exception("'iconv' is not supported as a slugify mode " - "in PieCrust2. Use 'transliterate'.") - raise Exception("Unknown slugify flag: %s" % v) - mode |= f - return mode +ROUTE_TYPE_SOURCE = 0 +ROUTE_TYPE_GENERATOR = 1 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. + sources and generators. """ def __init__(self, app, cfg): self.app = app - self.source_name = cfg['source'] - self.taxonomy_name = cfg.get('taxonomy') - self.taxonomy_term_sep = cfg.get('term_separator', '/') - - sm = cfg.get('slugify_mode') - if not sm: - sm = app.config.get('site/slugify_mode', 'encode') - self.slugify_mode = _parse_slugify_mode(sm) + self.source_name = cfg.get('source') + self.generator_name = cfg.get('generator') + if not self.source_name and not self.generator_name: + raise InvalidRouteError( + "Both `source` and `generator` are specified.") self.pretty_urls = app.config.get('site/pretty_urls') self.trailing_slash = app.config.get('site/trailing_slash') @@ -127,23 +100,45 @@ self.template_func = None self.template_func_name = None self.template_func_args = [] + self.template_func_vararg = None self._createTemplateFunc(cfg.get('func')) @property - def is_taxonomy_route(self): - return self.taxonomy_name is not None + def route_type(self): + if self.source_name: + return ROUTE_TYPE_SOURCE + elif self.generator_name: + return ROUTE_TYPE_GENERATOR + else: + raise InvalidRouteError() @property + def is_source_route(self): + return self.route_type == ROUTE_TYPE_SOURCE + + @property + def is_generator_route(self): + return self.route_type == ROUTE_TYPE_GENERATOR + + @cached_property def source(self): + if not self.is_source_route: + return InvalidRouteError("This is not a source route.") for src in self.app.sources: if src.name == self.source_name: return src - raise Exception("Can't find source '%s' for route '%'." % ( + raise Exception("Can't find source '%s' for route '%s'." % ( self.source_name, self.uri)) - @property - def source_realm(self): - return self.source.realm + @cached_property + def generator(self): + if not self.is_generator_route: + return InvalidRouteError("This is not a generator route.") + for gen in self.app.generators: + if gen.name == self.generator_name: + return gen + raise Exception("Can't find generator '%s' for route '%s'." % ( + self.generator_name, self.uri)) def matchesMetadata(self, route_metadata): return self.required_route_metadata.issubset(route_metadata.keys()) @@ -234,38 +229,6 @@ return uri - def getTaxonomyTerms(self, route_metadata): - if not self.is_taxonomy_route: - raise Exception("This route isn't a taxonomy route.") - - tax = self.app.getTaxonomy(self.taxonomy_name) - all_values = route_metadata.get(tax.term_name) - if all_values is None: - raise Exception("'%s' values couldn't be found in route metadata" % - tax.term_name) - - if self.taxonomy_term_sep in all_values: - return tuple(all_values.split(self.taxonomy_term_sep)) - return all_values - - def slugifyTaxonomyTerm(self, term): - if isinstance(term, tuple): - return self.taxonomy_term_sep.join( - map(self._slugifyOne, term)) - return self._slugifyOne(term) - - def _slugifyOne(self, term): - if self.slugify_mode & SLUGIFY_TRANSLITERATE: - term = unidecode.unidecode(term) - if self.slugify_mode & SLUGIFY_LOWERCASE: - term = term.lower() - if self.slugify_mode & SLUGIFY_DOT_TO_DASH: - term = re_first_dot_to_dash.sub('', term) - term = re_dot_to_dash.sub('-', term) - if self.slugify_mode & SLUGIFY_SPACE_TO_DASH: - term = re_space_to_dash.sub('-', term) - return term - def _uriFormatRepl(self, m): name = m.group('name') #TODO: fix this hard-coded shit @@ -280,7 +243,7 @@ def _uriPatternRepl(self, m): name = m.group('name') qualifier = m.group('qual') - if qualifier == 'path' or self.taxonomy_name: + if qualifier == 'path': return r'(?P<%s>[^\?]*)' % name return r'(?P<%s>[^/\?]+)' % name @@ -302,66 +265,54 @@ (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') + self.template_func_args = [] + arg_list = m.group('args') if arg_list: - self.template_func_args += template_func_arg_re.findall(arg_list) + self.template_func_args = template_func_arg_re.findall(arg_list) + for i in range(len(self.template_func_args) - 1): + if self.template_func_args[i][0] == '+': + raise Exception("Only the last route parameter can be a " + "variable argument (prefixed with `+`)") - if self.taxonomy_name: - # 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] + if (self.template_func_args and + self.template_func_args[-1][0] == '+'): + self.template_func_vararg = self.template_func_args[-1][1:] - # We need to register this use of a taxonomy term. - if len(values) == 1: - registered_values = str(values[0]) - else: - registered_values = tuple([str(v) for v in values]) - eis = self.app.env.exec_info_stack - cpi = eis.current_page_info.render_ctx.current_pass_info - if cpi: - cpi.used_taxonomy_terms.add( - (self.source_name, self.taxonomy_name, - registered_values)) - - str_values = self.slugifyTaxonomyTerm(registered_values) - term_name = self.template_func_args[0] - metadata = {term_name: str_values} + def template_func(*args): + is_variable = (self.template_func_vararg is not None) + if not is_variable and 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))) + elif is_variable and len(args) < len(self.template_func_args): + raise Exception( + "Route function '%s' expected at least %d arguments, " + "got %d." % + (func_def, len(self.template_func_args), + len(args))) - return self.getUri(metadata) + metadata = {} + non_var_args = list(self.template_func_args) + if is_variable: + del non_var_args[-1] - 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): - #TODO: fix this hard-coded shit. - if arg_name in ['year', 'month', 'day']: - arg_val = int(arg_val) - metadata[arg_name] = arg_val - return self.getUri(metadata) + for arg_name, arg_val in zip(non_var_args, args): + #TODO: fix this hard-coded shit. + if arg_name in ['year', 'month', 'day']: + arg_val = int(arg_val) + metadata[arg_name] = arg_val + + if is_variable: + metadata[self.template_func_vararg] = [] + for i in range(len(non_var_args), len(args)): + metadata[self.template_func_vararg].append(args[i]) + + if self.is_generator_route: + self.generator.onRouteFunctionUsed(self, metadata) + + return self.getUri(metadata) self.template_func = template_func