diff --git a/docs/settings.rst b/docs/settings.rst index 90b9e570..3652788b 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -783,7 +783,8 @@ Metadata Extra metadata dictionaries keyed by relative path. Relative paths require correct OS-specific directory separators (i.e. / in UNIX and \\ in Windows) - unlike some other Pelican file settings. + unlike some other Pelican file settings. Paths to a directory apply to all + files under it. The most-specific path wins conflicts. Not all metadata needs to be :ref:`embedded in source file itself `. For example, blog posts are often named following a diff --git a/pelican/readers.py b/pelican/readers.py index 94c4481e..0edfed0e 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -698,8 +698,20 @@ def path_metadata(full_path, source_path, settings=None): if settings.get('DEFAULT_DATE', None) == 'fs': metadata['date'] = SafeDatetime.fromtimestamp( os.stat(full_path).st_mtime) - metadata.update(settings.get('EXTRA_PATH_METADATA', {}).get( - source_path, {})) + + # Apply EXTRA_PATH_METADATA for the source path and the paths of any + # parent directories. Sorting EPM first ensures that the most specific + # path wins conflicts. + + epm = settings.get('EXTRA_PATH_METADATA', {}) + for path, meta in sorted(epm.items()): + # Enforce a trailing slash when checking for parent directories. + # This prevents false positives when one file or directory's name + # is a prefix of another's. + dirpath = os.path.join(path, '') + if source_path == path or source_path.startswith(dirpath): + metadata.update(meta) + return metadata diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index b35a511e..12c231de 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -273,6 +273,38 @@ class RstReaderTest(ReaderTest): } self.assertDictHasSubset(page.metadata, expected) + def test_article_extra_path_metadata_recurse(self): + parent = "TestCategory" + notparent = "TestCategory/article" + path = "TestCategory/article_without_category.rst" + + epm = { + parent: {'epmr_inherit': parent, + 'epmr_override': parent, }, + notparent: {'epmr_bogus': notparent}, + path: {'epmr_override': path, }, + } + expected_metadata = { + 'epmr_inherit': parent, + 'epmr_override': path, + } + + page = self.read_file(path=path, EXTRA_PATH_METADATA=epm) + self.assertDictHasSubset(page.metadata, expected_metadata) + + # Make sure vars aren't getting "inherited" by mistake... + path = "article.rst" + page = self.read_file(path=path, EXTRA_PATH_METADATA=epm) + for k in expected_metadata.keys(): + self.assertNotIn(k, page.metadata) + + # Same, but for edge cases where one file's name is a prefix of + # another. + path = "TestCategory/article_without_category.rst" + page = self.read_file(path=path, EXTRA_PATH_METADATA=epm) + for k in epm[notparent].keys(): + self.assertNotIn(k, page.metadata) + def test_typogrify(self): # if nothing is specified in the settings, the content should be # unmodified