diff --git a/docs/integrations.md b/docs/integrations.md index 5d4997f..cde420b 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -3,6 +3,66 @@ title: Integrations icon: octicons/plug-16 --- +## Blog plugin (from Material theme) + +Since version 1.17, the plugin integrates with the [Blog plugin (shipped with Material theme)](https://squidfunk.github.io/mkdocs-material/plugins/blog/) (see also [the tutorial about blog + RSS plugins](https://squidfunk.github.io/mkdocs-material/tutorials/blogs/engage/)). + +In some cases, the RSS plugin needs to work with the Material Blog: + +- for blog posts, the structure of the path to social cards is depending on blog configuration +- retrieve the author's name from the `.authors.yml` file +- optionnaly retrieve the author's email from the `.authors.yml` file + +If you don't want this integration, you can disable it with the option: `use_material_blog=false`. + +> See [related section in settings](./configuration.md#use_material_blog). + +### Example of blog authors with email + +```yaml title="docs/blog/.authors.yml" +authors: + alexvoss: + name: Alex Voss + description: Weltenwanderer + avatar: https://github.com/alexvoss.png + guts: + avatar: https://cdn.geotribu.fr/img/internal/contributeurs/jmou.jfif + description: GIS Watchman + name: Julien Moura + url: https://github.com/guts/ + email: joe@biden.com +``` + +This given Markdown post: + +```markdown title="blog/posts/demo.md" +--- +authors: + - alexvoss + - guts +date: 2024-12-02 +categories: + - tutorial +--- + +# Demonstration blog post + +[...] +``` + +Will be rendered as: + +```xml title="/build/site/feed_rss_created.xml" +[...] + + Demonstration blog post + Alex Voss + Julien Moura (joe@biden.com) +[...] +``` + +---- + ## Social Cards plugin (from Material theme) Since version 1.10, the plugin integrates with the [Social Cards plugin (shipped with Material theme)](https://squidfunk.github.io/mkdocs-material/setup/setting-up-social-cards/) (see also [the full plugin documentation here](https://squidfunk.github.io/mkdocs-material/plugins/social/)). diff --git a/mkdocs_rss_plugin/integrations/theme_material_base.py b/mkdocs_rss_plugin/integrations/theme_material_base.py index 901abad..7747412 100644 --- a/mkdocs_rss_plugin/integrations/theme_material_base.py +++ b/mkdocs_rss_plugin/integrations/theme_material_base.py @@ -17,8 +17,6 @@ # conditional try: from material import __version__ as material_version - from material.plugins.blog.plugin import BlogPlugin - from pymdownx.slugs import slugify except ImportError: material_version = None diff --git a/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py b/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py index b90c00c..1492952 100644 --- a/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py +++ b/mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py @@ -5,11 +5,14 @@ # ################################## # standard library +from functools import lru_cache +from pathlib import Path from typing import Optional # 3rd party from mkdocs.config.defaults import MkDocsConfig from mkdocs.plugins import get_plugin_logger +from mkdocs.structure.pages import Page # package from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME @@ -101,3 +104,45 @@ def is_blog_plugin_enabled_mkdocs( logger.debug("Material blog plugin is enabled in Mkdocs configuration.") self.IS_BLOG_PLUGIN_ENABLED = True return True + + @lru_cache + def author_name_from_id(self, author_id: str) -> str: + """Return author name from author_id used in Material blog plugin (.authors.yml). + + Args: + author_id (str): author key in .authors.yml + + Returns: + str: author name or passed author_id if not found within .authors.yml + """ + if ( + self.blog_plugin_cfg.config.authors + and isinstance(self.blog_plugin_cfg, BlogPlugin) + and hasattr(self.blog_plugin_cfg, "authors") + and isinstance(self.blog_plugin_cfg.authors, dict) + ): + if author_id in self.blog_plugin_cfg.authors: + author_metadata = self.blog_plugin_cfg.authors.get(author_id) + if "email" in self.blog_plugin_cfg.authors.get(author_id): + return f"{author_metadata.get('name')} ({author_metadata.get('email')})" + else: + return author_metadata.get("name") + else: + logger.error( + f"Author ID '{author_id}' is not part of known authors: " + f"{self.blog_plugin_cfg.authors}. Returning author_id." + ) + return author_id + + def is_page_a_blog_post(self, mkdocs_page: Page) -> bool: + """Identifies if the given page is part of Material Blog. + + Args: + mkdocs_page (Page): page to identify + + Returns: + bool: True if the given page is a Material Blog post. + """ + return Path(mkdocs_page.file.src_uri).is_relative_to( + self.blog_plugin_cfg.config.blog_dir + ) diff --git a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py index 257efeb..fcdaab9 100644 --- a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py +++ b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py @@ -27,7 +27,6 @@ # conditional try: from material import __version__ as material_version - from pymdownx.slugs import slugify except ImportError: material_version = None @@ -261,10 +260,9 @@ def get_social_card_build_path_for_page( mkdocs_site_dir = self.mkdocs_site_build_dir # if page is a blog post - if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( - mkdocs_page.file.src_uri - ).is_relative_to( - self.integration_material_blog.blog_plugin_cfg.config.blog_dir + if ( + self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED + and self.integration_material_blog.is_page_a_blog_post(mkdocs_page) ): expected_built_card_path = Path( f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/" @@ -306,10 +304,9 @@ def get_social_card_cache_path_for_page(self, mkdocs_page: Page) -> Optional[Pat if self.IS_INSIDERS: # if page is a blog post - if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( - mkdocs_page.file.src_uri - ).is_relative_to( - self.integration_material_blog.blog_plugin_cfg.config.blog_dir + if ( + self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED + and self.integration_material_blog.is_page_a_blog_post(mkdocs_page) ): expected_cached_card_path = self.social_cards_cache_dir.joinpath( f"assets/images/social/{Path(mkdocs_page.file.dest_uri).parent}.png" @@ -376,10 +373,9 @@ def get_social_card_url_for_page( mkdocs_site_url = self.mkdocs_site_url # if page is a blog post - if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path( - mkdocs_page.file.src_uri - ).is_relative_to( - self.integration_material_blog.blog_plugin_cfg.config.blog_dir + if ( + self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED + and self.integration_material_blog.is_page_a_blog_post(mkdocs_page) ): page_social_card = ( f"{mkdocs_site_url}assets/images/social/" diff --git a/mkdocs_rss_plugin/util.py b/mkdocs_rss_plugin/util.py index 94b0705..a25cde0 100644 --- a/mkdocs_rss_plugin/util.py +++ b/mkdocs_rss_plugin/util.py @@ -387,7 +387,16 @@ def get_authors_from_meta(self, in_page: Page) -> Optional[tuple[str]]: if isinstance(in_page.meta.get("authors"), str): return (in_page.meta.get("authors"),) elif isinstance(in_page.meta.get("authors"), (list, tuple)): - return tuple(in_page.meta.get("authors")) + if ( + self.material_blog.IS_ENABLED + and self.material_blog.is_page_a_blog_post(in_page) + ): + return [ + self.material_blog.author_name_from_id(author_id) + for author_id in in_page.meta.get("authors") + ] + else: + return tuple(in_page.meta.get("authors")) else: logging.warning( "Type of authors value in page.meta (%s) is not valid. " diff --git a/tests/fixtures/docs/blog/.authors.yml b/tests/fixtures/docs/blog/.authors.yml index 17b0e05..5c64a70 100644 --- a/tests/fixtures/docs/blog/.authors.yml +++ b/tests/fixtures/docs/blog/.authors.yml @@ -13,3 +13,4 @@ authors: name: Julien Moura slug: julien-moura url: https://geotribu.fr/team/julien-moura/ + email: guts@gmail.com diff --git a/tests/fixtures/docs/blog/posts/firstpost.md b/tests/fixtures/docs/blog/posts/firstpost.md index 5cdcd8c..730fc51 100644 --- a/tests/fixtures/docs/blog/posts/firstpost.md +++ b/tests/fixtures/docs/blog/posts/firstpost.md @@ -1,6 +1,7 @@ --- authors: - alexvoss + - guts date: 2023-10-11 categories: - meta @@ -29,4 +30,3 @@ pharetra, pellentesque risus in, consectetur urna. Nulla id enim facilisis arcu tincidunt pulvinar. Vestibulum laoreet risus scelerisque porta congue. In velit purus, dictum quis neque nec, molestie viverra risus. Nam pellentesque tellus id elit ultricies, vel finibus erat cursus. - diff --git a/tests/fixtures/mkdocs_items_material_blog_enabled.yml b/tests/fixtures/mkdocs_items_material_blog_enabled.yml index df34928..445ef73 100644 --- a/tests/fixtures/mkdocs_items_material_blog_enabled.yml +++ b/tests/fixtures/mkdocs_items_material_blog_enabled.yml @@ -3,11 +3,10 @@ site_description: Test RSS with blog plugin also enabled site_url: https://guts.github.io/mkdocs-rss-plugin plugins: - - blog: - blog_dir: blog - authors_profiles: true - - rss: - use_material_blog: true + - blog: + blog_dir: blog + authors_profiles: true + - rss theme: - name: material + name: material diff --git a/tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml b/tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml new file mode 100644 index 0000000..0ed22a6 --- /dev/null +++ b/tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml @@ -0,0 +1,13 @@ +site_name: Test RSS Plugin +site_description: Test RSS with blog plugin also enabled +site_url: https://guts.github.io/mkdocs-rss-plugin + +plugins: + - blog: + blog_dir: blog + authors_profiles: true + - rss: + use_material_blog: false + +theme: + name: material diff --git a/tests/test_integrations_material_blog.py b/tests/test_integrations_material_blog.py index 3255777..5b84e48 100644 --- a/tests/test_integrations_material_blog.py +++ b/tests/test_integrations_material_blog.py @@ -14,11 +14,14 @@ # ################################## # Standard library +import tempfile import unittest from logging import DEBUG, getLogger from pathlib import Path +from traceback import format_exception # 3rd party +import feedparser from mkdocs.config import load_config # package @@ -41,6 +44,24 @@ class TestRssPluginIntegrationsMaterialBlog(BaseTest): """Test integration of Material Blog plugin with RSS plugin.""" # -- TESTS --------------------------------------------------------- + def test_plugin_config_social_cards_enabled_but_integration_disabled(self): + # default reference + cfg_mkdocs = load_config( + str( + Path( + "tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml" + ).resolve() + ) + ) + + integration_social_cards = IntegrationMaterialBlog( + mkdocs_config=cfg_mkdocs, + switch_force=cfg_mkdocs.plugins.get("rss").config.use_material_blog, + ) + self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) + self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED) + self.assertFalse(integration_social_cards.IS_ENABLED) + def test_plugin_config_blog_enabled(self): # default reference cfg_mkdocs = load_config( @@ -52,6 +73,57 @@ def test_plugin_config_blog_enabled(self): self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED) self.assertTrue(integration_social_cards.IS_ENABLED) + def test_simple_build(self): + with tempfile.TemporaryDirectory() as tmpdirname: + cli_result = self.build_docs_setup( + testproject_path="docs", + mkdocs_yml_filepath=Path( + "tests/fixtures/mkdocs_items_material_blog_enabled.yml" + ), + output_path=tmpdirname, + strict=False, + ) + + if cli_result.exception is not None: + e = cli_result.exception + logger.debug(format_exception(type(e), e, e.__traceback__)) + + self.assertEqual(cli_result.exit_code, 0) + self.assertIsNone(cli_result.exception) + + # created items + feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml") + self.assertEqual(feed_parsed.bozo, 0) + + # updated items + feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml") + self.assertEqual(feed_parsed.bozo, 0) + + with tempfile.TemporaryDirectory() as tmpdirname: + cli_result = self.build_docs_setup( + testproject_path="docs", + mkdocs_yml_filepath=Path( + "tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml" + ), + output_path=tmpdirname, + strict=False, + ) + + if cli_result.exception is not None: + e = cli_result.exception + logger.debug(format_exception(type(e), e, e.__traceback__)) + + self.assertEqual(cli_result.exit_code, 0) + self.assertIsNone(cli_result.exception) + + # created items + feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml") + self.assertEqual(feed_parsed.bozo, 0) + + # updated items + feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml") + self.assertEqual(feed_parsed.bozo, 0) + # ############################################################################## # ##### Stand alone program ########