Mercurial > piecrust2
changeset 334:b034f6f15e22
bake: Several bug taxonomy-related fixes for incorrect incremental bakes.
* Improve how the baker processes taxonomy terms and figures out what needs
to be re-baked or not.
* Create bake entries for clean taxnomy terms so they're not deleted by an
incremental bake.
* Add more information to bake records.
* Slugify taxonomy terms is now done by the route in one place.
* Fix a bug where the cache key for invalidating rendered segments was not
computed the same way as when the caching was done.
* Fix how term combinations are passed around, rendered, printed, parsed, etc.
(TODO: more word needed in the routing functions)
* Expose to the template whether a taxonomy term is a combination or not.
* Display term combinations better in the built-in theme.
* Rename `route.taxonomy` to `route.taxonomy_name` to prevent confusion.
* Add options to show bake records for previous bakes.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 03 Apr 2015 10:59:50 -0700 |
parents | 91b07f9efdc1 |
children | 8511137d1b62 |
files | piecrust/app.py piecrust/baking/baker.py piecrust/baking/records.py piecrust/baking/single.py piecrust/commands/builtin/baking.py piecrust/commands/builtin/info.py piecrust/rendering.py piecrust/resources/theme/pages/_tag.html piecrust/routing.py piecrust/serving.py |
diffstat | 10 files changed, 237 insertions(+), 128 deletions(-) [+] |
line wrap: on
line diff
--- a/piecrust/app.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/app.py Fri Apr 03 10:59:50 2015 -0700 @@ -531,7 +531,7 @@ def getRoutes(self, source_name, skip_taxonomies=False): for route in self.routes: if route.source_name == source_name: - if not skip_taxonomies or route.taxonomy is None: + if not skip_taxonomies or route.taxonomy_name is None: yield route def getRoute(self, source_name, source_metadata): @@ -542,7 +542,7 @@ def getTaxonomyRoute(self, tax_name, source_name): for route in self.routes: - if route.taxonomy == tax_name and route.source_name == source_name: + if route.taxonomy_name == tax_name and route.source_name == source_name: return route return None
--- a/piecrust/baking/baker.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/baking/baker.py Fri Apr 03 10:59:50 2015 -0700 @@ -175,7 +175,8 @@ (source.name, fac.ref_spec)) continue - entry = BakeRecordPageEntry(fac) + entry = BakeRecordPageEntry(fac.source.name, fac.rel_path, + fac.path) record.addEntry(entry) route = self.app.getRoute(source.name, fac.metadata) @@ -193,8 +194,22 @@ def _bakeTaxonomies(self, record): logger.debug("Baking taxonomies") + class _TaxonomyTermsInfo(object): + def __init__(self): + self.dirty_terms = set() + self.all_terms = set() + + def __str__(self): + return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) + + def __repr__(self): + return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) + # Let's see all the taxonomy terms for which we must bake a # listing page... first, pre-populate our big map of used terms. + # For each source name, we have a list of taxonomies, and for each + # taxonomies, a list of terms, some being 'dirty', some used last + # time, etc. buckets = {} tax_names = [t.name for t in self.app.taxonomies] source_names = [s.name for s in self.app.sources] @@ -202,56 +217,78 @@ source_taxonomies = {} buckets[sn] = source_taxonomies for tn in tax_names: - source_taxonomies[tn] = set() + source_taxonomies[tn] = _TaxonomyTermsInfo() # Now see which ones are 'dirty' based on our bake record. logger.debug("Gathering dirty taxonomy terms") for prev_entry, cur_entry in record.transitions.values(): for tax in self.app.taxonomies: - changed_terms = None # Re-bake all taxonomy pages that include new or changed # pages. - if (not prev_entry and cur_entry and - cur_entry.was_baked_successfully): - changed_terms = cur_entry.config.get(tax.setting_name) - elif (prev_entry and cur_entry and - cur_entry.was_baked_successfully): - changed_terms = [] - prev_terms = prev_entry.config.get(tax.setting_name) + if cur_entry and cur_entry.was_baked_successfully: + if prev_entry and prev_entry.was_baked_successfully: + # Entry was re-baked this time. Mark as dirty both the + # old and new terms. + changed_terms = [] + prev_terms = prev_entry.config.get(tax.setting_name) + cur_terms = cur_entry.config.get(tax.setting_name) + if tax.is_multiple: + if prev_terms is not None: + changed_terms += prev_terms + if cur_terms is not None: + changed_terms += cur_terms + else: + if prev_terms is not None: + changed_terms.append(prev_terms) + if cur_terms is not None: + changed_terms.append(cur_terms) + else: + # Entry was not baked last time. Just mark as dirty + # all the new terms. + changed_terms = cur_entry.config.get(tax.setting_name) + + if changed_terms is not None: + if not isinstance(changed_terms, list): + changed_terms = [changed_terms] + tt_info = buckets[cur_entry.source_name][tax.name] + tt_info.dirty_terms |= set(changed_terms) + + # Remember all terms used. + if cur_entry and cur_entry.was_baked_successfully: cur_terms = cur_entry.config.get(tax.setting_name) - if tax.is_multiple: - if prev_terms is not None: - changed_terms += prev_terms - if cur_terms is not None: - changed_terms += cur_terms - else: - if prev_terms is not None: - changed_terms.append(prev_terms) - if cur_terms is not None: - changed_terms.append(cur_terms) - if changed_terms is not None: - if not isinstance(changed_terms, list): - changed_terms = [changed_terms] - buckets[cur_entry.source_name][tax.name] |= ( - set(changed_terms)) + if cur_terms is not None: + if not isinstance(cur_terms, list): + cur_terms = [cur_terms] + tt_info = buckets[cur_entry.source_name][tax.name] + tt_info.all_terms |= set(cur_terms) + elif (prev_entry and prev_entry.was_baked_successfully and + cur_entry and not cur_entry.was_baked): + prev_terms = prev_entry.config.get(tax.setting_name) + if prev_terms is not None: + if not isinstance(prev_terms, list): + prev_terms = [prev_terms] + tt_info = buckets[prev_entry.source_name][tax.name] + tt_info.all_terms |= set(prev_terms) # Re-bake the combination pages for terms that are 'dirty'. known_combinations = set() logger.debug("Gathering dirty term combinations") for prev_entry, cur_entry in record.transitions.values(): - if cur_entry: + if cur_entry and cur_entry.was_baked_successfully: known_combinations |= cur_entry.used_taxonomy_terms elif prev_entry: known_combinations |= prev_entry.used_taxonomy_terms for sn, tn, terms in known_combinations: - changed_terms = buckets[sn][tn] - if not changed_terms.isdisjoint(set(terms)): - changed_terms.add(terms) + tt_info = buckets[sn][tn] + tt_info.all_terms.add(terms) + if not tt_info.dirty_terms.isdisjoint(set(terms)): + tt_info.dirty_terms.add(terms) # Start baking those terms. pool, queue, abort = self._createWorkerPool(record, self.num_workers) for source_name, source_taxonomies in buckets.items(): - for tax_name, terms in source_taxonomies.items(): + for tax_name, tt_info in source_taxonomies.items(): + terms = tt_info.dirty_terms if len(terms) == 0: continue @@ -280,14 +317,38 @@ logger.debug( "Queuing: %s [%s, %s]" % (fac.ref_spec, tax_name, term)) - entry = BakeRecordPageEntry(fac, tax_name, term) + entry = BakeRecordPageEntry( + fac.source.name, fac.rel_path, fac.path, + (tax_name, term, source_name)) record.addEntry(entry) - queue.addJob( - BakeWorkerJob(fac, route, entry, tax_name, term)) + queue.addJob(BakeWorkerJob(fac, route, entry)) success = self._waitOnWorkerPool(pool, abort) record.current.success &= success + # Now we create bake entries for all the terms that were *not* dirty. + # This is because otherwise, on the next incremental bake, we wouldn't + # find any entry for those things, and figure that we need to delete + # their outputs. + for prev_entry, cur_entry in record.transitions.values(): + # Only consider taxonomy-related entries that don't have any + # current version. + if (prev_entry and prev_entry.taxonomy_info and + not cur_entry): + sn = prev_entry.source_name + tn, tt, tsn = prev_entry.taxonomy_info + tt_info = buckets[tsn][tn] + if tt in tt_info.all_terms: + logger.debug("Creating unbaked entry for taxonomy " + "term '%s:%s'." % (tn, tt)) + entry = BakeRecordPageEntry( + prev_entry.source_name, prev_entry.rel_path, + prev_entry.path, prev_entry.taxonomy_info) + record.addEntry(entry) + else: + logger.debug("Taxonomy term '%s:%s' isn't used anymore." % + (tn, tt)) + def _handleDeletetions(self, record): for path, reason in record.getDeletions(): logger.debug("Removing '%s': %s" % (path, reason)) @@ -355,13 +416,10 @@ class BakeWorkerJob(object): - def __init__(self, factory, route, record_entry, - taxonomy_name=None, taxonomy_term=None): + def __init__(self, factory, route, record_entry): self.factory = factory self.route = route self.record_entry = record_entry - self.taxonomy_name = taxonomy_name - self.taxonomy_term = taxonomy_term @property def source(self): @@ -406,10 +464,7 @@ entry = job.record_entry try: - self._page_baker.bake( - job.factory, job.route, entry, - taxonomy_name=job.taxonomy_name, - taxonomy_term=job.taxonomy_term) + self._page_baker.bake(job.factory, job.route, entry) except BakingError as ex: logger.debug("Got baking error. Adding it to the record.") while ex:
--- a/piecrust/baking/records.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/baking/records.py Fri Apr 03 10:59:50 2015 -0700 @@ -1,17 +1,16 @@ import os.path import logging -from piecrust.sources.base import PageSource from piecrust.records import Record, TransitionalRecord logger = logging.getLogger(__name__) -def _get_transition_key(source_name, rel_path, taxonomy_name=None, - taxonomy_term=None): +def _get_transition_key(source_name, rel_path, taxonomy_info=None): key = '%s:%s' % (source_name, rel_path) - if taxonomy_name and taxonomy_term: - key += ';%s:' % taxonomy_name + if taxonomy_info: + taxonomy_name, taxonomy_term, taxonomy_source_name = taxonomy_info + key += ';%s:%s=' % (taxonomy_source_name, taxonomy_name) if isinstance(taxonomy_term, tuple): key += '/'.join(taxonomy_term) else: @@ -20,7 +19,7 @@ class BakeRecord(Record): - RECORD_VERSION = 9 + RECORD_VERSION = 11 def __init__(self): super(BakeRecord, self).__init__() @@ -32,24 +31,35 @@ FLAG_NONE = 0 FLAG_SOURCE_MODIFIED = 2**0 FLAG_OVERRIDEN = 2**1 +FLAG_FORCED_BY_SOURCE = 2**2 class BakeRecordPageEntry(object): - def __init__(self, factory, taxonomy_name=None, taxonomy_term=None): - self.path = factory.path - self.rel_path = factory.rel_path - self.source_name = factory.source.name - self.taxonomy_name = taxonomy_name - self.taxonomy_term = taxonomy_term - self.path_mtime = os.path.getmtime(factory.path) + """ An entry in the bake record. + + The `taxonomy_info` attribute should be a tuple of the form: + (taxonomy name, term, source name) + """ + def __init__(self, source_name, rel_path, path, taxonomy_info=None): + self.source_name = source_name + self.rel_path = rel_path + self.path = path + self.taxonomy_info = taxonomy_info self.flags = FLAG_NONE self.config = None self.errors = [] self.out_uris = [] self.out_paths = [] + self.clean_uris = [] + self.clean_out_paths = [] self.used_source_names = set() self.used_taxonomy_terms = set() + self.used_pagination_item_count = 0 + + @property + def path_mtime(self): + return os.path.getmtime(self.path) @property def was_baked(self): @@ -63,11 +73,6 @@ def num_subs(self): return len(self.out_paths) - def __getstate__(self): - state = self.__dict__.copy() - del state['path_mtime'] - return state - class TransitionalBakeRecord(TransitionalRecord): def __init__(self, previous_path=None): @@ -82,7 +87,7 @@ def getTransitionKey(self, entry): return _get_transition_key(entry.source_name, entry.rel_path, - entry.taxonomy_name, entry.taxonomy_term) + entry.taxonomy_info) def getOverrideEntry(self, factory, uri): for pair in self.transitions.values(): @@ -100,10 +105,8 @@ return prev return None - def getPreviousEntry(self, source_name, rel_path, taxonomy_name=None, - taxonomy_term=None): - key = _get_transition_key(source_name, rel_path, - taxonomy_name, taxonomy_term) + def getPreviousEntry(self, source_name, rel_path, taxonomy_info=None): + key = _get_transition_key(source_name, rel_path, taxonomy_info) pair = self.transitions.get(key) if pair is not None: return pair[0]
--- a/piecrust/baking/single.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/baking/single.py Fri Apr 03 10:59:50 2015 -0700 @@ -3,7 +3,8 @@ import codecs import logging import urllib.parse -from piecrust.baking.records import FLAG_OVERRIDEN, FLAG_SOURCE_MODIFIED +from piecrust.baking.records import ( + FLAG_OVERRIDEN, FLAG_SOURCE_MODIFIED, FLAG_FORCED_BY_SOURCE) from piecrust.data.filters import (PaginationFilter, HasFilterClause, IsFilterClause, AndBooleanClause, page_value_accessor) @@ -56,21 +57,15 @@ return os.path.normpath(os.path.join(*bake_path)) - def bake(self, factory, route, record_entry, - taxonomy_name=None, taxonomy_term=None): - taxonomy = None + def bake(self, factory, route, record_entry): + bake_taxonomy_info = None route_metadata = dict(factory.metadata) - if taxonomy_name and taxonomy_term: - # TODO: add options for combining and slugifying terms - taxonomy = self.app.getTaxonomy(taxonomy_name) - if taxonomy.is_multiple: - if isinstance(taxonomy_term, tuple): - slugified_term = '/'.join(taxonomy_term) - else: - slugified_term = taxonomy_term - else: - slugified_term = taxonomy_term - route_metadata.update({taxonomy.setting_name: slugified_term}) + if record_entry.taxonomy_info: + tax_name, tax_term, tax_source_name = record_entry.taxonomy_info + taxonomy = self.app.getTaxonomy(tax_name) + slugified_term = route.slugifyTaxonomyTerm(tax_term) + route_metadata[taxonomy.term_name] = slugified_term + bake_taxonomy_info = (taxonomy, tax_term) # Generate the URL using the route. page = factory.buildPage() @@ -83,10 +78,11 @@ raise BakingError( "Page '%s' maps to URL '%s' but is overriden by page" "'%s:%s'." % (factory.ref_spec, uri, - override.source_name, override.rel_path)) + override.source_name, + override.rel_path)) logger.debug("'%s' [%s] is overriden by '%s:%s'. Skipping" % - (factory.ref_spec, uri, override.source_name, - override.rel_path)) + (factory.ref_spec, uri, override.source_name, + override.rel_path)) record_entry.flags |= FLAG_OVERRIDEN return @@ -97,7 +93,7 @@ record_entry.config = copy_public_page_config(page.config) prev_record_entry = self.record.getPreviousEntry( factory.source.name, factory.rel_path, - taxonomy_name, taxonomy_term) + record_entry.taxonomy_info) logger.debug("Baking '%s'..." % uri) @@ -118,7 +114,9 @@ logger.debug("'%s' is known to use sources %s, at least one " "of which got baked. Will force bake this page. " % (uri, used_src_names)) + record_entry.flags |= FLAG_FORCED_BY_SOURCE force_this = True + if PASS_FORMATTING in invalidated_render_passes: logger.debug("Will invalidate cached formatting for '%s' " "since sources were using during that pass." @@ -134,7 +132,7 @@ do_bake = True if not force_this: try: - in_path_time = record_entry.path_mtime + in_path_time = page.path_mtime out_path_time = os.path.getmtime(out_path) if out_path_time >= in_path_time: do_bake = False @@ -151,7 +149,9 @@ cur_sub += 1 has_more_subs = True logger.debug(" %s is up to date, skipping to next " - "sub-page." % out_path) + "sub-page." % out_path) + record_entry.clean_uris.append(sub_uri) + record_entry.clean_out_paths.append(out_path) continue # We don't know how many subs to expect... just skip. @@ -161,19 +161,19 @@ # All good, proceed. try: if invalidate_formatting: - cache_key = '%s:%s' % (uri, cur_sub) + cache_key = sub_uri self.app.env.rendered_segments_repository.invalidate( cache_key) logger.debug(" p%d -> %s" % (cur_sub, out_path)) ctx, rp = self._bakeSingle(page, sub_uri, cur_sub, out_path, - taxonomy, taxonomy_term) + bake_taxonomy_info) except Exception as ex: if self.app.debug: logger.exception(ex) page_rel_path = os.path.relpath(page.path, self.app.root_dir) raise BakingError("%s: error baking '%s'." % - (page_rel_path, uri)) from ex + (page_rel_path, uri)) from ex # Copy page assets. if (cur_sub == 1 and self.copy_assets and @@ -201,17 +201,20 @@ record_entry.used_taxonomy_terms |= ctx.used_taxonomy_terms has_more_subs = False - if (ctx.used_pagination is not None and - ctx.used_pagination.has_more): - cur_sub += 1 - has_more_subs = True + if ctx.used_pagination is not None: + if cur_sub == 1: + record_entry.used_pagination_item_count = \ + ctx.used_pagination.total_item_count + if ctx.used_pagination.has_more: + cur_sub += 1 + has_more_subs = True def _bakeSingle(self, page, sub_uri, num, out_path, - taxonomy=None, taxonomy_term=None): + taxonomy_info=None): ctx = PageRenderingContext(page, sub_uri) ctx.page_num = num - if taxonomy and taxonomy_term: - ctx.setTaxonomyFilter(taxonomy, taxonomy_term) + if taxonomy_info: + ctx.setTaxonomyFilter(taxonomy_info[0], taxonomy_info[1]) rp = render_page(ctx)
--- a/piecrust/commands/builtin/baking.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/commands/builtin/baking.py Fri Apr 03 10:59:50 2015 -0700 @@ -5,7 +5,11 @@ import fnmatch import datetime from piecrust.baking.baker import Baker -from piecrust.baking.records import BakeRecord +from piecrust.baking.records import ( + BakeRecord, + FLAG_OVERRIDEN as BAKE_FLAG_OVERRIDEN, + FLAG_SOURCE_MODIFIED as BAKE_FLAG_SOURCE_MODIFIED, + FLAG_FORCED_BY_SOURCE as BAKE_FLAG_FORCED_BY_SOURCE) from piecrust.chefutil import format_timed from piecrust.commands.base import ChefCommand from piecrust.processing.base import ProcessorPipeline @@ -13,6 +17,7 @@ ProcessorPipelineRecord, FLAG_PREPARED, FLAG_PROCESSED, FLAG_OVERRIDEN, FLAG_BYPASSED_STRUCTURED_PROCESSING) +from piecrust.rendering import PASS_FORMATTING, PASS_RENDERING logger = logging.getLogger(__name__) @@ -104,11 +109,17 @@ '-t', '--out', help="A pattern that will be used to filter the output path " "of entries to show.") + parser.add_argument( + '--last', + type=int, + default=0, + help="Show the last Nth bake record.") def run(self, ctx): out_dir = ctx.args.output or os.path.join(ctx.app.root_dir, '_counter') - record_name = (hashlib.md5(out_dir.encode('utf8')).hexdigest() + - '.record') + record_id = hashlib.md5(out_dir.encode('utf8')).hexdigest() + suffix = '' if ctx.args.last == 0 else '.%d' % ctx.args.last + record_name = '%s%s.record' % (record_id, suffix) pattern = None if ctx.args.path: @@ -126,6 +137,7 @@ # Show the bake record. record = BakeRecord.load(record_cache.getCachePath(record_name)) logging.info("Bake record for: %s" % record.out_dir) + logging.info("From: %s" % record_name) logging.info("Last baked: %s" % datetime.datetime.fromtimestamp(record.bake_time)) if record.success: @@ -141,17 +153,36 @@ if fnmatch.fnmatch(o, out_pattern)])): continue + flags = [] + if entry.flags & BAKE_FLAG_OVERRIDEN: + flags.append('overriden') + if entry.flags & BAKE_FLAG_SOURCE_MODIFIED: + flags.append('overriden') + if entry.flags & BAKE_FLAG_FORCED_BY_SOURCE: + flags.append('forced by source') + + passes = {PASS_RENDERING: 'render', PASS_FORMATTING: 'format'} + used_srcs = ['%s (%s)' % (s[0], passes[s[1]]) + for s in entry.used_source_names] + logging.info(" - ") logging.info(" path: %s" % entry.rel_path) logging.info(" spec: %s:%s" % (entry.source_name, entry.rel_path)) - logging.info(" taxonomy: %s:%s" % (entry.taxonomy_name, - entry.taxonomy_term)) + if entry.taxonomy_info: + logging.info(" taxonomy: %s:%s for %s" % + entry.taxonomy_info) + else: + logging.info(" taxonomy: <none>") + logging.info(" flags: %s" % ', '.join(flags)) logging.info(" config: %s" % entry.config) logging.info(" out URLs: %s" % entry.out_uris) logging.info(" out paths: %s" % [os.path.relpath(p, out_dir) for p in entry.out_paths]) - logging.info(" used srcs: %s" % entry.used_source_names) + logging.info(" clean URLs:%s" % entry.clean_uris) + logging.info(" used srcs: %s" % used_srcs) + logging.info(" used terms:%s" % entry.used_taxonomy_terms) + logging.info(" used pgn: %d" % entry.used_pagination_item_count) if entry.errors: logging.error(" errors: %s" % entry.errors)
--- a/piecrust/commands/builtin/info.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/commands/builtin/info.py Fri Apr 03 10:59:50 2015 -0700 @@ -78,7 +78,7 @@ for route in ctx.app.routes: logger.info("%s:" % route.uri_pattern) logger.info(" source: %s" % route.source_name) - logger.info(" taxonomy: %s" % (route.taxonomy or '')) + logger.info(" taxonomy: %s" % (route.taxonomy_name or '')) logger.info(" regex: %s" % route.uri_re.pattern)
--- a/piecrust/rendering.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/rendering.py Fri Apr 03 10:59:50 2015 -0700 @@ -68,9 +68,6 @@ def source_metadata(self): return self.page.source_metadata - def reset(self): - self.used_pagination = None - def setPagination(self, paginator): if self.used_pagination is not None: raise Exception("Pagination has already been used.") @@ -82,9 +79,10 @@ self.used_source_names.add((source.name, self.current_pass)) def setTaxonomyFilter(self, taxonomy, term_value): + is_combination = isinstance(term_value, tuple) flt = PaginationFilter(value_accessor=page_value_accessor) if taxonomy.is_multiple: - if isinstance(term_value, tuple): + if is_combination: abc = AndBooleanClause() for t in term_value: abc.addClause(HasFilterClause(taxonomy.setting_name, t)) @@ -95,8 +93,10 @@ else: flt.addClause(IsFilterClause(taxonomy.setting_name, term_value)) self.pagination_filter = flt + self.custom_data = { - taxonomy.term_name: term_value} + taxonomy.term_name: term_value, + 'is_multiple_%s' % taxonomy.term_name: is_combination} def render_page(ctx): @@ -117,7 +117,7 @@ ctx.current_pass = PASS_FORMATTING repo = ctx.app.env.rendered_segments_repository if repo and not ctx.force_render: - cache_key = '%s:%s' % (ctx.uri, ctx.page_num) + cache_key = ctx.uri page_time = page.path_mtime contents = repo.get( cache_key,
--- a/piecrust/resources/theme/pages/_tag.html Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/resources/theme/pages/_tag.html Fri Apr 03 10:59:50 2015 -0700 @@ -2,7 +2,11 @@ title: format: none --- -<h2>Posts tagged with {{ tag }}</h2> +{% set display_tag = tag %} +{% if is_multiple_tag %} + {% set display_tag = tag|join(', ') %} +{% endif %} +<h2>Posts tagged with {{ display_tag }}</h2> <section> {% for post in pagination.posts %}
--- a/piecrust/routing.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/routing.py Fri Apr 03 10:59:50 2015 -0700 @@ -27,6 +27,9 @@ def __init__(self, app, cfg): self.app = app + self.source_name = cfg['source'] + self.taxonomy_name = cfg.get('taxonomy') + self.pretty_urls = app.config.get('site/pretty_urls') self.trailing_slash = app.config.get('site/trailing_slash') self.pagination_suffix_format = app.config.get( @@ -66,8 +69,6 @@ for m in route_re.finditer(self.uri_pattern): self.required_source_metadata.add(m.group('name')) - self.source_name = cfg['source'] - self.taxonomy = cfg.get('taxonomy') self.template_func = None self.template_func_name = None self.template_func_args = [] @@ -157,6 +158,19 @@ uri = self.uri_root + uri return uri + def slugifyTaxonomyTerm(self, term): + #TODO: add options for transliterating and combining terms. + if isinstance(term, tuple): + return '/'.join(term) + return term + + def unslugifyTaxonomyTerm(self, term): + #TODO: same as above. + split_terms = term.split('/') + if len(split_terms) == 1: + return term + return tuple(split_terms) + def _uriFormatRepl(self, m): name = m.group('name') #TODO: fix this hard-coded shit @@ -171,7 +185,7 @@ def _uriPatternRepl(self, m): name = m.group('name') qualifier = m.group('qual') - if qualifier == 'path': + if qualifier == 'path' or self.taxonomy_name: return r'(?P<%s>[^\?]*)' % name return r'(?P<%s>[^/\?]+)' % name @@ -198,7 +212,7 @@ if arg_list: self.template_func_args += template_func_arg_re.findall(arg_list) - if self.taxonomy: + 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. @@ -226,12 +240,10 @@ 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)) + (self.source_name, self.taxonomy_name, + registered_values)) - if len(values) == 1: - str_values = values[0] - else: - str_values = '/'.join(values) + str_values = self.slugifyTaxonomyTerm(registered_values) term_name = self.template_func_args[0] metadata = {term_name: str_values} @@ -270,7 +282,7 @@ self._funcs.append((route, route.template_func)) def __call__(self, *args, **kwargs): - if len(args) == len(self._arg_names): + if len(self._funcs) == 1 or len(args) == len(self._arg_names): f = self._funcs[0][1] return f(*args, **kwargs)
--- a/piecrust/serving.py Fri Apr 03 08:44:21 2015 -0700 +++ b/piecrust/serving.py Fri Apr 03 10:59:50 2015 -0700 @@ -227,22 +227,23 @@ raise RouteNotFoundError("Can't find route for: %s" % req_path) taxonomy = None - term_value = None + tax_terms = None for route, route_metadata in routes: source = app.getSource(route.source_name) - if route.taxonomy is None: + if route.taxonomy_name is None: rel_path, fac_metadata = source.findPagePath( route_metadata, MODE_PARSING) if rel_path is not None: break else: - taxonomy = app.getTaxonomy(route.taxonomy) - term_value = route_metadata.get(taxonomy.term_name) - if term_value is not None: + taxonomy = app.getTaxonomy(route.taxonomy_name) + route_terms = route_metadata.get(taxonomy.term_name) + if route_terms is not None: tax_page_ref = taxonomy.getPageRef(source.name) rel_path = tax_page_ref.rel_path source = tax_page_ref.source - fac_metadata = {taxonomy.term_name: term_value} + tax_terms = route.unslugifyTaxonomyTerm(route_terms) + fac_metadata = {taxonomy.term_name: tax_terms} break else: raise SourceNotFoundError( @@ -257,7 +258,7 @@ render_ctx = PageRenderingContext(page, req_path, page_num, force_render=True) if taxonomy is not None: - render_ctx.setTaxonomyFilter(taxonomy, term_value) + render_ctx.setTaxonomyFilter(taxonomy, tax_terms) # See if this page is known to use sources. If that's the case, # just don't use cached rendered segments for that page (but still