Skip to content

Commit e1364a0

Browse files
authored
Feature: use author name and email from .authors.yml set up in Material Blog (#340)
Related to: #250
2 parents 9e5f2b3 + c15778a commit e1364a0

File tree

10 files changed

+216
-23
lines changed

10 files changed

+216
-23
lines changed

docs/integrations.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,66 @@ title: Integrations
33
icon: octicons/plug-16
44
---
55

6+
## Blog plugin (from Material theme)
7+
8+
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/)).
9+
10+
In some cases, the RSS plugin needs to work with the Material Blog:
11+
12+
- for blog posts, the structure of the path to social cards is depending on blog configuration
13+
- retrieve the author's name from the `.authors.yml` file
14+
- optionnaly retrieve the author's email from the `.authors.yml` file
15+
16+
If you don't want this integration, you can disable it with the option: `use_material_blog=false`.
17+
18+
> See [related section in settings](./configuration.md#use_material_blog).
19+
20+
### Example of blog authors with email
21+
22+
```yaml title="docs/blog/.authors.yml"
23+
authors:
24+
alexvoss:
25+
name: Alex Voss
26+
description: Weltenwanderer
27+
avatar: https://github.com/alexvoss.png
28+
guts:
29+
avatar: https://cdn.geotribu.fr/img/internal/contributeurs/jmou.jfif
30+
description: GIS Watchman
31+
name: Julien Moura
32+
url: https://github.com/guts/
33+
34+
```
35+
36+
This given Markdown post:
37+
38+
```markdown title="blog/posts/demo.md"
39+
---
40+
authors:
41+
- alexvoss
42+
- guts
43+
date: 2024-12-02
44+
categories:
45+
- tutorial
46+
---
47+
48+
# Demonstration blog post
49+
50+
[...]
51+
```
52+
53+
Will be rendered as:
54+
55+
```xml title="/build/site/feed_rss_created.xml"
56+
[...]
57+
<item>
58+
<title>Demonstration blog post</title>
59+
<author>Alex Voss</author>
60+
<author>Julien Moura ([email protected])</author>
61+
[...]
62+
```
63+
64+
----
65+
666
## Social Cards plugin (from Material theme)
767

868
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/)).

mkdocs_rss_plugin/integrations/theme_material_base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
# conditional
1818
try:
1919
from material import __version__ as material_version
20-
from material.plugins.blog.plugin import BlogPlugin
21-
from pymdownx.slugs import slugify
2220

2321
except ImportError:
2422
material_version = None

mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
# ##################################
66

77
# standard library
8+
from functools import lru_cache
9+
from pathlib import Path
810
from typing import Optional
911

1012
# 3rd party
1113
from mkdocs.config.defaults import MkDocsConfig
1214
from mkdocs.plugins import get_plugin_logger
15+
from mkdocs.structure.pages import Page
1316

