diff --git a/.github/workflows/lint-and-tests.yml b/.github/workflows/lint-and-tests.yml index 44f22db..6a2820d 100644 --- a/.github/workflows/lint-and-tests.yml +++ b/.github/workflows/lint-and-tests.yml @@ -22,8 +22,6 @@ jobs: fail-fast: false matrix: python-version: - - "3.8" - - "3.9" - "3.10" - "3.11" - "3.12" diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..7f09a1d --- /dev/null +++ b/.mailmap @@ -0,0 +1,8 @@ +Dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com> + +Julien M. +Julien M. + +Y.D.X. <73375426+YDX-2147483647@users.noreply.github.com> +Y.D.X. <73375426+YDX-2147483647@users.noreply.github.com> <73375426+YDX-2147483647@users.noreply.github.com> diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0de88fc..2bd5bd6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,29 @@ -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks -exclude: "node_modules|migrations|.venv|.direnv|tests/dev/|tests/fixtures/" +exclude: ".venv|.direnv|tests/dev/|tests/fixtures/" fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: check-added-large-files - args: ["--maxkb=500"] + args: + - --maxkb=500 - id: check-ast - id: check-builtin-literals - id: check-case-conflict - id: check-json - id: check-toml - id: check-yaml - args: [--unsafe] + args: + - --unsafe - id: detect-private-key - id: end-of-file-fixer - id: fix-byte-order-marker - id: fix-encoding-pragma - args: [--remove] + args: + - --remove - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] + args: + - --markdown-linebreak-ext=md - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 @@ -33,32 +35,40 @@ repos: hooks: - id: pyupgrade args: - - "--py38-plus" + - "--py310-plus" - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.4.7" hooks: - id: ruff - args: ["--fix-only", "--target-version=py38"] + args: + - --fix-only + - --target-version=py310 - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: - id: isort - args: ["--profile", "black", "--filter-files"] + args: + - --profile + - black + - --filter-files - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black - args: ["--target-version=py38"] + args: + - --target-version=py310 - repo: https://github.com/pycqa/flake8 rev: 7.0.0 hooks: - id: flake8 language: python - args: ["--config=setup.cfg", "--select=E9,F63,F7,F82"] + args: + - --config=setup.cfg + - --select=E9,F63,F7,F82 ci: autofix_prs: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 181e52c..242668d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --> +## 1.13.0 - 2024-06-10 + +### Bugs fixes πŸ› + +### Features and enhancements πŸŽ‰ + +* feature: allow customize output filenames by @Guts in +* feature: allow multiple instances by @Guts in +* chore: set minimal python version to 3.10 by @Guts in +* chore(deps): set minimum Mkdocs version to 1.4 by @Guts in +* Refacto: modernize config date from meta by @Guts in + +### Tooling πŸ”§ + +* ci: use trusted publisher and remove token by @Guts in +* tooling: add SonarCloud config by @Guts in + +### Documentation πŸ“– + +* Documentation: improve guide how to make JSON feed discoverable by @Guts in +* Docs: order settings aZ and minor improvements by @Guts in +* docs: complete JSON schema with latest features by @Guts in +* docs: fix internal links by @Guts in + +---- + ## 1.12.2 - 2024-04-30 ### Bugs fixes πŸ› diff --git a/docs/configuration.md b/docs/configuration.md index 7de4e20..a869404 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -49,7 +49,7 @@ Basically, each page is an item element in the RSS feed. | :------ | :------------: | :------------------------ | | [`page.canonical_url`](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/pages.py#L97-L105) | **required** and *optional* | mandatory item element [`link`](https://www.w3schools.com/xml/rss_tag_title_link_description_item.asp) and also used as [`guid`](https://www.w3schools.com/xml/rss_tag_guid.asp) | | [`page.meta.title`](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data) | **required** | [`title`](https://www.w3schools.com/xml/rss_tag_title_link_description_item.asp) | -| [`page.meta.description`](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data) if present, else extract headlines from the content. See below the [item_description_length option](#item-description-length). | **required** | [`description`](https://www.w3schools.com/xml/rss_tag_title_link_description_item.asp) | +| [`page.meta.description`](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data) if present, else extract headlines from the content. See below the [item_description_length option](#abstract_chars_count). | **required** | [`description`](https://www.w3schools.com/xml/rss_tag_title_link_description_item.asp) | | creation or last update datetime according git log. Can be overridden by dates in page.meta. If not, then it uses [MkDocs build date](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/utils/__init__.py#L111-L118) | **recommended** | [`pubDate`](https://www.w3schools.com/xml/rss_tag_pubdate_item.asp) | | [`page.meta.image`](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data) | *optional* | item element [`enclosure`](https://www.w3schools.com/xml/rss_tag_enclosure.asp). Some HTTP requests can be performed to retrieve remote images length. | | [`page.meta.authors`](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data) or `page.meta.author`. Accepted value types: `str` `list`, `tuple`.
To comply with the standard, the page writer is responsible to fill this field following this syntax: `john@doe.com (John Doe)` ([read this SO](https://stackoverflow.com/a/6079731/2556577)). | *optional* | [`author`](https://www.w3schools.com/XML/rss_tag_author.asp) | @@ -133,7 +133,7 @@ Output: For a sample see [homepage](./index.md#quickstart). -### `enabled`: enabling/disabling the plugin +### :material-toggle-switch: `enabled`: enabling/disabling the plugin { #enabled } You can use the `enabled` option to optionally disable this plugin. A possible use case is local development where you might want faster build times. It's recommended to use this option with an environment variable together with a default fallback (introduced in `mkdocs` v1.2.1, see [docs](https://www.mkdocs.org/user-guide/configuration/#environment-variables)). Example: @@ -152,7 +152,7 @@ mkdocs serve ---- -### `json_feed_enabled`: enabling/disabling export to JSON Feed { #json_feed_enabled } +### :simple-json: `json_feed_enabled`: enabling/disabling export to JSON Feed { #json_feed_enabled } Set it to `false` if you want to only export to RSS. @@ -160,7 +160,7 @@ Default: `true`. ---- -### `rss_feed_enabled`: enabling/disabling export to RSS { #rss_feed_enabled } +### :material-rss: `rss_feed_enabled`: enabling/disabling export to RSS { #rss_feed_enabled } Set it to `false` if you want to only export to JSON feed. @@ -168,77 +168,7 @@ Default: `true`. ---- -### `image`: set the channel image - -`image`: URL to image to use as feed illustration. - -Default: `None`. - -#### Example - -```yaml -plugins: - - rss: - image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png -``` - -Output: - -```xml - - - https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png - - MkDocs RSS Plugin - https://guts.github.io/mkdocs-rss-plugin/ - -``` - ----- - -### `comments_path`: item comments path - -`comments_path`: path to add to each item URL pointing. - -Default: `None`. - -For example, if you're using Material for Mkdocs with comment integration (Disqus or Isso), the comment block is under the div id `__comments`, so you can set: `comments_path: "#__comments"` and the output will be: - -```xml - - This page title is a perfect clickbait! - https://website.com/articles/best_article/ - https://website.com/articles/best_article/#__comments - [...] - - -``` - ----- - -### `length`: number of items to include in feed - -`length`: number of pages to include as feed items (entries). - -Default: `20` - ----- - -### `feed_ttl`: feed's cache time - -`feed_ttl`: number of minutes to be cached. Inserted as channel `ttl` element. See: [W3C RSS 2.0 documentation](https://www.w3schools.com/xml/rss_tag_ttl.asp). - -Default: `1440` (= 1 day) - -Output: - -```xml -1440 -``` - ----- - -### `abstract_chars_count`: item description length +### :material-more: `abstract_chars_count`: item description length { #abstract_chars_count } Used, in combination with `abstract_delimiter`, to determine each [item description element](https://www.w3schools.com/xml/rss_tag_title_link_description_item.asp): @@ -255,7 +185,7 @@ Default: `150` ---- -### `abstract_delimiter`: abstract delimiter +### :material-pen-minus: `abstract_delimiter`: abstract delimiter { #abstract_delimiter } Please see `abstract_chars_count` for how this setting is used. A value of `""` (the empty string) disables this step. @@ -265,13 +195,13 @@ Default: `` ---- -### `categories`: item categories +### :material-tag-multiple: `categories`: item categories { #categories } `categories`: list of page metadata values to use as [RSS item categories](https://www.w3schools.com/xml/rss_tag_category_item.asp). Default: `None`. -#### Example +#### Example { #categories_example } In configuration: @@ -327,7 +257,27 @@ Output: ---- -### `date_from_meta`: override dates from git log with page.meta +### :material-comment-bookmark-outline: `comments_path`: item comments path { #comments_path } + +`comments_path`: path to add to each item URL pointing. + +Default: `None`. + +For example, if you're using Material for Mkdocs with comment integration (Disqus or Isso), the comment block is under the div id `__comments`, so you can set: `comments_path: "#__comments"` and the output will be: + +```xml + + This page title is a perfect clickbait! + https://website.com/articles/best_article/ + https://website.com/articles/best_article/#__comments + [...] + + +``` + +---- + +### :material-calendar-start: `date_from_meta`: override dates from git log with page.meta { #date_from_meta } Basically, the plugin aims to retrieve creation and update dates from git log. But sometimes, it does not match the content workflow: markdown generated from sources, . @@ -339,7 +289,7 @@ So, it's possible to use the dates manually specified into the [page metadata] t - `default_timezone`: timezone to use by default to make aware datetimes. Default to `UTC`. Introduced in version 1.3.0 with [PR 142](https://github.com/Guts/mkdocs-rss-plugin/pull/142). - `default_time`: time to use if page contains only a date. Useful to avoid the 'midnight syndrome' or have to specify hour in every single page. Default to `None`. 24h-clock format is expected: `%H:%M`. Example: `"14:20"`. Introduced in version 1.4.0 with [PR 145](https://github.com/Guts/mkdocs-rss-plugin/pull/145). -#### Example +#### Example { #date_from_meta_example } For example, in your `best_article.md` created in 2019, you can write the front-matter like this: @@ -417,7 +367,7 @@ At the end, into the RSS you will get: ---- -### `feeds_filenames`: customize the output feed URL { #feeds_filenames } +### :material-alphabet-latin: `feeds_filenames`: customize the output feed URL { #feeds_filenames } > Since version 1.13.0. @@ -442,19 +392,57 @@ Default: ---- -### `pretty_print`: prettified XML +### :material-clock-end: `feed_ttl`: feed's cache time { #feed_ttl } -By default, the output file is minified, using Jinja2 strip options and manual work. It's possible to disable it and prettify the output using `pretty_print: true`. +`feed_ttl`: number of minutes to be cached. Inserted as channel `ttl` element. See: [W3C RSS 2.0 documentation](https://www.w3schools.com/xml/rss_tag_ttl.asp). + +Default: `1440` (= 1 day) + +Output: + +```xml +1440 +``` + +---- + +### :material-image-outline: `image`: set the channel image { #image } + +`image`: URL to image to use as feed illustration. + +Default: `None`. + +#### Example ```yaml plugins: - rss: - pretty_print: true + image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png ``` -Default: `False`. +Output: + +```xml + + + https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png + + MkDocs RSS Plugin + https://guts.github.io/mkdocs-rss-plugin/ + +``` -### `match_path`: filter pages to include in feed +---- + +### :material-counter: `length`: number of items to include in feed { #length } + +`length`: number of pages to include as feed items (entries). + +Default: `20` + +---- + +### :material-regex: `match_path`: filter pages to include in feed { #match_path } This adds a `match_path` option which should be a regex pattern matching the path to your files within the `docs_dir`. For example if you had a blog under `docs/blog` where `docs_dir` is `docs` you might use: @@ -474,7 +462,23 @@ plugins: Default: `.*`. -### `url_parameters`: additional URL parameters +---- + +### :material-format-indent-increase: `pretty_print`: prettified XML { #pretty_print } + +By default, the output file is minified, using Jinja2 strip options and manual work. It's possible to disable it and prettify the output using `pretty_print: true`. + +```yaml +plugins: + - rss: + pretty_print: true +``` + +Default: `False`. + +---- + +### :material-track-light: `url_parameters`: additional URL parameters { #url_parameters } This option allows you to add parameters to the URLs of the RSS feed items. It works as a dictionary of keys/values that is passed to [Python *urllib.parse.urlencode*](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlencode). One possible use case is the addition of [Urchin Tracking Module (UTM) parameters](https://en.wikipedia.org/wiki/UTM_parameters): @@ -506,7 +510,9 @@ Will result in: Default: `None`. -### `use_git`: enable/disable git log +---- + +### :material-git: `use_git`: enable/disable git log { #use_git } If `false`, the plugin does not try use the git log nor does not check if this is a valid git repository and use informations exclusively from `page.meta` (YAML frontmatter). @@ -514,7 +520,9 @@ Useful if you build your documentation in an environment where you can't easily Default: `true`. -### `use_material_social_cards`: enable/disable integration with Material Social Cards plugin +---- + +### :material-cards: `use_material_social_cards`: enable/disable integration with Material Social Cards plugin { #use_material_social_cards } If `false`, the integration with Social Cards is disabled. diff --git a/docs/index.md b/docs/index.md index 2d25381..aa217e0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,17 +56,24 @@ plugins: As examples, here come the feeds generated for this documentation: -- [feed_rss_created.xml](feed_rss_created.xml) for latest **created** pages: [W3C validator](https://validator.w3.org/feed/check.cgi?url=https%3A//guts.github.io/mkdocs-rss-plugin/feed_rss_created.xml) -- [feed_rss_updated.xml](feed_rss_updated.xml) for latest **updated** pages: [W3C validator](https://validator.w3.org/feed/check.cgi?url=https%3A//guts.github.io/mkdocs-rss-plugin/feed_rss_updated.xml) +- [feed_rss_created.xml](feed_rss_created.xml) and [feed_json_created.json](feed_json_created.json) for latest **created** pages: [W3C validator](https://validator.w3.org/feed/check.cgi?url=https%3A//guts.github.io/mkdocs-rss-plugin/feed_rss_created.xml) +- [feed_rss_updated.xml](feed_rss_updated.xml) and [feed_json_updated.json](feed_json_updated.json) for latest **updated** pages: [W3C validator](https://validator.w3.org/feed/check.cgi?url=https%3A//guts.github.io/mkdocs-rss-plugin/feed_rss_updated.xml) -Or it could be displayed as a Feedly follow button: +Or it could be displayed as a RSS or Feedly follow button: -[![Feedly button](https://s3.feedly.com/img/follows/feedly-follow-rectangle-flat-big_2x.png "Follow us on Feedly"){: width=130 height= 50 loading=lazy }](https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fguts.github.io%2Fmkdocs-rss-plugin%2Ffeed_rss_created.xml) +[![RSS logo](assets/rss_icon.svg "Subscribe to our RSS"){: width=130 loading=lazy }](https://guts.github.io/mkdocs-rss-plugin/feed_rss_created.xml) +[![Feedly button](https://s3.feedly.com/img/follows/feedly-follow-rectangle-flat-big_2x.png "Follow us on Feedly"){: width=130 loading=lazy }](https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fguts.github.io%2Fmkdocs-rss-plugin%2Ffeed_rss_created.xml) {: align=middle } For JSON Feed, you can use the icon: -[JSON Feed icon](https://jsonfeed.org/graphics/icon.png) +[![JSON Feed icon](https://jsonfeed.org/graphics/icon.png){: width=130 loading=lazy }](https://guts.github.io/mkdocs-rss-plugin/feed_json_created.json) +{: align=middle } + +!!! tip + See how to make your [RSS](integrations.md#reference-rss-feeds-in-html-meta-tags) and [JSON](integrations.md#reference-json-feeds-in-html-meta-tags) discoverable. + +---- ## Credits @@ -74,7 +81,7 @@ For JSON Feed, you can use the icon: - Plugin logic is inspired from [Tim Vink git-based plugins](https://github.com/timvink?tab=repositories&q=mkdocs-git&type=&language=) and main parts of Git stuff are nearly copied/pasted. - Using magic mainly from: - - [GitPython](https://gitpython.readthedocs.io/) - - [Jinja2](https://jinja.palletsprojects.com/) + - [GitPython](https://gitpython.readthedocs.io/) + - [Jinja2](https://jinja.palletsprojects.com/) - Documentation colors are a tribute to the classic RSS color scheme: orange and white. - Logo generated with DALLΒ·E. diff --git a/docs/integrations.md b/docs/integrations.md index 2b6e3dc..5d4997f 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -15,7 +15,7 @@ Here's how the RSS plugin prioritizes the image to be used in the feed: If you don't want this integration, you can disable it with the option: `use_material_social_cards=false`. -> See [related section in settings](./configuration.md#use_material_social_cards-enabledisable-integration-with-material-social-cards-plugin). +> See [related section in settings](./configuration.md#use_material_social_cards). ---- diff --git a/docs/schema.json b/docs/schema.json index 95be3e8..2896f31 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -1,6 +1,7 @@ { "$schema": "https://json-schema.org/draft-07/schema", - "title": "RSS feeds (i.e. a channel of items) using git log and page metadata.", + "title": "RSS and JSON feeds (i.e. a channel of items) using git log and page metadata.", + "description": "Support multiple instances in a single Mkdocs website.", "oneOf": [ { "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/", @@ -22,9 +23,15 @@ "default": 160, "minimum": -1 }, + "abstract_delimiter": { + "title": "String to mark where the description ends. Also called excerpt.", + "default": "", + "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#abstract_delimiter", + "type": "string" + }, "categories": { "title": "List of page metadata keys to use as item categories.", - "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#categories-item-categories", + "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#categories", "type": "array", "default": null, "items": { @@ -35,7 +42,7 @@ }, "comments_path": { "title": "Part of URL to the items' comment div.", - "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#comments_path-item-comments-path", + "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#comments_path", "type": "string", "default": null, "format": "uri-reference" @@ -98,6 +105,29 @@ "type": "integer", "default": 1440 }, + "feeds_filenames": { + "title": "Customize output RSS and JSON feeds names.", + "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#feeds_filenames", + "type": "object", + "properties": { + "json_created": { + "default": "feed_json_created.json", + "type": "string" + }, + "json_updated": { + "default": "feed_json_updated.json", + "type": "string" + }, + "rss_created": { + "default": "feed_rss_created.xml", + "type": "string" + }, + "rss_updated": { + "default": "feed_rss_updated.xml", + "type": "string" + } + } + }, "image": { "title": "Feed channel illustration", "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#image-set-the-channel-image", diff --git a/mkdocs.yml b/mkdocs.yml index 46caa30..6a516d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,7 +39,8 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets: - base_path: "." + base_path: + - "." check_paths: true - pymdownx.superfences - pymdownx.tabbed: @@ -56,8 +57,8 @@ markdown_extensions: nav: - Home: index.md - Settings: - - configuration.md - - integrations.md + - configuration.md + - integrations.md - Contributing: contributing.md - API: api.md - Changelog: changelog.md @@ -105,7 +106,6 @@ plugins: abstract_delimiter: date_from_meta: as_creation: "date" - as_update: false datetime_format: "%Y-%m-%d %H:%M" default_timezone: "Europe/Paris" default_time: "22:00" diff --git a/mkdocs_rss_plugin/__about__.py b/mkdocs_rss_plugin/__about__.py index 748039b..abd7812 100644 --- a/mkdocs_rss_plugin/__about__.py +++ b/mkdocs_rss_plugin/__about__.py @@ -40,7 +40,7 @@ __title_clean__ = "".join(e for e in __title__ if e.isalnum()) __uri__ = "https://github.com/Guts/mkdocs-rss-plugin/" -__version__ = "1.12.2" +__version__ = "1.13.0" __version_info__ = tuple( [ int(num) if num.isdigit() else num diff --git a/mkdocs_rss_plugin/config.py b/mkdocs_rss_plugin/config.py index b2129bd..49d7482 100644 --- a/mkdocs_rss_plugin/config.py +++ b/mkdocs_rss_plugin/config.py @@ -4,6 +4,9 @@ # ########## Libraries ############# # ################################## +# standard +from datetime import datetime +from typing import Union # 3rd party from mkdocs.config import config_options @@ -14,6 +17,16 @@ # ################################## +class _DateFromMeta(Config): + # TODO: remove deprecated code in future version. Only str values will be accepted + # for as_creation and as_update + as_creation = config_options.Type(Union[bool, str], default="git") + as_update = config_options.Type(Union[bool, str], default="git") + datetime_format = config_options.Type(str, default="%Y-%m-%d %H:%M") + default_time = config_options.Type(str, default=datetime.min.strftime("%H:%M")) + default_timezone = config_options.Type(str, default="UTC") + + class _FeedsFilenamesConfig(Config): json_created = config_options.Type(str, default="feed_json_created.json") json_updated = config_options.Type(str, default="feed_json_updated.json") @@ -30,14 +43,14 @@ class RssPluginConfig(Config): config_options.ListOfItems(config_options.Type(str)) ) comments_path = config_options.Optional(config_options.Type(str)) - date_from_meta = config_options.Optional(config_options.Type(dict)) + date_from_meta = config_options.SubConfig(_DateFromMeta) enabled = config_options.Type(bool, default=True) feed_ttl = config_options.Type(int, default=1440) + feeds_filenames = config_options.SubConfig(_FeedsFilenamesConfig) image = config_options.Optional(config_options.Type(str)) json_feed_enabled = config_options.Type(bool, default=True) length = config_options.Type(int, default=20) match_path = config_options.Type(str, default=".*") - feeds_filenames = config_options.SubConfig(_FeedsFilenamesConfig) pretty_print = config_options.Type(bool, default=False) rss_feed_enabled = config_options.Type(bool, default=True) url_parameters = config_options.Optional(config_options.Type(dict)) diff --git a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py index a8f6e42..f932112 100644 --- a/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py +++ b/mkdocs_rss_plugin/integrations/theme_material_social_plugin.py @@ -6,7 +6,6 @@ # standard library from pathlib import Path -from typing import Optional # 3rd party from mkdocs.config.config_options import Config @@ -174,7 +173,7 @@ def get_social_cards_dir(self, mkdocs_config: Config) -> str: return social_plugin_cfg.config.cards_dir def get_social_card_build_path_for_page( - self, mkdocs_page: Page, mkdocs_site_dir: Optional[str] = None + self, mkdocs_page: Page, mkdocs_site_dir: str | None = None ) -> Path: """Get social card URL for a specific page in documentation. @@ -197,7 +196,7 @@ def get_social_card_build_path_for_page( def get_social_card_url_for_page( self, mkdocs_page: Page, - mkdocs_site_url: Optional[str] = None, + mkdocs_site_url: str | None = None, ) -> str: """Get social card URL for a specific page in documentation. diff --git a/mkdocs_rss_plugin/models.py b/mkdocs_rss_plugin/models.py index 7e863bd..bdc6866 100644 --- a/mkdocs_rss_plugin/models.py +++ b/mkdocs_rss_plugin/models.py @@ -7,7 +7,7 @@ # standard from datetime import datetime from pathlib import Path -from typing import NamedTuple, Optional +from typing import NamedTuple # ############################################################################ @@ -16,14 +16,14 @@ class PageInformation(NamedTuple): """Data type to set and get page information in order to produce the RSS feed.""" - abs_path: Optional[Path] = None - categories: Optional[list] = None - authors: Optional[tuple] = None - created: Optional[datetime] = None - description: Optional[str] = None - guid: Optional[str] = None - image: Optional[str] = None - title: Optional[str] = None - updated: Optional[datetime] = None - url_comments: Optional[str] = None - url_full: Optional[str] = None + abs_path: Path | None = None + categories: list | None = None + authors: tuple | None = None + created: datetime | None = None + description: str | None = None + guid: str | None = None + image: str | None = None + title: str | None = None + updated: datetime | None = None + url_comments: str | None = None + url_full: str | None = None diff --git a/mkdocs_rss_plugin/plugin.py b/mkdocs_rss_plugin/plugin.py index 6f7db8f..1b5d0e6 100644 --- a/mkdocs_rss_plugin/plugin.py +++ b/mkdocs_rss_plugin/plugin.py @@ -10,14 +10,15 @@ from datetime import datetime from email.utils import formatdate from pathlib import Path -from re import compile -from typing import Optional +from re import compile as re_compile # 3rd party from jinja2 import Environment, FileSystemLoader, select_autoescape from mkdocs.config import config_options +from mkdocs.config.defaults import MkDocsConfig from mkdocs.exceptions import PluginError from mkdocs.plugins import BasePlugin, event_priority, get_plugin_logger +from mkdocs.structure.files import Files from mkdocs.structure.pages import Page from mkdocs.utils import get_build_timestamp @@ -51,32 +52,33 @@ class GitRssPlugin(BasePlugin[RssPluginConfig]): """Main class for MkDocs plugin.""" + # allow to set the plugin multiple times in the same mkdocs config + supports_multiple_instances = True + def __init__(self): """Instanciation.""" - # dates source - self.src_date_created = self.src_date_updated = "git" - self.meta_datetime_format: Optional[str] = None - self.meta_default_timezone: str = "UTC" - self.meta_default_time: Optional[datetime.time] = None # pages storage self.pages_to_filter: list = [] # prepare output feeds self.feed_created: dict = {} self.feed_updated: dict = {} - def on_config(self, config: config_options.Config) -> dict: + def on_config(self, config: MkDocsConfig) -> MkDocsConfig: """The config event is the first event called on build and is run immediately after the user configuration is loaded and validated. Any alterations to the config should be made here. - https://www.mkdocs.org/user-guide/plugins/#on_config - :param config: global configuration object - :type config: config_options.Config + See: https://www.mkdocs.org/user-guide/plugins/#on_config - :raises FileExistsError: if the template for the RSS feed is not found + Args: + config (config_options.Config): global configuration object - :return: plugin configuration object - :rtype: dict + Raises: + FileExistsError: if the template for the RSS feed is not found + PluginError: if the 'date_from_meta.default_time' format does not comply + + Returns: + MkDocsConfig: global configuration object """ # Skip if disabled if not self.config.enabled: @@ -129,48 +131,56 @@ def on_config(self, config: config_options.Config) -> dict: base_feed["logo_url"] = self.config.image # pattern to match pages included in output - self.match_path_pattern = compile(self.config.match_path) + self.match_path_pattern = re_compile(self.config.match_path) # date handling - if self.config.date_from_meta is not None: - self.src_date_created = self.config.date_from_meta.get("as_creation", False) - self.src_date_updated = self.config.date_from_meta.get("as_update", False) - self.meta_datetime_format = self.config.date_from_meta.get( - "datetime_format", "%Y-%m-%d %H:%M" + if ( + self.config.date_from_meta.as_creation == "git" + and self.config.date_from_meta.as_update == "git" + ): + logger.debug("Dates will be retrieved from git log.") + elif any( + [ + isinstance(self.config.date_from_meta.as_creation, bool), + isinstance(self.config.date_from_meta.as_update, bool), + ] + ): + deprecation_msg = ( + "Since version 1.13, using a boolean for " + "'date_from_meta.as_creation' and 'date_from_meta.as_update' is " + "deprecated. Please update your " + "`rss` plugin settings in your Mkdocs configuration " + f"({config.config_file_path}) by using a str or removing the value if " + "you were using `False`., " ) - self.meta_default_timezone = self.config.date_from_meta.get( - "default_timezone", "UTC" + logger.warning(DeprecationWarning(deprecation_msg)) + self.config.date_from_meta.as_creation = ( + self.config.date_from_meta.as_update + ) = "git" + + # check if default time complies with expected format + try: + self.config.date_from_meta.default_time = datetime.strptime( + self.config.date_from_meta.default_time, "%H:%M" ) - self.meta_default_time = self.config.date_from_meta.get( - "default_time", None + except ValueError as err: + raise PluginError( + "Config error: `date_from_meta.default_time` value " + f"'{self.config.date_from_meta.default_time}' format doesn't match the " + f"expected format %H:%M. Trace: {err}" ) - if self.meta_default_time: - try: - self.meta_default_time = datetime.strptime( - self.meta_default_time, "%H:%M" - ) - except ValueError as err: - raise PluginError( - "Config error: `date_from_meta.default_time` value " - f"'{self.meta_default_time}' format doesn't match the expected " - f"format %H:%M. Trace: {err}" - ) - else: - self.meta_default_time = datetime.min - if self.config.use_git: - logger.debug( - "Dates will be retrieved FIRSTLY from page meta (yaml " - "frontmatter). The git log will be used as fallback." - ) - else: - logger.debug( - "Dates will be retrieved ONLY from page meta (yaml " - "frontmatter). The build date will be used as fallback, without any " - "call to Git." - ) + if self.config.use_git: + logger.debug( + "Dates will be retrieved FIRSTLY from page meta (yaml " + "frontmatter). The git log will be used as fallback." + ) else: - logger.debug("Dates will be retrieved from git log.") + logger.debug( + "Dates will be retrieved ONLY from page meta (yaml " + "frontmatter). The build date will be used as fallback, without any " + "call to Git." + ) # create 2 final dicts self.feed_created = deepcopy(base_feed) @@ -197,34 +207,32 @@ def on_config(self, config: config_options.Config) -> dict: "configuration file whereas a URL is mandatory to publish. " "See: https://validator.w3.org/feed/docs/rss2.html#requiredChannelElements" ) - self.feed_created["rss_url"] = self.feed_updated["rss_url"] = None + self.feed_created["rss_url"] = self.feed_updated["json_url"] = ( + self.feed_updated["rss_url"] + ) = self.feed_updated["json_url"] = None # ending event return config @event_priority(priority=-75) def on_page_content( - self, html: str, page: Page, config: config_options.Config, files - ) -> Optional[str]: - """The page_content event is called after the Markdown text is rendered - to HTML (but before being passed to a template) and can be used to alter - the HTML body of the page. - - https://www.mkdocs.org/user-guide/plugins/#on_page_content - - :param html: HTML rendered from Markdown source as string - :type html: str - :param page: mkdocs.nav.Page instance - :type page: Page - :param config: global configuration object - :type config: config_options.Config - :param files: global navigation object - :type files: [type] - - :return: HTML rendered from Markdown source as string - :rtype: str + self, html: str, page: Page, config: MkDocsConfig, files: Files + ) -> str | None: + """The page_content event is called after the Markdown text is rendered to HTML + (but before being passed to a template) and can be used to alter the HTML + body of the page. + + See: https://www.mkdocs.org/user-guide/plugins/#on_page_content + + Args: + html (str): HTML rendered from Markdown source as string + page (Page): `mkdocs.structure.pages.Page` instance + config (MkDocsConfig): global configuration object + files (Files): global files collection + + Returns: + Optional[str]: HTML rendered from Markdown source as string """ - # Skip if disabled if not self.config.enabled: return @@ -241,11 +249,11 @@ def on_page_content( # retrieve dates from git log page_dates = self.util.get_file_dates( in_page=page, - source_date_creation=self.src_date_created, - source_date_update=self.src_date_updated, - meta_datetime_format=self.meta_datetime_format, - meta_default_timezone=self.meta_default_timezone, - meta_default_time=self.meta_default_time, + source_date_creation=self.config.date_from_meta.as_creation, + source_date_update=self.config.date_from_meta.as_update, + meta_datetime_format=self.config.date_from_meta.datetime_format, + meta_default_timezone=self.config.date_from_meta.default_timezone, + meta_default_time=self.config.date_from_meta.default_time, ) # handle custom URL parameters @@ -295,7 +303,7 @@ def on_page_content( ) ) - def on_post_build(self, config: config_options.Config) -> Optional[dict]: + def on_post_build(self, config: config_options.Config) -> dict | None: """The post_build event does not alter any variables. \ Use this event to call post-build scripts. \ See: diff --git a/mkdocs_rss_plugin/timezoner_py39.py b/mkdocs_rss_plugin/timezoner.py similarity index 100% rename from mkdocs_rss_plugin/timezoner_py39.py rename to mkdocs_rss_plugin/timezoner.py diff --git a/mkdocs_rss_plugin/timezoner_pre39.py b/mkdocs_rss_plugin/timezoner_pre39.py deleted file mode 100644 index 73ae7e5..0000000 --- a/mkdocs_rss_plugin/timezoner_pre39.py +++ /dev/null @@ -1,56 +0,0 @@ -#! python3 # noqa: E265 - - -""" - Manage timezones for pages date(time)s using pytz module. - Meant to be dropped when Python 3.8 reaches EOL. -""" - -# ############################################################################ -# ########## Libraries ############# -# ################################## - -# standard library -from datetime import datetime - -# 3rd party -import pytz -from mkdocs.plugins import get_plugin_logger - -# package -from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME - -# ############################################################################ -# ########## Globals ############# -# ################################ - - -logger = get_plugin_logger(MKDOCS_LOGGER_NAME) - - -# ############################################################################ -# ########## Functions ########### -# ################################ - - -def set_datetime_zoneinfo( - input_datetime: datetime, config_timezone: str = "UTC" -) -> datetime: - """Apply timezone to a naive datetime. - - :param input_datetime: offset-naive datetime - :type input_datetime: datetime - :param config_timezone: name of timezone as registered in IANA database, - defaults to "UTC". Example : Europe/Paris. - :type config_timezone: str, optional - - :return: offset-aware datetime - :rtype: datetime - """ - if input_datetime.tzinfo: - return input_datetime - elif not config_timezone: - return input_datetime.replace(tzinfo=pytz.utc) - else: - config_tz = pytz.timezone(config_timezone) - return config_tz.localize(input_datetime) diff --git a/mkdocs_rss_plugin/util.py b/mkdocs_rss_plugin/util.py index e436229..7b43c82 100644 --- a/mkdocs_rss_plugin/util.py +++ b/mkdocs_rss_plugin/util.py @@ -8,12 +8,12 @@ import logging import re import ssl -import sys +from collections.abc import Iterable from datetime import date, datetime from email.utils import format_datetime from mimetypes import guess_type from pathlib import Path -from typing import Any, Iterable, Optional, Tuple, Union +from typing import Any from urllib import request from urllib.error import HTTPError, URLError from urllib.parse import urlencode, urljoin, urlparse, urlunparse @@ -32,12 +32,7 @@ from mkdocs_rss_plugin.integrations.theme_material_social_plugin import ( IntegrationMaterialSocialCards, ) - -# conditional imports -if sys.version_info < (3, 9): - from mkdocs_rss_plugin.timezoner_pre39 import set_datetime_zoneinfo -else: - from mkdocs_rss_plugin.timezoner_py39 import set_datetime_zoneinfo +from mkdocs_rss_plugin.timezoner import set_datetime_zoneinfo # ############################################################################ # ########## Globals ############# @@ -90,9 +85,9 @@ def __init__( self, path: str = ".", use_git: bool = True, - integration_material_social_cards: Optional[ + integration_material_social_cards: None | ( IntegrationMaterialSocialCards - ] = None, + ) = None, ): """Class hosting the plugin logic. @@ -171,7 +166,7 @@ def build_url(self, base_url: str, path: str, args_dict: dict = None) -> str: url_parts[4] = urlencode(args_dict) return urlunparse(url_parts) - def get_value_from_dot_key(self, data: dict, dot_key: Union[str, bool]) -> Any: + def get_value_from_dot_key(self, data: dict, dot_key: str | bool) -> Any: """ Retrieves a value from a dictionary using a dot notation key. @@ -200,8 +195,8 @@ def get_file_dates( source_date_update: str = "git", meta_datetime_format: str = "%Y-%m-%d %H:%M", meta_default_timezone: str = "UTC", - meta_default_time: Optional[datetime] = None, - ) -> Tuple[datetime, datetime]: + meta_default_time: datetime | None = None, + ) -> tuple[datetime, datetime]: """Extract creation and update dates from page metadata (yaml frontmatter) or git log for given file. @@ -357,7 +352,7 @@ def get_file_dates( get_build_datetime(), ) - def get_authors_from_meta(self, in_page: Page) -> Optional[Tuple[str]]: + def get_authors_from_meta(self, in_page: Page) -> tuple[str] | None: """Returns authors from page meta. It handles 'author' and 'authors' for keys, \ str and iterable as values types. @@ -541,7 +536,7 @@ def get_description_or_abstract( ) return "" - def get_image(self, in_page: Page, base_url: str) -> Optional[Tuple[str, str, int]]: + def get_image(self, in_page: Page, base_url: str) -> tuple[str, str, int] | None: """Get page's image from page meta or social cards and returns properties. Args: @@ -590,9 +585,9 @@ def get_image(self, in_page: Page, base_url: str) -> Optional[Tuple[str, str, in else: logger.debug( f"Social card: {img_local_path} still not exists. Trying to " - f"retrieve length from remote image: {img_url}" + f"retrieve length from remote image: {img_url}. " "Note that would work only if the social card image has been " - "published before)." + "already published before the build." ) img_length = self.get_remote_image_length(image_url=img_url) @@ -643,7 +638,7 @@ def get_remote_image_length( http_method: str = "HEAD", attempt: int = 0, ssl_context: ssl.SSLContext = None, - ) -> Optional[int]: + ) -> int | None: """Retrieve length for remote images (starting with 'http' \ in meta.image or meta.illustration). \ It tries to perform a HEAD request and get the length from the headers. \ @@ -695,7 +690,7 @@ def get_remote_image_length( return int(img_length) @staticmethod - def get_site_url(mkdocs_config: Config) -> Optional[str]: + def get_site_url(mkdocs_config: Config) -> str | None: """Extract site URL from MkDocs configuration and enforce the behavior to ensure \ returning a str with length > 0 or None. If exists, it adds an ending slash. @@ -721,7 +716,7 @@ def get_site_url(mkdocs_config: Config) -> Optional[str]: return site_url - def guess_locale(self, mkdocs_config: Config) -> Optional[str]: + def guess_locale(self, mkdocs_config: Config) -> str | None: """Extract language code from MkDocs or Theme configuration. :param mkdocs_config: configuration object diff --git a/requirements/base.txt b/requirements/base.txt index 1600f68..fc74df5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,6 +3,5 @@ GitPython>=3.1,<3.2 -mkdocs>=1.4,<2 -pytz==2022.* ; python_version < "3.9" +mkdocs>=1.5,<2 tzdata==2024.* ; python_version >= "3.9" and sys_platform == "win32" diff --git a/setup.py b/setup.py index 76b4b2a..a245d3b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ # standard library from pathlib import Path -from typing import List, Union # 3rd party from setuptools import find_packages, setup @@ -26,7 +25,7 @@ # ################################## -def load_requirements(requirements_files: Union[Path, List[Path]]) -> list: +def load_requirements(requirements_files: Path | list[Path]) -> list: """Helper to load requirements list from a path or a list of paths. Args: @@ -79,7 +78,7 @@ def load_requirements(requirements_files: Union[Path, List[Path]]) -> list: # run entry_points={"mkdocs.plugins": ["rss = mkdocs_rss_plugin.plugin:GitRssPlugin"]}, # dependencies - python_requires=">=3.8, <4", + python_requires=">=3.10, <4", extras_require={ # tooling "dev": load_requirements(HERE / "requirements/development.txt"), @@ -93,8 +92,6 @@ def load_requirements(requirements_files: Union[Path, List[Path]]) -> list: "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/sonar-project.properties b/sonar-project.properties index dd18175..d33fa5c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,7 +6,7 @@ sonar.projectKey=Guts_mkdocs-rss-plugin # only=main # Python versions -sonar.python.version=3.8, 3.9, 3.10, 3.11, 3.12 +sonar.python.version=3.10, 3.11, 3.12 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=mkdocs_rss_plugin diff --git a/tests/base.py b/tests/base.py index 1c13309..a5b5151 100644 --- a/tests/base.py +++ b/tests/base.py @@ -20,6 +20,9 @@ from mkdocs.config import load_config from mkdocs.config.base import Config +# package +from mkdocs_rss_plugin.plugin import GitRssPlugin + # ############################################################################# # ########## Classes ############### # ################################## @@ -45,19 +48,27 @@ def get_plugin_config_from_mkdocs( # instanciate plugin cfg_mkdocs = load_config(str(mkdocs_yml_filepath.resolve())) - plugins = cfg_mkdocs.get("plugins") - if "rss" not in plugins: + plugins = cfg_mkdocs.plugins + rss_plugin_instances = [ + plg for plg in plugins.items() if isinstance(plg[1], GitRssPlugin) + ] + if not len(rss_plugin_instances): logging.warning( f"Plugin {plugin_name} is not part of enabled plugin in the MkDocs " "configuration file: {mkdocs_yml_filepath}" ) - return {} - plugin_loaded = plugins.get("rss") + return cfg_mkdocs + + if len(rss_plugin_instances) == 1: + plugin = rss_plugin_instances[0][1] + self.assertIsInstance(plugin, GitRssPlugin) + elif len(rss_plugin_instances) >= 1: + plugin = rss_plugin_instances[1][1] + self.assertIsInstance(plugin, GitRssPlugin) - cfg = plugin_loaded.on_config(cfg_mkdocs) - logging.info("Fixture configuration loaded: " + str(cfg)) + logging.info(f"Fixture configuration loaded: {plugin.on_config(cfg_mkdocs)}") - return plugin_loaded.config + return plugin.config def build_docs_setup( self, diff --git a/tests/dev/dev_load_config.py b/tests/dev/dev_load_config.py new file mode 100644 index 0000000..3f132fc --- /dev/null +++ b/tests/dev/dev_load_config.py @@ -0,0 +1,20 @@ +from mkdocs.config import load_config + +from mkdocs_rss_plugin.plugin import GitRssPlugin + +mkdocs_cfg = load_config(config_file="tests/fixtures/mkdocs_multiple_instances.yml") + +print(mkdocs_cfg.plugins.keys()) +rss_instances = [ + plg for plg in mkdocs_cfg.plugins.items() if isinstance(plg[1], GitRssPlugin) +] +print(len(rss_instances)) + +for plg in mkdocs_cfg.plugins.items(): + print(plg) + print(isinstance(plg[1], GitRssPlugin)) + print(type(plg)) + +rss_instance_1 = plg[1] +print(dir(rss_instance_1)) +print(rss_instance_1.on_config(mkdocs_cfg)) diff --git a/tests/fixtures/docs/blog/posts/sample_blog_post.md b/tests/fixtures/docs/blog/posts/sample_blog_post.md index cf51740..c8c42bc 100644 --- a/tests/fixtures/docs/blog/posts/sample_blog_post.md +++ b/tests/fixtures/docs/blog/posts/sample_blog_post.md @@ -1,6 +1,7 @@ --- date: 2023-02-12 -authors: [guts] +authors: + - guts categories: - Blog --- diff --git a/tests/fixtures/mkdocs_complete.yml b/tests/fixtures/mkdocs_complete.yml index a2136ff..705fdcd 100644 --- a/tests/fixtures/mkdocs_complete.yml +++ b/tests/fixtures/mkdocs_complete.yml @@ -19,7 +19,6 @@ plugins: comments_path: "#__comments" date_from_meta: as_creation: "date" - as_update: false datetime_format: "%Y-%m-%d %H:%M" default_timezone: Europe/Paris default_time: "09:30" diff --git a/tests/fixtures/mkdocs_complete_no_git.yml b/tests/fixtures/mkdocs_complete_no_git.yml index 702ef47..ddda2b0 100644 --- a/tests/fixtures/mkdocs_complete_no_git.yml +++ b/tests/fixtures/mkdocs_complete_no_git.yml @@ -19,7 +19,6 @@ plugins: comments_path: "#__comments" date_from_meta: as_creation: "date" - as_update: false datetime_format: "%Y-%m-%d %H:%M" default_timezone: Europe/Paris default_time: "09:30" diff --git a/tests/fixtures/mkdocs_multiple_instances.yml b/tests/fixtures/mkdocs_multiple_instances.yml new file mode 100644 index 0000000..ad6e1fa --- /dev/null +++ b/tests/fixtures/mkdocs_multiple_instances.yml @@ -0,0 +1,16 @@ +site_name: Test Mkdocs with multiple RSS plugin instances +site_description: Multiple RSS plugin in a single mkdocs +site_url: https://guts.github.io/mkdocs-rss-plugin + +plugins: + - rss + - rss: + feeds_filenames: + json_created: blog.json + json_updated: blog-updated.json + rss_created: blog.xml + rss_updated: blog-updated.xml + match_path: "blog/.*" + +theme: + name: material diff --git a/tests/fixtures/mkdocs_simple.yml b/tests/fixtures/mkdocs_simple.yml index 770cc75..9f2749c 100644 --- a/tests/fixtures/mkdocs_simple.yml +++ b/tests/fixtures/mkdocs_simple.yml @@ -17,7 +17,6 @@ plugins: - rss: date_from_meta: as_creation: "date" - as_update: false datetime_format: "%Y-%m-%d %H:%M" default_timezone: "Europe/Paris" default_time: "22:00" diff --git a/tests/test_build.py b/tests/test_build.py index f2b2da3..3124fea 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -564,6 +564,51 @@ def test_simple_build_custom_output_basename(self): ) self.assertEqual(feed_parsed.bozo, 0) + def test_simple_build_multiple_instances(self): + config = self.get_plugin_config_from_mkdocs( + mkdocs_yml_filepath=Path("tests/fixtures/mkdocs_multiple_instances.yml"), + plugin_name="rss", + ) + + with tempfile.TemporaryDirectory() as tmpdirname: + cli_result = self.build_docs_setup( + testproject_path="docs", + mkdocs_yml_filepath=Path( + "tests/fixtures/mkdocs_multiple_instances.yml" + ), + output_path=tmpdirname, + strict=True, + ) + + 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) / config.feeds_filenames.rss_created + ) + self.assertEqual(feed_parsed.bozo, 0) + + # updated items + feed_parsed = feedparser.parse( + Path(tmpdirname) / config.feeds_filenames.rss_updated + ) + self.assertEqual(feed_parsed.bozo, 0) + + # created items - blog + feed_parsed = feedparser.parse(Path(tmpdirname).joinpath("blog.xml")) + self.assertEqual(feed_parsed.bozo, 0) + + # updated items - blog + feed_parsed = feedparser.parse( + Path(tmpdirname).joinpath("blog-updated.xml") + ) + self.assertEqual(feed_parsed.bozo, 0) + def test_simple_build_pretty_print_enabled(self): with tempfile.TemporaryDirectory() as tmpdirname: cli_result = self.build_docs_setup( @@ -654,16 +699,20 @@ def test_json_feed_validation(self): self.assertIsNone(cli_result.exception) # created items - with Path(tmpdirname).joinpath(OUTPUT_JSON_FEED_CREATED).open( - "r", encoding="UTF-8" - ) as in_json: + with ( + Path(tmpdirname) + .joinpath(OUTPUT_JSON_FEED_CREATED) + .open("r", encoding="UTF-8") as in_json + ): json_feed_created_data = json.load(in_json) jsonfeed.Feed.parse(json_feed_created_data) # updated items - with Path(tmpdirname).joinpath(OUTPUT_JSON_FEED_UPDATED).open( - "r", encoding="UTF-8" - ) as in_json: + with ( + Path(tmpdirname) + .joinpath(OUTPUT_JSON_FEED_UPDATED) + .open("r", encoding="UTF-8") as in_json + ): json_feed_updated_data = json.load(in_json) jsonfeed.Feed.parse(json_feed_updated_data) diff --git a/tests/test_config.py b/tests/test_config.py index 8908302..db5847a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -15,6 +15,7 @@ # Standard library import unittest +from datetime import datetime from pathlib import Path # 3rd party @@ -62,7 +63,13 @@ def test_plugin_config_defaults(self): "abstract_delimiter": "", "categories": None, "comments_path": None, - "date_from_meta": None, + "date_from_meta": { + "as_creation": "git", + "as_update": "git", + "datetime_format": "%Y-%m-%d %H:%M", + "default_time": datetime.min.strftime("%H:%M"), + "default_timezone": "UTC", + }, "enabled": True, "feed_ttl": 1440, "image": None, @@ -98,7 +105,13 @@ def test_plugin_config_image(self): "abstract_delimiter": "", "categories": None, "comments_path": None, - "date_from_meta": None, + "date_from_meta": { + "as_creation": "git", + "as_update": "git", + "datetime_format": "%Y-%m-%d %H:%M", + "default_time": datetime.min.strftime("%H:%M"), + "default_timezone": "UTC", + }, "enabled": True, "feed_ttl": 1440, "image": self.feed_image,