diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..3095fee45 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: minor + +Documents with the "disabled" status are ignored and not rendered to output diff --git a/docs/content.rst b/docs/content.rst index 7d7e2cfae..8969a1100 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -89,7 +89,7 @@ contains a list of reserved metadata keywords: ``summary`` Brief description of content for index pages ``lang`` Content language ID (``en``, ``fr``, etc.) ``translation`` If content is a translation of another (``true`` or ``false``) -``status`` Content status: ``draft``, ``hidden``, or ``published`` +``status`` Content status: ``draft``, ``hidden``, ``disabled``, or ``published`` ``template`` Name of template to use to generate content (without extension) ``save_as`` Save content to this relative file path ``url`` URL to use for this article/page @@ -633,6 +633,13 @@ attribute. Hidden posts will be output to ``ARTICLE_SAVE_AS`` as expected, but are not included by default in tag, category, and author indexes, nor in the main article feed. This has the effect of creating an "unlisted" post. +Disabled Posts +============== + +Posts marked with the ``disabled`` status are ignored entirely. They are not +processed, and not output to the ``ARTICLE_SAVE_AS`` path. These posts will +also not be included in indexes or feeds. + .. _W3C ISO 8601: https://www.w3.org/TR/NOTE-datetime .. _AsciiDoc: https://asciidoc.org .. _Pelican Plugins: https://github.com/pelican-plugins diff --git a/pelican/contents.py b/pelican/contents.py index 5a4032617..82e995ea6 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -547,9 +547,29 @@ def refresh_metadata_intersite_links(self) -> None: self._summary = self.metadata["summary"] +class SkipStub(Content): + # Stub class representing content that should not be processed in any way + + def __init__( + self, content, metadata=None, settings=None, source_path=None, context=None + ): + self.source_path = source_path + + def is_valid(self): + return False + + @property + def content(self): + raise NotImplementedError("Stub content should not be read") + + @property + def save_as(self): + raise NotImplementedError("Stub content cannot be saved") + + class Page(Content): mandatory_properties = ("title",) - allowed_statuses = ("published", "hidden", "draft") + allowed_statuses = ("published", "hidden", "draft", "disabled") default_status = "published" default_template = "page" @@ -560,7 +580,7 @@ def _expand_settings(self, key: str) -> str: class Article(Content): mandatory_properties = ("title", "date", "category") - allowed_statuses = ("published", "hidden", "draft") + allowed_statuses = ("published", "hidden", "draft", "disabled") default_status = "published" default_template = "article" diff --git a/pelican/generators.py b/pelican/generators.py index 548c494fe..418ab284b 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -19,7 +19,7 @@ ) from pelican.cache import FileStampDataCacher -from pelican.contents import Article, Page, Static +from pelican.contents import Article, Page, SkipStub, Static from pelican.plugins import signals from pelican.plugins._utils import plugin_enabled from pelican.readers import Readers @@ -690,6 +690,10 @@ def generate_context(self): self._add_failed_source_path(f) continue + if isinstance(article, SkipStub): + logger.debug("Safely skipping %s", f) + continue + if not article.is_valid(): self._add_failed_source_path(f) continue @@ -702,6 +706,10 @@ def generate_context(self): all_drafts.append(article) elif article.status == "hidden": hidden_articles.append(article) + elif article.status == "skipped": + raise AssertionError( + "Documents with 'disabled' status should be skipped" + ) self.add_source_path(article) self.add_static_links(article) @@ -899,6 +907,10 @@ def generate_context(self): self._add_failed_source_path(f) continue + if isinstance(page, SkipStub): + logger.debug("Safely skipping %s", f) + continue + if not page.is_valid(): self._add_failed_source_path(f) continue @@ -911,6 +923,11 @@ def generate_context(self): hidden_pages.append(page) elif page.status == "draft": draft_pages.append(page) + elif page.status == "skipped": + raise AssertionError( + "Documents with 'disabled' status should be skipped" + ) + self.add_source_path(page) self.add_static_links(page) diff --git a/pelican/readers.py b/pelican/readers.py index 3d0e8d580..85e8819f5 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -15,7 +15,7 @@ from pelican import rstdirectives # NOQA from pelican.cache import FileStampDataCacher -from pelican.contents import Author, Category, Page, Tag +from pelican.contents import Author, Category, Page, SkipStub, Tag from pelican.plugins import signals from pelican.utils import file_suffix, get_date, pelican_open, posixize_path @@ -669,6 +669,9 @@ def typogrify_wrapper(text): ) context_signal.send(context_sender, metadata=metadata) + if metadata.get("status") == "disabled": + content_class = SkipStub + return content_class( content=content, metadata=metadata, diff --git a/pelican/tests/content/article_skip.md b/pelican/tests/content/article_skip.md new file mode 100644 index 000000000..341d90dc6 --- /dev/null +++ b/pelican/tests/content/article_skip.md @@ -0,0 +1,5 @@ +Title: Skipped article +Date: 2024-06-30 +Status: disabled + +This content will not be rendered. diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index a1bbc5590..8876a8a51 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -183,15 +183,16 @@ def test_article_object_caching(self): generator.readers.read_file = MagicMock() generator.generate_context() """ - 6 files don't get cached because they were not valid + 7 files don't get cached because they were not valid - article_with_attributes_containing_double_quotes.html - article_with_comments.html - article_with_null_attributes.html - 2012-11-30_md_w_filename_meta#foo-bar.md - empty.md - empty_with_bom.md + - article_skip.md """ - self.assertEqual(generator.readers.read_file.call_count, 6) + self.assertEqual(generator.readers.read_file.call_count, 7) def test_article_reader_content_caching(self): """Test raw article content caching at the reader level"""