Kaynağa Gözat

New category_meta plugin.

Zack Weinberg 9 yıl önce
ebeveyn
işleme
ba71aac048

+ 22 - 0
category_meta/README.md

@@ -0,0 +1,22 @@
+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.
+
+You can use this, for example, to control the slug used for each
+category independently of its name, or to add a description at the top
+of each category page.

+ 1 - 0
category_meta/__init__.py

@@ -0,0 +1 @@
+from .category_meta import *

+ 120 - 0
category_meta/category_meta.py

@@ -0,0 +1,120 @@
+'''Copyright 2014 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: Content.url_format does not honor category.slug (or
+### author.slug).  The sanest way to fix this without modifying core
+### is to dynamically redefine each article's class to a subclass
+### of itself with the bug fixed.
+###
+### https://github.com/getpelican/pelican/issues/1547
+
+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, 'slug'):
+                    metadata['author'] = self.author.slug
+                if hasattr(self.category, 'slug'):
+                    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(article):
+    article.__class__ = make_patched_subclass(article.__class__)
+
+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
+    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
+
+        patch_urlformat(article)
+        article.category = category_objects[m.group(1)]
+
+    generator.articles = real_articles
+
+def register():
+    signals.article_generator_pretaxonomy.connect(pretaxonomy_hook)