1417
# package
1518
from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME
@@ -101,3 +104,45 @@ def is_blog_plugin_enabled_mkdocs(
101104
logger.debug("Material blog plugin is enabled in Mkdocs configuration.")
102105
self.IS_BLOG_PLUGIN_ENABLED = True
103106
return True
107+
108+
@lru_cache
109+
def author_name_from_id(self, author_id: str) -> str:
110+
"""Return author name from author_id used in Material blog plugin (.authors.yml).
111+
112+
Args:
113+
author_id (str): author key in .authors.yml
114+
115+
Returns:
116+
str: author name or passed author_id if not found within .authors.yml
117+
"""
118+
if (
119+
self.blog_plugin_cfg.config.authors
120+
and isinstance(self.blog_plugin_cfg, BlogPlugin)
121+
and hasattr(self.blog_plugin_cfg, "authors")
122+
and isinstance(self.blog_plugin_cfg.authors, dict)
123+
):
124+
if author_id in self.blog_plugin_cfg.authors:
125+
author_metadata = self.blog_plugin_cfg.authors.get(author_id)
126+
if "email" in self.blog_plugin_cfg.authors.get(author_id):
127+
return f"{author_metadata.get('name')} ({author_metadata.get('email')})"
128+
else:
129+
return author_metadata.get("name")
130+
else:
131+
logger.error(
132+
f"Author ID '{author_id}' is not part of known authors: "
133+
f"{self.blog_plugin_cfg.authors}. Returning author_id."
134+
)
135+
return author_id
136+
137+
def is_page_a_blog_post(self, mkdocs_page: Page) -> bool:
138+
"""Identifies if the given page is part of Material Blog.
139+
140+
Args:
141+
mkdocs_page (Page): page to identify
142+
143+
Returns:
144+
bool: True if the given page is a Material Blog post.
145+
"""
146+
return Path(mkdocs_page.file.src_uri).is_relative_to(
147+
self.blog_plugin_cfg.config.blog_dir
148+
)

mkdocs_rss_plugin/integrations/theme_material_social_plugin.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
# conditional
2828
try:
2929
from material import __version__ as material_version
30-
from pymdownx.slugs import slugify
3130

3231
except ImportError:
3332
material_version = None
@@ -261,10 +260,9 @@ def get_social_card_build_path_for_page(
261260
mkdocs_site_dir = self.mkdocs_site_build_dir
262261

263262
# if page is a blog post
264-
if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path(
265-
mkdocs_page.file.src_uri
266-
).is_relative_to(
267-
self.integration_material_blog.blog_plugin_cfg.config.blog_dir
263+
if (
264+
self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
265+
and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
268266
):
269267
expected_built_card_path = Path(
270268
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
306304
if self.IS_INSIDERS:
307305

308306
# if page is a blog post
309-
if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path(
310-
mkdocs_page.file.src_uri
311-
).is_relative_to(
312-
self.integration_material_blog.blog_plugin_cfg.config.blog_dir
307+
if (
308+
self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
309+
and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
313310
):
314311
expected_cached_card_path = self.social_cards_cache_dir.joinpath(
315312
f"assets/images/social/{Path(mkdocs_page.file.dest_uri).parent}.png"
@@ -376,10 +373,9 @@ def get_social_card_url_for_page(
376373
mkdocs_site_url = self.mkdocs_site_url
377374

378375
# if page is a blog post
379-
if self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED and Path(
380-
mkdocs_page.file.src_uri
381-
).is_relative_to(
382-
self.integration_material_blog.blog_plugin_cfg.config.blog_dir
376+
if (
377+
self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED
378+
and self.integration_material_blog.is_page_a_blog_post(mkdocs_page)
383379
):
384380
page_social_card = (
385381
f"{mkdocs_site_url}assets/images/social/"

mkdocs_rss_plugin/util.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,16 @@ def get_authors_from_meta(self, in_page: Page) -> Optional[tuple[str]]:
387387
if isinstance(in_page.meta.get("authors"), str):
388388
return (in_page.meta.get("authors"),)
389389
elif isinstance(in_page.meta.get("authors"), (list, tuple)):
390-
return tuple(in_page.meta.get("authors"))
390+
if (
391+
self.material_blog.IS_ENABLED
392+
and self.material_blog.is_page_a_blog_post(in_page)
393+
):
394+
return [
395+
self.material_blog.author_name_from_id(author_id)
396+
for author_id in in_page.meta.get("authors")
397+
]
398+
else:
399+
return tuple(in_page.meta.get("authors"))
391400
else:
392401
logging.warning(
393402
"Type of authors value in page.meta (%s) is not valid. "

tests/fixtures/docs/blog/.authors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ authors:
1313
name: Julien Moura
1414
slug: julien-moura
1515
url: https://geotribu.fr/team/julien-moura/
16+

tests/fixtures/docs/blog/posts/firstpost.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
authors:
33
- alexvoss
4+
- guts
45
date: 2023-10-11
56
categories:
67
- meta
@@ -29,4 +30,3 @@ pharetra, pellentesque risus in, consectetur urna. Nulla id enim facilisis
2930
arcu tincidunt pulvinar. Vestibulum laoreet risus scelerisque porta congue.
3031
In velit purus, dictum quis neque nec, molestie viverra risus. Nam pellentesque
3132
tellus id elit ultricies, vel finibus erat cursus.
32-

tests/fixtures/mkdocs_items_material_blog_enabled.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ site_description: Test RSS with blog plugin also enabled
33
site_url: https://guts.github.io/mkdocs-rss-plugin
44

55
plugins:
6-
- blog:
7-
blog_dir: blog
8-
authors_profiles: true
9-
- rss:
10-
use_material_blog: true
6+
- blog:
7+
blog_dir: blog
8+
authors_profiles: true
9+
- rss
1110

1211
theme:
13-
name: material
12+
name: material
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
site_name: Test RSS Plugin
2+
site_description: Test RSS with blog plugin also enabled
3+
site_url: https://guts.github.io/mkdocs-rss-plugin
4+
5+
plugins:
6+
- blog:
7+
blog_dir: blog
8+
authors_profiles: true
9+
- rss:
10+
use_material_blog: false
11+
12+
theme:
13+
name: material

tests/test_integrations_material_blog.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
# ##################################
1515

1616
# Standard library
17+
import tempfile
1718
import unittest
1819
from logging import DEBUG, getLogger
1920
from pathlib import Path
21+
from traceback import format_exception
2022

2123
# 3rd party
24+
import feedparser
2225
from mkdocs.config import load_config
2326

2427
# package
@@ -41,6 +44,24 @@ class TestRssPluginIntegrationsMaterialBlog(BaseTest):
4144
"""Test integration of Material Blog plugin with RSS plugin."""
4245

4346
# -- TESTS ---------------------------------------------------------
47+
def test_plugin_config_social_cards_enabled_but_integration_disabled(self):
48+
# default reference
49+
cfg_mkdocs = load_config(
50+
str(
51+
Path(
52+
"tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml"
53+
).resolve()
54+
)
55+
)
56+
57+
integration_social_cards = IntegrationMaterialBlog(
58+
mkdocs_config=cfg_mkdocs,
59+
switch_force=cfg_mkdocs.plugins.get("rss").config.use_material_blog,
60+
)
61+
self.assertTrue(integration_social_cards.IS_THEME_MATERIAL)
62+
self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED)
63+
self.assertFalse(integration_social_cards.IS_ENABLED)
64+
4465
def test_plugin_config_blog_enabled(self):
4566
# default reference
4667
cfg_mkdocs = load_config(
@@ -52,6 +73,57 @@ def test_plugin_config_blog_enabled(self):
5273
self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED)
5374
self.assertTrue(integration_social_cards.IS_ENABLED)
5475

76+
def test_simple_build(self):
77+
with tempfile.TemporaryDirectory() as tmpdirname:
78+
cli_result = self.build_docs_setup(
79+
testproject_path="docs",
80+
mkdocs_yml_filepath=Path(
81+
"tests/fixtures/mkdocs_items_material_blog_enabled.yml"
82+
),
83+
output_path=tmpdirname,
84+
strict=False,
85+
)
86+
87+
if cli_result.exception is not None:
88+
e = cli_result.exception
89+
logger.debug(format_exception(type(e), e, e.__traceback__))
90+
91+
self.assertEqual(cli_result.exit_code, 0)
92+
self.assertIsNone(cli_result.exception)
93+
94+
# created items
95+
feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml")
96+
self.assertEqual(feed_parsed.bozo, 0)
97+
98+
# updated items
99+
feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml")
100+
self.assertEqual(feed_parsed.bozo, 0)
101+
102+
with tempfile.TemporaryDirectory() as tmpdirname:
103+
cli_result = self.build_docs_setup(
104+
testproject_path="docs",
105+
mkdocs_yml_filepath=Path(
106+
"tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml"
107+
),
108+
output_path=tmpdirname,
109+
strict=False,
110+
)
111+
112+
if cli_result.exception is not None:
113+
e = cli_result.exception
114+
logger.debug(format_exception(type(e), e, e.__traceback__))
115+
116+
self.assertEqual(cli_result.exit_code, 0)
117+
self.assertIsNone(cli_result.exception)
118+
119+
# created items
120+
feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml")
121+
self.assertEqual(feed_parsed.bozo, 0)
122+
123+
# updated items
124+
feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml")
125+
self.assertEqual(feed_parsed.bozo, 0)
126+
55127

56128
# ##############################################################################
57129
# ##### Stand alone program ########

0 commit comments

Comments
 (0)