123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- '''Copyright 2014, 2015 Zack Weinberg
- Category Metadata
- -----------------
- A plugin to read metadata for each category from an index file in that
- category's directory.
- For this plugin to work properly, your articles should not have a
- Category: tag in their metadata; instead, they should be stored in
- (subdirectories of) per-category directories. Each per-category
- directory must have a file named 'index.ext' at its top level, where
- .ext is any extension that will be picked up by an article reader.
- The metadata of that article becomes the metadata for the category,
- copied over verbatim, with three special cases:
- * The category's name is set to the article's title.
- * The category's slug is set to the name of the parent directory
- of the index.ext file.
- * The _text_ of the article is stored as category.description.
- '''
- from pelican import signals
- import os
- import re
- import logging
- logger = logging.getLogger(__name__)
- ### CORE BUG: https://github.com/getpelican/pelican/issues/1547
- ### Content.url_format does not honor category.slug (or author.slug).
- ### The sanest way to work around this is to dynamically redefine each
- ### article's class to a subclass of itself with the bug fixed.
- ###
- ### Core was fixed in rev 822fb134e041c6938c253dd4db71813c4d0dc74a,
- ### which is not yet in any release, so we dynamically detect whether
- ### the installed version of Pelican still has the bug.
- patched_subclasses = {}
- def make_patched_subclass(klass):
- if klass.__name__ not in patched_subclasses:
- class PatchedContent(klass):
- @property
- def url_format(self):
- metadata = super(PatchedContent, self).url_format
- if hasattr(self, 'author'):
- metadata['author'] = self.author.slug
- if hasattr(self, 'category'):
- metadata['category'] = self.category.slug
- return metadata
- # Code in core uses Content class names as keys for things.
- PatchedContent.__name__ = klass.__name__
- patched_subclasses[klass.__name__] = PatchedContent
- return patched_subclasses[klass.__name__]
- def patch_urlformat(cont):
- # Test whether this content object needs to be patched.
- md = cont.url_format
- if ((hasattr(cont, 'author') and cont.author.slug != md['author']) or
- (hasattr(cont, 'category') and cont.category.slug != md['category'])):
- logger.debug("Detected bug 1547, applying workaround.")
- cont.__class__ = make_patched_subclass(cont.__class__)
- ### END OF BUG WORKAROUND
- def make_category(article, slug):
- # Reuse the article's existing category object.
- category = article.category
- # Setting a category's name resets its slug, so do that first.
- category.name = article.title
- try:
- category.slug = slug
- except AttributeError:
- category._slug = slug
- # Description from article text.
- # XXX Relative URLs in the article content may not be handled correctly.
- setattr(category, 'description', article.content)
- # Metadata, to the extent that this makes sense.
- for k, v in article.metadata.items():
- if k not in ('path', 'slug', 'category', 'name', 'title',
- 'description', 'reader'):
- setattr(category, k, v)
- logger.debug("Category: %s -> %s", category.slug, category.name)
- return category
- def pretaxonomy_hook(generator):
- """This hook is invoked before the generator's .categories property is
- filled in. Each article has already been assigned a category
- object, but these objects are _not_ unique per category and so are
- not safe to tack metadata onto (as is).
- The category metadata we're looking for is represented as an
- Article object, one per directory, whose filename is 'index.ext'.
- """
- category_objects = {}
- real_articles = []
- for article in generator.articles:
- dirname, fname = os.path.split(article.source_path)
- fname, _ = os.path.splitext(fname)
- if fname == 'index':
- category_objects[dirname] = \
- make_category(article, os.path.basename(dirname))
- else:
- real_articles.append(article)
- category_assignment = \
- re.compile("^(" +
- "|".join(re.escape(prefix)
- for prefix in category_objects.keys()) +
- ")/")
- for article in real_articles:
- m = category_assignment.match(article.source_path)
- if not m or m.group(1) not in category_objects:
- logger.error("No category assignment for %s (%s)",
- article, article.source_path)
- continue
- article.category = category_objects[m.group(1)]
- patch_urlformat(article)
- generator.articles = real_articles
- def register():
- signals.article_generator_pretaxonomy.connect(pretaxonomy_hook)
|