├── .dockerignore ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml ├── labeler.yml ├── release.yml └── workflows │ ├── docker-test.yml │ ├── documentation.yml │ ├── lint-and-tests.yml │ ├── pr-auto-labeler.yml │ └── release.yml ├── .gitignore ├── .mailmap ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── codecov.yml ├── docs ├── api.md ├── assets │ ├── logo_rss_plugin_mkdocs.png │ ├── logo_rss_plugin_mkdocs.svg │ ├── logo_rss_plugin_mkdocs.webp │ ├── logo_rss_plugin_mkdocs_full.svg │ └── rss_icon.svg ├── changelog.md ├── configuration.md ├── contributing.md ├── index.md ├── integrations.md ├── schema.json └── theme │ └── overrides │ └── main.html ├── mkdocs.yml ├── mkdocs_rss_plugin ├── __about__.py ├── __init__.py ├── config.py ├── constants.py ├── git_manager │ ├── __init__.py │ └── ci.py ├── integrations │ ├── __init__.py │ ├── theme_material_base.py │ ├── theme_material_blog_plugin.py │ └── theme_material_social_plugin.py ├── models.py ├── plugin.py ├── templates │ └── rss.xml.jinja2 ├── timezoner.py └── util.py ├── requirements.txt ├── requirements ├── base.txt ├── development.txt ├── documentation.txt └── testing.txt ├── setup.cfg ├── setup.py ├── sonar-project.properties └── tests ├── __init__.py ├── base.py ├── dev ├── dev_cached_http.py ├── dev_json_feed_validation.py ├── dev_load_config.py ├── dev_markdown_extension_rss.py └── json_feed_sample.json ├── fixtures ├── docs │ ├── blog │ │ ├── .authors.yml │ │ ├── index.md │ │ └── posts │ │ │ ├── firstpost.md │ │ │ ├── sample_blog_post.md │ │ │ └── secondpost.md │ ├── folder_ignored │ │ └── page_should_be_ignored_by_pathmatch.md │ ├── index.md │ ├── page_with_meta.md │ ├── page_with_meta_as_string.md │ ├── page_with_meta_bad_date.md │ ├── page_with_meta_date_in_dot_key.md │ ├── page_with_meta_draft_true.md │ ├── page_with_meta_no_creation_date.md │ ├── page_with_meta_override_rss_description.md │ ├── page_with_meta_singulars.md │ ├── page_without_meta_early_delimiter.md │ ├── page_without_meta_long.md │ └── page_without_meta_short.md ├── mkdocs_bad_config.yml ├── mkdocs_complete.yml ├── mkdocs_complete_no_git.yml ├── mkdocs_custom_feeds_filenames.yml ├── mkdocs_custom_title_description.yml ├── mkdocs_dates_overridden.yml ├── mkdocs_dates_overridden_in_dot_key.yml ├── mkdocs_disabled.yml ├── mkdocs_feed_length_custom.yml ├── mkdocs_feed_ttl_custom.yml ├── mkdocs_item_categories.yml ├── mkdocs_item_comments.yml ├── mkdocs_item_delimiter_empty.yml ├── mkdocs_item_image_social_cards_blog.yml ├── mkdocs_item_image_social_cards_blog_directory_url_disabled.yml ├── mkdocs_item_image_social_cards_disabled_site.yml ├── mkdocs_item_image_social_cards_enabled_but_integration_disabled.yml ├── mkdocs_item_image_social_cards_enabled_site.yml ├── mkdocs_item_image_social_enabled_but_cards_disabled.yml ├── mkdocs_item_length_default.yml ├── mkdocs_item_length_unlimited.yml ├── mkdocs_item_no_comments.yml ├── mkdocs_items_material_blog_enabled.yml ├── mkdocs_items_material_blog_enabled_but_integration_disabled.yml ├── mkdocs_jsonfeed_enabled_not_rss.yml ├── mkdocs_language_specific_material.yml ├── mkdocs_locale_with_territory.yml ├── mkdocs_locale_without_territory.yml ├── mkdocs_minimal.yml ├── mkdocs_minimal_no_site_url.yml ├── mkdocs_multiple_instances.yml ├── mkdocs_pretty_print_disabled.yml ├── mkdocs_pretty_print_enabled.yml ├── mkdocs_rss_enabled_not_jsonfeed.yml ├── mkdocs_simple.yml ├── test-build-material.dockerfile └── test-build-min-python.dockerfile ├── test_build.py ├── test_build_no_git.py ├── test_config.py ├── test_integrations_material.py ├── test_integrations_material_blog.py ├── test_integrations_material_social_cards.py ├── test_rss_util.py └── test_timezoner.py /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/.classpath 3 | **/.dockerignore 4 | **/.env 5 | **/.git 6 | **/.github 7 | **/.gitignore 8 | **/.project 9 | **/.pytest_cache 10 | **/.settings 11 | **/.toolstarget 12 | **/.venv 13 | **/.vs 14 | **/.vscode 15 | **/*.*proj.user 16 | **/*.dbmdl 17 | **/*.jfm 18 | **/azds.yaml 19 | **/bin 20 | **/charts 21 | **/htmlcov 22 | **/docker-compose* 23 | **/Dockerfile* 24 | **/junit 25 | **/node_modules 26 | **/npm-debug.log 27 | **/obj 28 | **/secrets.dev.yaml 29 | **/site 30 | # **/tests 31 | **/values.dev.yaml 32 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: guts 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: GeoJulien 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: pip 9 | directory: "/" 10 | schedule: 11 | interval: monthly 12 | time: "11:00" 13 | labels: 14 | - dependencies 15 | 16 | - package-ecosystem: "github-actions" 17 | directory: "/" 18 | schedule: 19 | interval: "monthly" 20 | time: "18:00" 21 | reviewers: 22 | - Guts 23 | labels: 24 | - ci-cd 25 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | bug: 2 | - head-branch: 3 | - ^fix 4 | - fix 5 | - ^hotfix 6 | - hotfix 7 | 8 | ci-cd: 9 | - changed-files: 10 | - any-glob-to-any-file: 11 | - .github/** 12 | 13 | dependencies: 14 | - changed-files: 15 | - any-glob-to-any-file: 16 | - requirements/*.txt 17 | - requirements.txt 18 | 19 | documentation: 20 | - changed-files: 21 | - any-glob-to-any-file: 22 | - "*.md" 23 | - docs/** 24 | - requirements/documentation.txt 25 | - head-branch: 26 | - ^docs 27 | - documentation 28 | 29 | enhancement: 30 | - head-branch: 31 | - ^feature 32 | - feature 33 | - ^improve 34 | - improve 35 | 36 | packaging: 37 | - changed-files: 38 | - any-glob-to-any-file: 39 | - MANIFEST.in 40 | - setup.py 41 | - head-branch: 42 | - ^packaging 43 | - packaging 44 | 45 | quality: 46 | - changed-files: 47 | - any-glob-to-any-file: 48 | - tests/**/* 49 | 50 | tooling: 51 | - changed-files: 52 | - any-glob-to-any-file: 53 | - ".*" 54 | - codecov.yml 55 | - setup.cfg 56 | - .vscode/**/* 57 | - head-branch: 58 | - ^tooling 59 | - tooling 60 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot 5 | - pre-commit-ci 6 | categories: 7 | - title: Bugs fixes 🐛 8 | labels: 9 | - bug 10 | - title: Features and enhancements 🎉 11 | labels: 12 | - compliance 13 | - enhancement 14 | - title: Tooling 🔧 15 | labels: 16 | - ci-cd 17 | - tooling 18 | - title: Documentation 📖 19 | labels: 20 | - documentation 21 | - title: Other Changes 22 | labels: 23 | - "*" 24 | -------------------------------------------------------------------------------- /.github/workflows/docker-test.yml: -------------------------------------------------------------------------------- 1 | name: "🐳 Docker Builder" 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | pull_request: 7 | branches: [main] 8 | paths-ignore: 9 | - "docs/**" 10 | push: 11 | branches: [main] 12 | paths-ignore: 13 | - "docs/**" 14 | 15 | jobs: 16 | docker-build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Test Docker Build 22 | run: | 23 | docker build -f tests/fixtures/test-build-material.dockerfile -t mkdocs-plugins/rss . 24 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: "📚 Documentation" 3 | 4 | # Controls when the action will run. Triggers the workflow on push or pull request 5 | # events but only for the master branch 6 | on: 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - "docs/**" 12 | - "*.md" 13 | - ".github/workflows/documentation.yml" 14 | - ./mkdocs_rss_plugin 15 | - requirements/documentation.txt 16 | tags: 17 | - "*" 18 | 19 | pull_request: 20 | branches: 21 | - main 22 | paths: 23 | - .github/workflows/documentation.yml 24 | - docs/ 25 | - requirements/documentation.txt 26 | 27 | workflow_dispatch: 28 | 29 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 30 | permissions: 31 | contents: read 32 | pages: write 33 | id-token: write 34 | 35 | # Allow one concurrent deployment 36 | concurrency: 37 | group: "pages" 38 | cancel-in-progress: true 39 | 40 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 41 | jobs: 42 | # This workflow contains a single job called "build" 43 | deploy: 44 | # The type of runner that the job will run on 45 | runs-on: ubuntu-latest 46 | 47 | # Steps represent a sequence of tasks that will be executed as part of the job 48 | steps: 49 | - name: Get source code 50 | uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 0 53 | 54 | - name: Set up Python 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: "3.11" 58 | cache: "pip" 59 | cache-dependency-path: "requirements/documentation.txt" 60 | 61 | - name: Install dependencies 62 | run: | 63 | python -m pip install --upgrade pip setuptools wheel 64 | python -m pip install --upgrade -r requirements.txt 65 | python -m pip install --upgrade -r requirements/documentation.txt 66 | 67 | - name: Build static website 68 | env: 69 | MKDOCS_ENABLE_PLUGIN_GIT_COMMITTERS: true 70 | MKDOCS_ENABLE_PLUGIN_GIT_DATES: true 71 | MKDOCS_ENABLE_PLUGIN_MKDOCSTRINGS: true 72 | MKDOCS_ENABLE_PLUGIN_PRIVACY: true 73 | MKDOCS_ENABLE_PLUGIN_RSS: true 74 | MKDOCS_ENABLE_PLUGIN_SOCIAL: true 75 | run: mkdocs build --verbose 76 | 77 | - name: Save build doc as artifact 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: documentation 81 | path: site/ 82 | if-no-files-found: error 83 | retention-days: 30 84 | 85 | - name: Setup Pages 86 | uses: actions/configure-pages@v5 87 | if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main') 88 | 89 | - name: Upload artifact 90 | uses: actions/upload-pages-artifact@v3 91 | if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main') 92 | with: 93 | # Upload entire repository 94 | path: site/ 95 | 96 | - name: Deploy to GitHub Pages 97 | id: deployment 98 | if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main') 99 | uses: actions/deploy-pages@v4 100 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-tests.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: "🐍 Lint and Test" 3 | 4 | # Controls when the action will run. Triggers the workflow on push or pull request 5 | # events but only for the master branch 6 | on: 7 | pull_request: 8 | branches: 9 | - main 10 | paths-ignore: 11 | - "docs/**" 12 | push: 13 | branches: 14 | - main 15 | paths-ignore: 16 | - "docs/**" 17 | 18 | jobs: 19 | lintest: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | python-version: 25 | - "3.9" 26 | - "3.10" 27 | - "3.11" 28 | - "3.12" 29 | - "3.13" 30 | 31 | # Steps represent a sequence of tasks that will be executed as part of the job 32 | steps: 33 | - name: Get source code 34 | uses: actions/checkout@v4 35 | 36 | - name: Set up Python ${{ matrix.python-version }} 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | cache: "pip" 41 | cache-dependency-path: "requirements/*.txt" 42 | 43 | - name: Install dependencies 44 | run: | 45 | python -m pip install --upgrade pip setuptools wheel 46 | python -m pip install --upgrade -r requirements.txt 47 | python -m pip install --upgrade -r requirements/development.txt 48 | python -m pip install --upgrade -r requirements/testing.txt 49 | 50 | - name: Lint with flake8 51 | run: | 52 | # stop the build if there are Python syntax errors or undefined names 53 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 54 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 55 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 56 | 57 | - name: Install project 58 | run: python -m pip install -e . 59 | 60 | - name: Run Unit tests 61 | run: python -m pytest 62 | 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v5.4.3 65 | with: 66 | env_vars: PYTHON 67 | flags: unittests 68 | name: Code Coverage for unittests on python-${{ matrix.python-version }}] 69 | env: 70 | PYTHON: ${{ matrix.python-version }} 71 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 72 | -------------------------------------------------------------------------------- /.github/workflows/pr-auto-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "🏷 PR Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | triage: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/labeler@v5 10 | with: 11 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 12 | sync-labels: true 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: "🚀 Release" 5 | 6 | on: 7 | push: 8 | tags: 9 | - "*" 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | environment: 15 | name: pypi 16 | url: https://pypi.org/project/mkdocs-rss-plugin/ 17 | permissions: 18 | contents: write 19 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 20 | discussions: write 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: "3.x" 29 | cache: "pip" 30 | cache-dependency-path: "requirements/base.txt" 31 | 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip setuptools wheel 35 | python -m pip install --upgrade -r requirements.txt 36 | python -m pip install --upgrade build 37 | 38 | - name: Build a binary wheel and a source tarball 39 | run: >- 40 | python -m 41 | build 42 | --sdist 43 | --wheel 44 | --outdir dist/ 45 | . 46 | 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: python_wheel 50 | path: dist/* 51 | if-no-files-found: error 52 | 53 | - name: Publish to PyPI 54 | if: startsWith(github.ref, 'refs/tags') 55 | uses: pypa/gh-action-pypi-publish@release/v1 56 | with: 57 | print-hash: true 58 | 59 | - name: Create/update release on GitHub 60 | if: startsWith(github.ref, 'refs/tags/') 61 | uses: softprops/action-gh-release@v2 62 | with: 63 | discussion_category_name: announcements 64 | fail_on_unmatched_files: true 65 | files: dist/* 66 | generate_release_notes: true 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # #################################################################################### 2 | # ## Mac OS ################################ 3 | # ########################################## 4 | 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | # #################################################################################### 33 | # ## Windows ############################### 34 | # ########################################## 35 | 36 | # Windows thumbnail cache files 37 | Thumbs.db 38 | Thumbs.db:encryptable 39 | ehthumbs.db 40 | ehthumbs_vista.db 41 | 42 | # Dump file 43 | *.stackdump 44 | 45 | # Folder config file 46 | [Dd]esktop.ini 47 | 48 | # Recycle Bin used on file shares 49 | $RECYCLE.BIN/ 50 | 51 | # Windows Installer files 52 | *.cab 53 | *.msi 54 | *.msix 55 | *.msm 56 | *.msp 57 | 58 | # Windows shortcuts 59 | *.lnk 60 | 61 | # #################################################################################### 62 | # ## Python ############################## 63 | ########################################## 64 | 65 | # Byte-compiled / optimized / DLL files 66 | __pycache__/ 67 | *.py[cod] 68 | *$py.class 69 | 70 | # C extensions 71 | *.so 72 | 73 | # Distribution / packaging 74 | .Python 75 | build/ 76 | develop-eggs/ 77 | dist/ 78 | downloads/ 79 | eggs/ 80 | .eggs/ 81 | lib/ 82 | lib64/ 83 | parts/ 84 | sdist/ 85 | var/ 86 | wheels/ 87 | pip-wheel-metadata/ 88 | share/python-wheels/ 89 | *.egg-info/ 90 | .installed.cfg 91 | *.egg 92 | MANIFEST 93 | 94 | # PyInstaller 95 | # Usually these files are written by a python script from a template 96 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 97 | *.manifest 98 | *.spec 99 | 100 | # Installer logs 101 | pip-log.txt 102 | pip-delete-this-directory.txt 103 | 104 | # Unit test / coverage reports 105 | htmlcov/ 106 | .tox/ 107 | .nox/ 108 | .coverage 109 | .coverage.* 110 | .cache 111 | nosetests.xml 112 | coverage.xml 113 | *.cover 114 | *.py,cover 115 | .hypothesis/ 116 | .pytest_cache/ 117 | 118 | # Translations 119 | *.mo 120 | *.pot 121 | 122 | # Django stuff: 123 | *.log 124 | local_settings.py 125 | db.sqlite3 126 | db.sqlite3-journal 127 | 128 | # Flask stuff: 129 | instance/ 130 | .webassets-cache 131 | 132 | # Scrapy stuff: 133 | .scrapy 134 | 135 | # Sphinx documentation 136 | docs/_build/ 137 | docs/_apidoc/ 138 | 139 | # PyBuilder 140 | target/ 141 | 142 | # Jupyter Notebook 143 | .ipynb_checkpoints 144 | 145 | # IPython 146 | profile_default/ 147 | ipython_config.py 148 | 149 | # pyenv 150 | .python-version 151 | 152 | # pipenv 153 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 154 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 155 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 156 | # install all needed dependencies. 157 | #Pipfile.lock 158 | 159 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 160 | __pypackages__/ 161 | 162 | # Celery stuff 163 | celerybeat-schedule 164 | celerybeat.pid 165 | 166 | # SageMath parsed files 167 | *.sage.py 168 | 169 | # Environments 170 | .env 171 | .envrc 172 | .direnv/ 173 | .venv 174 | env/ 175 | venv/ 176 | ENV/ 177 | env.bak/ 178 | venv.bak/ 179 | 180 | # Spyder project settings 181 | .spyderproject 182 | .spyproject 183 | 184 | # Rope project settings 185 | .ropeproject 186 | 187 | # JetBrains project settings 188 | .idea 189 | 190 | # mkdocs documentation 191 | /site 192 | 193 | # mypy 194 | .mypy_cache/ 195 | .dmypy.json 196 | dmypy.json 197 | 198 | # Pyre type checker 199 | .pyre/ 200 | 201 | # pytype static type analyzer 202 | .pytype/ 203 | 204 | # #################################################################################### 205 | # ## VisualStudioCode ###################### 206 | # ########################################## 207 | .vscode 208 | *.code-workspace 209 | 210 | ### VisualStudioCode Patch ### 211 | # Ignore all local history of files 212 | .history 213 | 214 | # #################################################################################### 215 | # ## Custom ###################### 216 | # ################################ 217 | 218 | **/*.xml 219 | site/ 220 | tests/fixtures/docs/temp_page_not_in_git_log.md 221 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2 | Dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com> 3 | 4 | Julien M. 5 | Julien M. 6 | 7 | Y.D.X. <73375426+YDX-2147483647@users.noreply.github.com> 8 | Y.D.X. <73375426+YDX-2147483647@users.noreply.github.com> <73375426+YDX-2147483647@users.noreply.github.com> 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: ".venv|.direnv|tests/dev/|tests/fixtures/" 2 | fail_fast: false 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-added-large-files 8 | args: 9 | - --maxkb=500 10 | - id: check-ast 11 | - id: check-builtin-literals 12 | - id: check-case-conflict 13 | - id: check-json 14 | - id: check-toml 15 | - id: check-yaml 16 | args: 17 | - --unsafe 18 | - id: detect-private-key 19 | - id: end-of-file-fixer 20 | - id: fix-byte-order-marker 21 | - id: fix-encoding-pragma 22 | args: 23 | - --remove 24 | - id: trailing-whitespace 25 | args: 26 | - --markdown-linebreak-ext=md 27 | 28 | - repo: https://github.com/pre-commit/pygrep-hooks 29 | rev: v1.10.0 30 | hooks: 31 | - id: python-use-type-annotations 32 | 33 | - repo: https://github.com/asottile/pyupgrade 34 | rev: v3.20.0 35 | hooks: 36 | - id: pyupgrade 37 | args: 38 | - "--py39-plus" 39 | 40 | - repo: https://github.com/astral-sh/ruff-pre-commit 41 | rev: "v0.11.12" 42 | hooks: 43 | - id: ruff 44 | args: 45 | - --fix-only 46 | - --target-version=py39 47 | 48 | - repo: https://github.com/pycqa/isort 49 | rev: 6.0.1 50 | hooks: 51 | - id: isort 52 | args: 53 | - --profile 54 | - black 55 | - --filter-files 56 | 57 | - repo: https://github.com/psf/black 58 | rev: 25.1.0 59 | hooks: 60 | - id: black 61 | args: 62 | - --target-version=py39 63 | 64 | - repo: https://github.com/pycqa/flake8 65 | rev: 7.2.0 66 | hooks: 67 | - id: flake8 68 | additional_dependencies: 69 | - flake8-docstrings<2 70 | language: python 71 | args: 72 | - --config=setup.cfg 73 | - --select=E9,F63,F7,F82 74 | - --docstring-convention=google 75 | 76 | ci: 77 | autofix_prs: true 78 | autoupdate_schedule: monthly 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing guidelines are now published on [the online documentation](https://guts.github.io/mkdocs-rss-plugin/contributing/). 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julien Moura (In Geo Veritas) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include mkdocs_rss_plugin/templates * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MkDocs RSS plugin 2 | 3 | [![PyPi version badge](https://badgen.net/pypi/v/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 4 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 5 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 6 | 7 | [![codecov](https://codecov.io/gh/Guts/mkdocs-rss-plugin/branch/main/graph/badge.svg?token=A0XPLKiwiW)](https://codecov.io/gh/Guts/mkdocs-rss-plugin) 8 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 9 | [![flake8](https://img.shields.io/badge/linter-flake8-green)](https://flake8.pycqa.org/) 10 | [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) 11 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 12 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/Guts/mkdocs-rss-plugin/master.svg)](https://results.pre-commit.ci/latest/github/Guts/mkdocs-rss-plugin/master) 13 | [![📚 Documentation](https://github.com/Guts/mkdocs-rss-plugin/actions/workflows/documentation.yml/badge.svg)](https://github.com/Guts/mkdocs-rss-plugin/actions/workflows/documentation.yml) 14 | 15 | A plugin for [MkDocs](https://www.mkdocs.org), the static site generator, which creates [RSS 2.0](https://wikipedia.org/wiki/RSS) and [JSON Feed 1.1](https://www.jsonfeed.org/version/1.1/) feeds using the creation and modification dates from [git log](https://git-scm.com/docs/git-log) and page metadata ([YAML frontmatter](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data)). 16 | 17 | ## Installation 18 | 19 | ```sh 20 | pip install mkdocs-rss-plugin 21 | ``` 22 | 23 | ## Usage 24 | 25 | Minimal [`mkdocs.yml` configuration](https://www.mkdocs.org/user-guide/configuration/#project-information): 26 | 27 | ```yaml 28 | site_description: required. Used as feed mandatory channel description. 29 | site_name: required. Used as feed mandatory channel title and items source URL label. 30 | site_url: required. Used to build feed items URLs. 31 | ``` 32 | 33 | Minimal plugin option: 34 | 35 | ```yaml 36 | plugins: 37 | - rss 38 | ``` 39 | 40 | Full options: 41 | 42 | ```yaml 43 | plugins: 44 | - rss: 45 | abstract_chars_count: 160 # -1 for full content 46 | abstract_delimiter: 47 | categories: 48 | - tags 49 | comments_path: "#__comments" 50 | date_from_meta: 51 | as_creation: "date" # means from page.meta.date 52 | as_update: "git" # means from git log 53 | datetime_format: "%Y-%m-%d %H:%M" 54 | default_time: "09:30" 55 | default_timezone: Europe/Paris 56 | enabled: true 57 | feed_description: "My custom feed description" # MkDocs site_description: will be used if this key is not present 58 | feeds_filenames: 59 | json_created: feed_json_created.json 60 | json_updated: feed_json_updated.json 61 | rss_created: feed_rss_created.xml 62 | rss_updated: feed_rss_updated.xml 63 | feed_title: "My custom feed title" # MkDocs site_name: will be used if this key is not present 64 | feed_ttl: 1440 65 | image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png 66 | json_feed_enabled: true 67 | length: 20 68 | match_path: ".*" 69 | pretty_print: false 70 | rss_feed_enabled: true 71 | url_parameters: 72 | utm_source: "documentation" 73 | utm_medium: "RSS" 74 | utm_campaign: "feed-syndication" 75 | use_git: true 76 | use_material_blog: true 77 | use_material_social_cards: true 78 | ``` 79 | 80 | For further information, [see the user documentation](https://guts.github.io/mkdocs-rss-plugin/). 81 | 82 | Following initiative from the author of Material for MkDocs, this plugin provides its own JSON schema to validate configuration: [source](https://github.com/Guts/mkdocs-rss-plugin/blob/main/docs/schema.json) - [documentation](https://guts.github.io/mkdocs-rss-plugin/schema.json). 83 | 84 | ## Development 85 | 86 | Clone the repository: 87 | 88 | ```sh 89 | # install development dependencies 90 | python -m pip install -U -r requirements/development.txt 91 | # alternatively: pip install -e .[dev] 92 | 93 | # install project as editable 94 | python -m pip install -e . 95 | 96 | # install git hooks 97 | pre-commit install 98 | ``` 99 | 100 | Then follow the [contribution guidelines](CONTRIBUTING.md). 101 | 102 | ### Run the tests 103 | 104 | ```sh 105 | # install development dependencies 106 | python -m pip install -U -r requirements/testing.txt 107 | # alternatively: pip install -e .[test] 108 | 109 | # run tests 110 | pytest 111 | ``` 112 | 113 | ### Build the documentation 114 | 115 | ```sh 116 | # install dependencies for documentation 117 | python -m pip install -U -r requirements/documentation.txt 118 | # alternatively: pip install -e .[doc] 119 | 120 | # build the documentation 121 | mkdocs build 122 | ``` 123 | 124 | ### Release workflow 125 | 126 | 1. Fill the `CHANGELOG.md` 127 | 1. Change the version number in `__about__.py` 128 | 1. Apply a git tag with the relevant version: `git tag -a 0.3.0 {git commit hash} -m "New awesome feature"` 129 | 1. Push tag to main branch: `git push origin 0.3.0` 130 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | status: 9 | project: off 10 | patch: off 11 | 12 | parsers: 13 | gcov: 14 | branch_detection: 15 | conditional: yes 16 | loop: yes 17 | method: no 18 | macro: no 19 | 20 | comment: 21 | layout: "reach,diff,flags,tree" 22 | behavior: default 23 | require_changes: no 24 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: fontawesome/solid/code 3 | --- 4 | 5 | # API 6 | 7 | ## Core 8 | 9 | ::: mkdocs_rss_plugin.plugin.GitRssPlugin 10 | 11 | ---- 12 | 13 | ::: mkdocs_rss_plugin.config.RssPluginConfig 14 | 15 | ::: mkdocs_rss_plugin.config._DateFromMeta 16 | 17 | ::: mkdocs_rss_plugin.config._FeedsFilenamesConfig 18 | 19 | ---- 20 | 21 | ::: mkdocs_rss_plugin.constants 22 | 23 | ## Models 24 | 25 | ::: mkdocs_rss_plugin.models.PageInformation 26 | 27 | ::: mkdocs_rss_plugin.models.RssFeedBase 28 | 29 | ## Integrations 30 | 31 | ::: mkdocs_rss_plugin.integrations.theme_material_base.IntegrationMaterialThemeBase 32 | 33 | ::: mkdocs_rss_plugin.integrations.theme_material_blog_plugin.IntegrationMaterialBlog 34 | 35 | ::: mkdocs_rss_plugin.integrations.theme_material_social_plugin.IntegrationMaterialSocialCards 36 | 37 | ## Utils 38 | 39 | ::: mkdocs_rss_plugin.git_manager.ci.CiHandler 40 | 41 | ::: mkdocs_rss_plugin.timezoner 42 | 43 | ::: mkdocs_rss_plugin.util.Util 44 | -------------------------------------------------------------------------------- /docs/assets/logo_rss_plugin_mkdocs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/docs/assets/logo_rss_plugin_mkdocs.png -------------------------------------------------------------------------------- /docs/assets/logo_rss_plugin_mkdocs.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/docs/assets/logo_rss_plugin_mkdocs.webp -------------------------------------------------------------------------------- /docs/assets/rss_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | description: "Release notes of the RSS plugin for MkDocs." 4 | categories: 5 | - Release notes 6 | search: 7 | exclude: true 8 | tags: 9 | - development 10 | --- 11 | 12 | --8<-- "CHANGELOG.md" 13 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/code-block-tags 3 | title: Contribution guide 4 | tags: 5 | - contribution 6 | - development 7 | --- 8 | 9 | First off, thanks for considering to contribute to this project! 10 | 11 | These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 12 | 13 | ## Development 14 | 15 | Clone the repository: 16 | 17 | ```sh 18 | # install development dependencies 19 | python -m pip install -U -r requirements/development.txt 20 | # alternatively: pip install -e .[dev] 21 | 22 | # install project as editable 23 | python -m pip install -e . 24 | 25 | # install git hooks 26 | pre-commit install 27 | ``` 28 | 29 | Then follow the [contribution guidelines](#guidelines). 30 | 31 | ### Run the tests 32 | 33 | ```sh 34 | # install development dependencies 35 | python -m pip install -U -r requirements/testing.txt 36 | # alternatively: pip install -e .[test] 37 | 38 | # run tests 39 | pytest 40 | ``` 41 | 42 | ### Build the documentation 43 | 44 | ```sh 45 | # install dependencies for documentation 46 | python -m pip install -U -r requirements/documentation.txt 47 | # alternatively: pip install -e .[doc] 48 | 49 | # build the documentation 50 | mkdocs build 51 | ``` 52 | 53 | ### Release workflow 54 | 55 | 1. Fill the `CHANGELOG.md` 56 | 1. Change the version number in `__about__.py` 57 | 1. Apply a git tag with the relevant version: `git tag -a 0.3.0 {git commit hash} -m "New awesome feature"` 58 | 1. Push tag to main branch: `git push origin 0.3.0` 59 | 60 | ---- 61 | 62 | ## Guidelines 63 | 64 | ### Git hooks 65 | 66 | We use git hooks through [pre-commit](https://pre-commit.com/) to enforce and automatically check some "rules". Please install it before to push any commit. 67 | 68 | See the relevant configuration file: `.pre-commit-config.yaml`. 69 | 70 | ### Code Style 71 | 72 | Make sure your code *roughly* follows [PEP-8](https://www.python.org/dev/peps/pep-0008/) and keeps things consistent with the rest of the code: 73 | 74 | - docstrings: [google-style](https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html) is used to technically describe what code does or not. 75 | - formatting: [black](https://black.readthedocs.io/) is used to automatically format the code without debate. 76 | - sorted imports: [isort](https://pycqa.github.io/isort/) is used to sort imports 77 | - static analysis: [flake8](https://flake8.pycqa.org/en/latest/) is used to catch some dizziness and keep the source code healthy. 78 | 79 | ### Pulls requests 80 | 81 | Pull requests are really welcome since you take the time to push or modify tests related to the code you edit or create. 82 | 83 | ---- 84 | 85 | ## IDE proposed settings 86 | 87 | Feel free to use the IDE you love. Here come configurations for some popular IDEs to fit those guidelines. 88 | 89 | ### Visual Studio Code 90 | 91 | ```jsonc 92 | { 93 | // Editor 94 | "files.associations": { 95 | "./requirements/*.txt": "pip-requirements" 96 | }, 97 | // Markdown 98 | "markdown.updateLinksOnFileMove.enabled": "prompt", 99 | "markdown.updateLinksOnFileMove.enableForDirectories": true, 100 | "markdown.validate.enabled": true, 101 | "markdown.validate.fileLinks.markdownFragmentLinks": "warning", 102 | "markdown.validate.fragmentLinks.enabled": "warning", 103 | "[markdown]": { 104 | "editor.bracketPairColorization.enabled": true, 105 | "editor.formatOnSave": true, 106 | "editor.guides.bracketPairs": "active", 107 | "files.trimTrailingWhitespace": false, 108 | }, 109 | // Python 110 | "[python]": { 111 | "editor.codeActionsOnSave": { 112 | "source.organizeImports": "explicit" 113 | }, 114 | "editor.defaultFormatter": "ms-python.black-formatter", 115 | "editor.formatOnSave": true, 116 | "editor.guides.bracketPairs": "active", 117 | "editor.rulers": [ 118 | 88 119 | ], 120 | "editor.wordWrapColumn": 88, 121 | }, 122 | // Extensions 123 | "flake8.args": [ 124 | "--config=setup.cfg", 125 | "--verbose" 126 | ], 127 | "isort.args": [ 128 | "--profile", 129 | "black" 130 | ], 131 | "isort.check": true, 132 | "autoDocstring.guessTypes": true, 133 | "autoDocstring.docstringFormat": "google", 134 | "autoDocstring.generateDocstringOnEnter": false, 135 | "yaml.customTags": [ 136 | "!ENV scalar", 137 | "!ENV sequence", 138 | "tag:yaml.org,2002:python/name:materialx.emoji.to_svg", 139 | "tag:yaml.org,2002:python/name:materialx.emoji.twemoji", 140 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format" 141 | ], 142 | } 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The MkDocs RSS Plugin 3 | authors: 4 | - dev@ingeoveritas.com (Julien Moura) 5 | - vinktim@gmail.com (Tim Vink) 6 | date: 2020-07-06 7 | description: "MkDocs RSS plugin: generate RSS and JSON feeds for your static website using git log ad YAML frontmatter (markdown pages'metadata header)." 8 | image: "assets/rss_icon.svg" 9 | tags: 10 | - JSON Feed 11 | - Mkdocs 12 | - plugin 13 | - RSS 14 | --- 15 | 16 | [![PyPi version badge](https://badgen.net/pypi/v/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 17 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 18 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mkdocs-rss-plugin)](https://pypi.org/project/mkdocs-rss-plugin/) 19 | 20 | [![codecov](https://codecov.io/gh/Guts/mkdocs-rss-plugin/branch/main/graph/badge.svg?token=A0XPLKiwiW)](https://codecov.io/gh/Guts/mkdocs-rss-plugin) 21 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 22 | [![flake8](https://img.shields.io/badge/linter-flake8-green)](https://flake8.pycqa.org/) 23 | [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) 24 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 25 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/Guts/mkdocs-rss-plugin/master.svg)](https://results.pre-commit.ci/latest/github/Guts/mkdocs-rss-plugin/master) 26 | [![📚 Documentation](https://github.com/Guts/mkdocs-rss-plugin/actions/workflows/documentation.yml/badge.svg)](https://github.com/Guts/mkdocs-rss-plugin/actions/workflows/documentation.yml) 27 | 28 | A plugin for [MkDocs](https://www.mkdocs.org), the static site generator, which creates [RSS 2.0](https://wikipedia.org/wiki/RSS) and [JSON Feed 1.1](https://www.jsonfeed.org/version/1.1/) feeds using the creation and modification dates from [git log](https://git-scm.com/docs/git-log) and page metadata ([YAML frontmatter](https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data)). 29 | 30 | ## Quickstart 31 | 32 | Installation: 33 | 34 | 35 | 36 | ```sh 37 | > pip install mkdocs-rss-plugin 38 | ---> 100% 39 | RSS plugin for Mkdocs installed! Add 'rss' to your 'plugins' section in mkdocs.yml 40 | ``` 41 | 42 | Then in your `mkdocs.yml`: 43 | 44 | ```yaml 45 | site_description: required. Used as feed mandatory channel description. 46 | site_name: required. Used as feed mandatory channel title and items source URL label. 47 | site_url: required. Used to build feed items URLs. 48 | 49 | plugins: 50 | - rss 51 | ``` 52 | 53 | ---- 54 | 55 | ## Example 56 | 57 | As examples, here are the feeds generated for this documentation: 58 | 59 | - [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) 60 | - [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) 61 | 62 | Or it could be displayed as a RSS or Feedly follow button: 63 | 64 | [![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) 65 | [![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) 66 | {: align=middle } 67 | 68 | For JSON Feed, you can use the icon: 69 | 70 | [![JSON Feed icon](https://raw.githubusercontent.com/manton/JSONFeed/master/graphics/icon.png){: width=130 loading=lazy }](https://guts.github.io/mkdocs-rss-plugin/feed_json_created.json) 71 | {: align=middle } 72 | 73 | !!! tip 74 | 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. 75 | 76 | ---- 77 | 78 | ## Credits 79 | 80 | ![RSS logo](assets/rss_icon.svg "RSS icon - Wikimedia"){: align=right } 81 | 82 | - 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. 83 | - Using magic mainly from: 84 | - [GitPython](https://gitpython.readthedocs.io/) 85 | - [Jinja2](https://jinja.palletsprojects.com/) 86 | - Documentation colors are a tribute to the classic RSS color scheme: orange and white. 87 | - Logo generated with DALL·E. 88 | -------------------------------------------------------------------------------- /docs/integrations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Integrations 3 | icon: octicons/plug-16 4 | --- 5 | 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 | email: joe@biden.com 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 | 58 | Demonstration blog post 59 | Alex Voss 60 | Julien Moura (joe@biden.com) 61 | [...] 62 | ``` 63 | 64 | ---- 65 | 66 | ## Social Cards plugin (from Material theme) 67 | 68 | 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/)). 69 | 70 | Here's how the RSS plugin prioritizes the image to be used in the feed: 71 | 72 | 1. an image (local path or URL) is defined in the page's YAML header of the page with the key `image`. Typically: `image: path_or_url_to_image.webp`. 73 | 1. an image (local path or URL) is defined in the page's YAML header with the key `illustration`. Typically: `illustration: path_or_url_to_image.webp`. 74 | 1. if neither is defined, but both the social plugin and the cards option are enabled, then the social card image is used. 75 | 76 | If you don't want this integration, you can disable it with the option: `use_material_social_cards=false`. 77 | 78 | > See [related section in settings](./configuration.md#use_material_social_cards). 79 | 80 | ---- 81 | 82 | ## Reference RSS feeds in HTML meta-tags 83 | 84 | To facilitate the discovery of RSS feeds, it's recommended to add relevant meta-tags in `` section in HTML pages. 85 | 86 | ### Automatically set with Material theme 87 | 88 | If you're using the Material theme, everything is automagically set up (see [the related documentation page](https://squidfunk.github.io/mkdocs-material/setup/setting-up-a-blog/#rss)) :partying_face:. 89 | 90 | ### Manually { #feed-discovery-manual-rss } 91 | 92 | You need to customize the theme's template. Typically, in `main.html`: 93 | 94 | ```html 95 | {% extends "base.html" %} 96 | 97 | {% block extrahead %} 98 | 99 | 100 | 101 | {% endblock %} 102 | ``` 103 | 104 | ---- 105 | 106 | ## Reference JSON feeds in HTML meta-tags 107 | 108 | To facilitate the discovery of JSON feeds, it's [recommended](https://www.jsonfeed.org/version/1.1/#discovery-a-name-discovery-a) to add relevant meta-tags in `` section in HTML pages. 109 | 110 | ### Manually { #feed-discovery-manual-json } 111 | 112 | You need to customize the theme's template. Firstly, you need to declare the folder where you store your template overrides: 113 | 114 | ```yaml title="mkdocs.yml" 115 | [...] 116 | theme: 117 | name: material 118 | custom_dir: docs/theme/overrides 119 | [...] 120 | ``` 121 | 122 | Then add a `main.html` inside: 123 | 124 | ```jinja title="docs/theme/overrides/main.html" 125 | {% extends "base.html" %} 126 | 127 | {% block extrahead %} 128 | {# JSON Feed #} 129 | {% if "rss" in config.plugins %} 130 | 135 | 140 | {% endif %} 141 | {% endblock %} 142 | ``` 143 | 144 | If your `main.html` is getting too large, or if you like to modularize anything with more than 3 lines, you can also put this configuration in a separated `partials` file: 145 | 146 | ```jinja title="content/theme/partials/json_feed.html.jinja2" 147 | {# JSON Feed #} 148 | {% if "rss" in config.plugins %} 149 | 154 | 159 | {% endif %} 160 | ``` 161 | 162 | And include it in `main.html`: 163 | 164 | ```jinja title="docs/theme/overrides/main.html" 165 | {% include "partials/json_feed.html.jinja2" %} 166 | ``` 167 | -------------------------------------------------------------------------------- /docs/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema", 3 | "title": "RSS and JSON feeds (i.e. a channel of items) using git log and page metadata.", 4 | "description": "Support multiple instances in a single Mkdocs website.", 5 | "oneOf": [ 6 | { 7 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/", 8 | "enum": [ 9 | "rss" 10 | ] 11 | }, 12 | { 13 | "type": "object", 14 | "properties": { 15 | "rss": { 16 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/", 17 | "type": "object", 18 | "properties": { 19 | "abstract_chars_count": { 20 | "title": "Number of characters to use as item description if it's not present within the page metadata. If this value is set to -1, then the articles' full HTML content will be filled into the description element.", 21 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#abstract_chars_count-item-description-length", 22 | "type": "integer", 23 | "default": 160, 24 | "minimum": -1 25 | }, 26 | "abstract_delimiter": { 27 | "title": "String to mark where the description ends. Also called excerpt.", 28 | "default": "", 29 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#abstract_delimiter", 30 | "type": "string" 31 | }, 32 | "categories": { 33 | "title": "List of page metadata keys to use as item categories.", 34 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#categories", 35 | "type": "array", 36 | "default": null, 37 | "items": { 38 | "type": "string" 39 | }, 40 | "minItems": 1, 41 | "uniqueItems": true 42 | }, 43 | "comments_path": { 44 | "title": "Part of URL to the items' comment div.", 45 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#comments_path", 46 | "type": "string", 47 | "default": null, 48 | "format": "uri-reference" 49 | }, 50 | "date_from_meta": { 51 | "title": "Use date from page metadata instead of git log.", 52 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#date_from_meta-override-dates-from-git-log-with-pagemeta", 53 | "type": "object", 54 | "default": null, 55 | "properties": { 56 | "as_creation": { 57 | "type": [ 58 | "boolean", 59 | "string" 60 | ] 61 | }, 62 | "as_update": { 63 | "type": [ 64 | "string" 65 | ] 66 | }, 67 | "datetime_format": { 68 | "type": [ 69 | "null", 70 | "string" 71 | ] 72 | }, 73 | "default_timezone": { 74 | "type": [ 75 | "null", 76 | "string" 77 | ], 78 | "default": "UTC" 79 | }, 80 | "default_time": { 81 | "examples": [ 82 | "08:15", 83 | "16:47", 84 | "22:22" 85 | ], 86 | "description": "Default time in 24h-clock syntax (i.e. 16:30) to apply to pages with date but time specified.", 87 | "type": [ 88 | "null", 89 | "string" 90 | ], 91 | "default": null 92 | } 93 | } 94 | }, 95 | "enabled": { 96 | "title": "Enable/Disable plugin", 97 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#enabled-enablingdisabling-the-plugin", 98 | "type": "boolean", 99 | "default": true 100 | }, 101 | "feed_ttl": { 102 | "title": "Number of pages to include as feed items (entries).", 103 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#feed_ttl-feeds-cache-time", 104 | "type": "integer", 105 | "default": 1440 106 | }, 107 | "feeds_filenames": { 108 | "title": "Customize output RSS and JSON feeds names.", 109 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#feeds_filenames", 110 | "type": "object", 111 | "properties": { 112 | "json_created": { 113 | "default": "feed_json_created.json", 114 | "type": "string" 115 | }, 116 | "json_updated": { 117 | "default": "feed_json_updated.json", 118 | "type": "string" 119 | }, 120 | "rss_created": { 121 | "default": "feed_rss_created.xml", 122 | "type": "string" 123 | }, 124 | "rss_updated": { 125 | "default": "feed_rss_updated.xml", 126 | "type": "string" 127 | } 128 | } 129 | }, 130 | "image": { 131 | "title": "Feed channel illustration", 132 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#image-set-the-channel-image", 133 | "type": "string", 134 | "default": null 135 | }, 136 | "json_feed_enabled": { 137 | "title": "Enable/Disable export to JSON Feed.", 138 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#json_feed_enabled-enablingdisabling-export-to-json-feed", 139 | "type": "boolean", 140 | "default": true 141 | }, 142 | "length": { 143 | "title": "Number of pages to include as feed items (entries).", 144 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#length-number-of-items-to-include-in-feed", 145 | "type": "integer", 146 | "default": 20 147 | }, 148 | "match_path": { 149 | "title": "Regex match pattern to filter pages.", 150 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#match_path-filter-pages-to-include-in-feed", 151 | "type": "string", 152 | "default": ".*" 153 | }, 154 | "pretty_print": { 155 | "title": "Minify/Prettify output", 156 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#pretty_print-prettified-xml", 157 | "type": "boolean", 158 | "default": false 159 | }, 160 | "rss_feed_enabled": { 161 | "title": "Enable/Disable export to RSS.", 162 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#enabled-enablingdisabling-the-plugin", 163 | "type": "boolean", 164 | "default": true 165 | }, 166 | "url_parameters": { 167 | "title": "URL parameters to include in the item URL.", 168 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#url_parameters-additional-url-parameters", 169 | "type": "object", 170 | "default": null 171 | }, 172 | "use_git": { 173 | "title": "Enable/Disable git use.", 174 | "description": "Disable it if you want to use only page.meta values.", 175 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#use_git-enabledisable-git-log", 176 | "type": "boolean", 177 | "default": true 178 | }, 179 | "use_material_social_cards": { 180 | "title": "Enable/Disable integration with Social Cards plugin from Material theme.", 181 | "markdownDescription": "https://guts.github.io/mkdocs-rss-plugin/configuration/#disabling-the-plugin", 182 | "type": "boolean", 183 | "default": true 184 | } 185 | }, 186 | "additionalProperties": false 187 | } 188 | }, 189 | "additionalProperties": false 190 | } 191 | ] 192 | } 193 | -------------------------------------------------------------------------------- /docs/theme/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extrahead %} 4 | {% if "rss" in config.plugins %} 5 | 10 | 15 | {% endif %} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json 2 | 3 | # Project information 4 | site_name: MkDocs RSS Plugin 5 | site_description: Documentation about the MkDocs RSS Plugin 6 | site_author: dev@ingeoveritas.com (Julien M. ) 7 | site_url: https://guts.github.io/mkdocs-rss-plugin/ 8 | copyright: "Guts - In Geo Veritas (MIT)" 9 | 10 | # Repository 11 | repo_name: guts/mkdocs-rss-plugin 12 | repo_url: https://github.com/guts/mkdocs-rss-plugin/ 13 | edit_uri: blob/main/docs/ 14 | 15 | # Custom folder structure 16 | docs_dir: "docs/" 17 | use_directory_urls: true 18 | 19 | extra: 20 | social: 21 | - icon: "fontawesome/brands/mastodon" 22 | link: https://mapstodon.space/@geojulien 23 | name: "Follow me on Mastodon" 24 | - icon: "fontawesome/solid/piggy-bank" 25 | link: https://github.com/sponsors/Guts 26 | name: "Sponsor development with Github" 27 | - icon: "simple/liberapay" 28 | link: https://liberapay.com/GeoJulien 29 | name: "Sponsor development with Liberapay" 30 | 31 | # Extensions to enhance markdown 32 | markdown_extensions: 33 | - admonition 34 | - attr_list 35 | - meta 36 | - pymdownx.emoji: 37 | emoji_index: !!python/name:material.extensions.emoji.twemoji 38 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 39 | - pymdownx.highlight: 40 | anchor_linenums: true 41 | line_spans: __span 42 | pygments_lang_class: true 43 | - pymdownx.inlinehilite 44 | - pymdownx.snippets: 45 | base_path: 46 | - "." 47 | check_paths: true 48 | - pymdownx.superfences 49 | - pymdownx.tabbed: 50 | alternate_style: true 51 | slugify: !!python/object/apply:pymdownx.slugs.slugify 52 | kwds: 53 | case: lower 54 | - toc: 55 | permalink: "#" 56 | slugify: !!python/object/apply:pymdownx.slugs.slugify 57 | kwds: 58 | case: lower 59 | 60 | nav: 61 | - Home: index.md 62 | - Settings: configuration.md 63 | - integrations.md 64 | - Contributing: contributing.md 65 | - API: api.md 66 | - Changelog: changelog.md 67 | 68 | plugins: 69 | - git-committers: 70 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_GIT_COMMITTERS, true] 71 | repository: guts/mkdocs-rss-plugin 72 | branch: main 73 | docs_path: docs/ 74 | cache_dir: 75 | !ENV [ 76 | MKDOCS_PLUGIN_GIT_COMMITTERS_CACHE_DIR, 77 | .cache/plugins/git-committers/, 78 | ] 79 | - git-revision-date-localized: 80 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_GIT_DATES, true] 81 | enable_creation_date: true 82 | fallback_to_build_date: true 83 | locale: en 84 | - minify: 85 | minify_html: true 86 | htmlmin_opts: 87 | remove_comments: true 88 | - mkdocstrings: 89 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_MKDOCSTRINGS, true] 90 | default_handler: python 91 | handlers: 92 | python: 93 | options: 94 | docstring_options: 95 | ignore_init_summary: false 96 | trim_doctest_flags: true 97 | docstring_style: google 98 | find_stubs_package: true 99 | heading_level: 3 100 | show_bases: true 101 | show_inheritance_diagram: true 102 | show_root_heading: true 103 | show_source: false 104 | merge_init_into_class: true 105 | 106 | - privacy: 107 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_PRIVACY, true] 108 | cache_dir: 109 | !ENV [MKDOCS_PLUGIN_PRIVACY_EXTERNAL_CACHE_DIR, .cache/plugins/privacy] 110 | - rss: 111 | abstract_chars_count: 160 112 | abstract_delimiter: 113 | date_from_meta: 114 | as_creation: "date" 115 | datetime_format: "%Y-%m-%d %H:%M" 116 | default_timezone: "Europe/Paris" 117 | default_time: "22:00" 118 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_RSS, true] 119 | image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png 120 | json_feed_enabled: true 121 | match_path: ".*" 122 | pretty_print: true 123 | rss_feed_enabled: true 124 | url_parameters: 125 | utm_source: "documentation" 126 | utm_medium: "RSS" 127 | utm_campaign: "feed-syndication" 128 | use_git: true 129 | use_material_social_cards: true 130 | - search: 131 | lang: en 132 | - social: 133 | enabled: !ENV [MKDOCS_ENABLE_PLUGIN_SOCIAL, true] 134 | cache_dir: !ENV [MKDOCS_PLUGIN_SOCIAL_CACHE_DIR, .cache/plugins/social] 135 | cards: true 136 | - termynal: 137 | prompt_literal_start: 138 | - "$" 139 | - ">" 140 | 141 | theme: 142 | name: material 143 | custom_dir: docs/theme/overrides 144 | favicon: assets/logo_rss_plugin_mkdocs.svg 145 | features: 146 | - content.action.edit 147 | - content.action.view 148 | - content.code.copy 149 | - content.tabs.link 150 | - content.tooltips 151 | - navigation.footer 152 | - navigation.tabs 153 | - navigation.tabs.sticky 154 | - navigation.top 155 | - navigation.tracking 156 | - search.highlight 157 | - search.share 158 | - search.suggest 159 | - toc.follow 160 | - toc.integrate 161 | font: 162 | code: Ubuntu Mono 163 | text: Ubuntu 164 | icon: 165 | edit: material/pencil 166 | logo: simple/rss 167 | view: material/eye 168 | tag: 169 | default: fontawesome/solid/tag 170 | language: en 171 | logo: assets/logo_rss_plugin_mkdocs.png 172 | palette: 173 | accent: amber 174 | primary: deep orange 175 | scheme: default 176 | 177 | validation: 178 | links: 179 | absolute_links: warn 180 | not_found: warn 181 | unrecognized_links: warn 182 | nav: 183 | absolute_links: warn 184 | not_found: warn 185 | omitted_files: warn 186 | 187 | watch: 188 | - CONTRIBUTING.md 189 | - README.md 190 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/__about__.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | """ 4 | Metadata about the package to easily retrieve informations about it. 5 | 6 | See: https://packaging.python.org/guides/single-sourcing-package-version/ 7 | """ 8 | 9 | # ############################################################################ 10 | # ########## Libraries ############# 11 | # ################################## 12 | 13 | # standard library 14 | from datetime import date 15 | 16 | # ############################################################################ 17 | # ########## Globals ############# 18 | # ################################ 19 | __all__ = [ 20 | "__author__", 21 | "__copyright__", 22 | "__email__", 23 | "__license__", 24 | "__summary__", 25 | "__title__", 26 | "__title_clean__", 27 | "__uri__", 28 | "__version__", 29 | "__version_info__", 30 | ] 31 | 32 | __author__ = "Julien Moura" 33 | __copyright__ = f"2020 - {date.today().year}, {__author__}" 34 | __email__ = "dev@ingeoveritas.com" 35 | __license__ = "MIT" 36 | __summary__ = ( 37 | "MkDocs plugin which generates a static RSS feed using git log and page.meta." 38 | ) 39 | __title__ = "MkDocs RSS plugin" 40 | __title_clean__ = "".join(e for e in __title__ if e.isalnum()) 41 | __uri__ = "https://github.com/Guts/mkdocs-rss-plugin/" 42 | 43 | __version__ = "1.17.3" 44 | __version_info__ = tuple( 45 | [ 46 | int(num) if num.isdigit() else num 47 | for num in __version__.replace("-", ".", 1).split(".") 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/mkdocs_rss_plugin/__init__.py -------------------------------------------------------------------------------- /mkdocs_rss_plugin/config.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # 3rd party 8 | from mkdocs.config import config_options 9 | from mkdocs.config.base import Config 10 | 11 | # package 12 | from mkdocs_rss_plugin.constants import DEFAULT_CACHE_FOLDER 13 | 14 | # ############################################################################ 15 | # ########## Classes ############### 16 | # ################################## 17 | 18 | 19 | class _DateFromMeta(Config): 20 | """Sub configuration object for related date options.""" 21 | 22 | # for as_creation and as_update 23 | as_creation = config_options.Type(str, default="git") 24 | as_update = config_options.Type(str, default="git") 25 | datetime_format = config_options.Type(str, default="%Y-%m-%d %H:%M") 26 | default_time = config_options.Type(str, default="00:00") 27 | default_timezone = config_options.Type(str, default="UTC") 28 | 29 | 30 | class _FeedsFilenamesConfig(Config): 31 | """Sub configuration for feeds filenames.""" 32 | 33 | json_created = config_options.Type(str, default="feed_json_created.json") 34 | json_updated = config_options.Type(str, default="feed_json_updated.json") 35 | rss_created = config_options.Type(str, default="feed_rss_created.xml") 36 | rss_updated = config_options.Type(str, default="feed_rss_updated.xml") 37 | 38 | 39 | class RssPluginConfig(Config): 40 | """Configuration for RSS plugin for Mkdocs.""" 41 | 42 | abstract_chars_count = config_options.Type(int, default=160) 43 | abstract_delimiter = config_options.Type(str, default="") 44 | categories = config_options.Optional( 45 | config_options.ListOfItems(config_options.Type(str)) 46 | ) 47 | cache_dir = config_options.Type(str, default=f"{DEFAULT_CACHE_FOLDER.resolve()}") 48 | comments_path = config_options.Optional(config_options.Type(str)) 49 | date_from_meta = config_options.SubConfig(_DateFromMeta) 50 | enabled = config_options.Type(bool, default=True) 51 | feeds_filenames = config_options.SubConfig(_FeedsFilenamesConfig) 52 | feed_description = config_options.Optional(config_options.Type(str)) 53 | feed_title = config_options.Optional(config_options.Type(str)) 54 | feed_ttl = config_options.Type(int, default=1440) 55 | image = config_options.Optional(config_options.Type(str)) 56 | json_feed_enabled = config_options.Type(bool, default=True) 57 | length = config_options.Type(int, default=20) 58 | match_path = config_options.Type(str, default=".*") 59 | pretty_print = config_options.Type(bool, default=False) 60 | rss_feed_enabled = config_options.Type(bool, default=True) 61 | url_parameters = config_options.Optional(config_options.Type(dict)) 62 | use_git = config_options.Type(bool, default=True) 63 | use_material_blog = config_options.Type(bool, default=True) 64 | use_material_social_cards = config_options.Type(bool, default=True) 65 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/constants.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | from pathlib import Path 9 | 10 | # package 11 | from mkdocs_rss_plugin import __about__ 12 | 13 | # ############################################################################ 14 | # ########## Globals ############# 15 | # ################################ 16 | 17 | DEFAULT_CACHE_FOLDER: Path = Path(".cache/plugins/rss") 18 | DEFAULT_TEMPLATE_FOLDER: Path = Path(__file__).parent / "templates" 19 | DEFAULT_TEMPLATE_FILENAME: Path = DEFAULT_TEMPLATE_FOLDER / "rss.xml.jinja2" 20 | MKDOCS_LOGGER_NAME: str = "[RSS-plugin]" 21 | REMOTE_REQUEST_HEADERS: dict[str, str] = { 22 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 23 | "User-Agent": f"{__about__.__title__}/{__about__.__version__}", 24 | } 25 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/git_manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/mkdocs_rss_plugin/git_manager/__init__.py -------------------------------------------------------------------------------- /mkdocs_rss_plugin/git_manager/ci.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | from os import environ, path 9 | 10 | # 3rd party 11 | from git import Git 12 | from mkdocs.plugins import get_plugin_logger 13 | 14 | # package 15 | from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME 16 | 17 | # ############################################################################ 18 | # ########## Globals ############# 19 | # ################################ 20 | 21 | logger = get_plugin_logger(MKDOCS_LOGGER_NAME) 22 | 23 | 24 | # ############################################################################ 25 | # ########## Functions ############# 26 | # ################################## 27 | 28 | 29 | class CiHandler: 30 | """Helper class to handle CI specific warnings.""" 31 | 32 | def __init__(self, repo: Git) -> None: 33 | """Initialize the CI handler. 34 | 35 | Args: 36 | repo (Git): Git repository object 37 | """ 38 | self.repo = repo 39 | 40 | def raise_ci_warnings(self) -> None: 41 | """Raise warnings when users use mkdocs-rss-plugin on CI build runners.""" 42 | if not self.is_shallow_clone(): 43 | return None 44 | 45 | n_commits = self.commit_count() 46 | 47 | # Gitlab Runners 48 | if environ.get("GITLAB_CI") and n_commits < 50: 49 | # Default is GIT_DEPTH of 50 for gitlab 50 | logger.info( 51 | """ 52 | Running on a gitlab runner might lead to wrong \ 53 | git revision dates due to a shallow git fetch depth. \ 54 | Make sure to set GIT_DEPTH to 1000 in your .gitlab-ci.yml file. \ 55 | (see https://docs.gitlab.com/ee/user/project/pipelines/settings.html#git-shallow-clone). 56 | """ 57 | ) 58 | 59 | # Github Actions 60 | if environ.get("GITHUB_ACTIONS") and n_commits == 1: 61 | # Default is fetch-depth of 1 for github actions 62 | logger.info( 63 | """ 64 | Running on github actions might lead to wrong \ 65 | git revision dates due to a shallow git fetch depth. \ 66 | Try setting fetch-depth to 0 in your github action \ 67 | (see https://github.com/actions/checkout). 68 | """ 69 | ) 70 | 71 | # Bitbucket pipelines 72 | if environ.get("CI") and n_commits < 50: 73 | # Default is fetch-depth of 50 for bitbucket pipelines 74 | logger.info( 75 | """ 76 | Running on bitbucket pipelines might lead to wrong \ 77 | git revision dates due to a shallow git fetch depth. \ 78 | Try setting "clone: depth" to "full" in your pipeline \ 79 | (see https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/ 80 | and search 'depth'). 81 | """ 82 | ) 83 | 84 | # Azure Devops Pipeline 85 | # Does not limit fetch-depth by default 86 | if environ.get("Agent.Source.Git.ShallowFetchDepth", 10e99) < n_commits: 87 | logger.info( 88 | """ 89 | Running on Azure pipelines \ 90 | with limited fetch-depth might lead to wrong git revision dates \ 91 | due to a shallow git fetch depth. \ 92 | Remove any Shallow Fetch settings \ 93 | (see https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/pipeline-options-for-git?view=azure-devops#shallow-fetch). 94 | """ 95 | ) 96 | 97 | def commit_count(self) -> int: 98 | """Helper function to determine the number of commits in a repository. 99 | 100 | Returns: 101 | int: Number of commits 102 | """ 103 | refs = self.repo.for_each_ref().split("\n") 104 | refs = [x.split()[0] for x in refs] 105 | 106 | counts = [ 107 | int(self.repo.rev_list(x, count=True, first_parent=True)) for x in refs 108 | ] 109 | return max(counts) 110 | 111 | def is_shallow_clone(self) -> bool: 112 | """Helper function to determine if repository is a shallow clone. 113 | 114 | References & Context: 115 | - https://github.com/timvink/mkdocs-rss-plugin/issues/10 116 | - https://stackoverflow.com/a/37203240/5525118 117 | 118 | 119 | Returns: 120 | bool: True if a repo is shallow clone 121 | """ 122 | return path.exists(".git/shallow") 123 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/integrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/mkdocs_rss_plugin/integrations/__init__.py -------------------------------------------------------------------------------- /mkdocs_rss_plugin/integrations/theme_material_base.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | from typing import Optional 9 | 10 | # 3rd party 11 | from mkdocs.config.defaults import MkDocsConfig 12 | from mkdocs.plugins import get_plugin_logger 13 | 14 | # package 15 | from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME 16 | 17 | # conditional 18 | try: 19 | from material import __version__ as material_version 20 | 21 | except ImportError: 22 | material_version = None 23 | 24 | 25 | # ############################################################################ 26 | # ########## Globals ############# 27 | # ################################ 28 | 29 | logger = get_plugin_logger(MKDOCS_LOGGER_NAME) 30 | 31 | # ############################################################################ 32 | # ########## Logic ############### 33 | # ################################ 34 | 35 | 36 | class IntegrationMaterialThemeBase: 37 | # attributes 38 | IS_THEME_MATERIAL: bool = False 39 | IS_INSIDERS: Optional[bool] = False 40 | 41 | def __init__(self, mkdocs_config: MkDocsConfig) -> None: 42 | """Integration instantiation. 43 | 44 | Args: 45 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 46 | """ 47 | # store Mkdocs config as attribute 48 | self.mkdocs_config = mkdocs_config 49 | 50 | self.IS_THEME_MATERIAL = self.is_mkdocs_theme_material() 51 | self.IS_INSIDERS = self.is_mkdocs_theme_material_insiders() 52 | 53 | def is_mkdocs_theme_material( 54 | self, mkdocs_config: Optional[MkDocsConfig] = None 55 | ) -> bool: 56 | """Check if the theme set in mkdocs.yml is material or not. 57 | 58 | Args: 59 | mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. 60 | 61 | Returns: 62 | bool: True if the theme's name is 'material'. False if not. 63 | """ 64 | if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): 65 | mkdocs_config: MkDocsConfig = self.mkdocs_config 66 | 67 | self.IS_THEME_MATERIAL = mkdocs_config.theme.name == "material" 68 | return self.IS_THEME_MATERIAL 69 | 70 | def is_mkdocs_theme_material_insiders(self) -> Optional[bool]: 71 | """Check if the material theme is community or insiders edition. 72 | 73 | Returns: 74 | bool: True if the theme is Insiders edition. False if community. None if 75 | the Material theme is not installed. 76 | """ 77 | if not self.IS_THEME_MATERIAL: 78 | return None 79 | 80 | if material_version is not None and "insiders" in material_version: 81 | logger.debug("Material theme edition INSIDERS") 82 | self.IS_INSIDERS = True 83 | return True 84 | else: 85 | logger.debug("Material theme edition COMMUNITY") 86 | self.IS_INSIDERS = False 87 | return False 88 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/integrations/theme_material_blog_plugin.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | from functools import lru_cache 9 | from pathlib import Path 10 | from typing import Optional 11 | 12 | # 3rd party 13 | from mkdocs.config.defaults import MkDocsConfig 14 | from mkdocs.plugins import get_plugin_logger 15 | from mkdocs.structure.pages import Page 16 | 17 | # package 18 | from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME 19 | from mkdocs_rss_plugin.integrations.theme_material_base import ( 20 | IntegrationMaterialThemeBase, 21 | ) 22 | 23 | # conditional 24 | try: 25 | from material import __version__ as material_version 26 | from material.plugins.blog.plugin import BlogPlugin 27 | 28 | except ImportError: 29 | material_version = None 30 | 31 | 32 | # ############################################################################ 33 | # ########## Globals ############# 34 | # ################################ 35 | 36 | logger = get_plugin_logger(MKDOCS_LOGGER_NAME) 37 | 38 | # ############################################################################ 39 | # ########## Logic ############### 40 | # ################################ 41 | 42 | 43 | class IntegrationMaterialBlog(IntegrationMaterialThemeBase): 44 | # attributes 45 | IS_ENABLED: bool = True 46 | IS_BLOG_PLUGIN_ENABLED: bool = True 47 | 48 | def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None: 49 | """Integration instantiation. 50 | 51 | Args: 52 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 53 | switch_force (bool, optional): option to force integration disabling. Set 54 | it to False to disable it even if Social Cards are enabled in Mkdocs 55 | configuration. Defaults to True. 56 | """ 57 | # check if the integration can be enabled or not 58 | self.IS_BLOG_PLUGIN_ENABLED = self.is_blog_plugin_enabled_mkdocs( 59 | mkdocs_config=mkdocs_config 60 | ) 61 | # if every conditions are True, enable the integration 62 | self.IS_ENABLED = all([self.IS_THEME_MATERIAL, self.IS_BLOG_PLUGIN_ENABLED]) 63 | 64 | # except if the end-user wants to disable it 65 | if switch_force is False: 66 | self.IS_ENABLED = False 67 | logger.debug( 68 | "Integration with Blog (Material theme) is " 69 | "disabled in plugin's option in Mkdocs configuration." 70 | ) 71 | 72 | def is_blog_plugin_enabled_mkdocs( 73 | self, mkdocs_config: Optional[MkDocsConfig] 74 | ) -> bool: 75 | """Check if blog plugin is installed and enabled. 76 | 77 | Args: 78 | mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. 79 | 80 | Returns: 81 | bool: True if the theme material and the plugin blog is enabled. 82 | """ 83 | if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): 84 | mkdocs_config = self.mkdocs_config 85 | 86 | if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config): 87 | logger.debug("Installed theme is not 'material'. Integration disabled.") 88 | return False 89 | 90 | if not mkdocs_config.plugins.get("material/blog"): 91 | logger.debug("Material blog plugin is not listed in configuration.") 92 | self.IS_BLOG_PLUGIN_ENABLED = False 93 | return False 94 | 95 | self.blog_plugin_cfg: BlogPlugin | None = mkdocs_config.plugins.get( 96 | "material/blog" 97 | ) 98 | 99 | if not self.blog_plugin_cfg.config.enabled: 100 | logger.debug("Material blog plugin is installed but disabled.") 101 | self.IS_BLOG_PLUGIN_ENABLED = False 102 | return False 103 | 104 | logger.debug("Material blog plugin is enabled in Mkdocs configuration.") 105 | self.IS_BLOG_PLUGIN_ENABLED = True 106 | 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('email')} ({author_metadata.get('name')})" 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 | ) 149 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/integrations/theme_material_social_plugin.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | import json 9 | from hashlib import md5 10 | from pathlib import Path 11 | from typing import Optional 12 | 13 | # 3rd party 14 | from mkdocs.config.defaults import MkDocsConfig 15 | from mkdocs.plugins import get_plugin_logger 16 | from mkdocs.structure.pages import Page 17 | 18 | # package 19 | from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME 20 | from mkdocs_rss_plugin.integrations.theme_material_base import ( 21 | IntegrationMaterialThemeBase, 22 | ) 23 | from mkdocs_rss_plugin.integrations.theme_material_blog_plugin import ( 24 | IntegrationMaterialBlog, 25 | ) 26 | 27 | # conditional 28 | try: 29 | from material import __version__ as material_version 30 | 31 | except ImportError: 32 | material_version = None 33 | 34 | 35 | # ############################################################################ 36 | # ########## Globals ############# 37 | # ################################ 38 | 39 | logger = get_plugin_logger(MKDOCS_LOGGER_NAME) 40 | 41 | # ############################################################################ 42 | # ########## Logic ############### 43 | # ################################ 44 | 45 | 46 | class IntegrationMaterialSocialCards(IntegrationMaterialThemeBase): 47 | # attributes 48 | IS_ENABLED: bool = True 49 | IS_SOCIAL_PLUGIN_ENABLED: bool = True 50 | IS_SOCIAL_PLUGIN_CARDS_ENABLED: bool = True 51 | CARDS_MANIFEST: Optional[dict] = None 52 | 53 | def __init__(self, mkdocs_config: MkDocsConfig, switch_force: bool = True) -> None: 54 | """Integration instantiation. 55 | 56 | Args: 57 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 58 | switch_force (bool, optional): option to force integration disabling. Set 59 | it to False to disable it even if Social Cards are enabled in Mkdocs 60 | configuration. Defaults to True. 61 | """ 62 | # support cards for blog posts 63 | self.integration_material_blog = IntegrationMaterialBlog( 64 | mkdocs_config=mkdocs_config 65 | ) 66 | # check if the integration can be enabled or not 67 | self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = ( 68 | self.is_social_plugin_and_cards_enabled_mkdocs(mkdocs_config=mkdocs_config) 69 | ) 70 | 71 | # if every conditions are True, enable the integration 72 | self.IS_ENABLED = all( 73 | [ 74 | self.IS_THEME_MATERIAL, 75 | self.IS_SOCIAL_PLUGIN_ENABLED, 76 | self.IS_SOCIAL_PLUGIN_CARDS_ENABLED, 77 | ] 78 | ) 79 | 80 | # except if the end-user wants to disable it 81 | if switch_force is False: 82 | self.IS_ENABLED = False 83 | logger.debug( 84 | "Integration with Social Cards (Material theme) is " 85 | "disabled in plugin's option in Mkdocs configuration." 86 | ) 87 | 88 | # if enabled, save some config elements 89 | if self.IS_ENABLED: 90 | self.mkdocs_site_url = mkdocs_config.site_url 91 | self.mkdocs_site_build_dir = mkdocs_config.site_dir 92 | self.social_cards_dir = self.get_social_cards_dir( 93 | mkdocs_config=mkdocs_config 94 | ) 95 | self.social_cards_assets_dir = self.get_social_cards_build_dir( 96 | mkdocs_config=mkdocs_config 97 | ) 98 | self.social_cards_cache_dir = self.get_social_cards_cache_dir( 99 | mkdocs_config=mkdocs_config 100 | ) 101 | 102 | if self.is_mkdocs_theme_material_insiders(): 103 | self.load_cache_cards_manifest() 104 | 105 | # store some attributes used to compute social card hash 106 | self.site_name = mkdocs_config.site_name 107 | self.site_description = mkdocs_config.site_description or "" 108 | 109 | def is_social_plugin_enabled_mkdocs( 110 | self, mkdocs_config: Optional[MkDocsConfig] = None 111 | ) -> bool: 112 | """Check if social plugin is installed and enabled. 113 | 114 | Args: 115 | mkdocs_config (Optional[MkDocsConfig]): Mkdocs website configuration object. 116 | 117 | Returns: 118 | bool: True if the theme material and the plugin social cards is enabled. 119 | """ 120 | if mkdocs_config is None and isinstance(self.mkdocs_config, MkDocsConfig): 121 | mkdocs_config = self.mkdocs_config 122 | 123 | if not self.is_mkdocs_theme_material(mkdocs_config=mkdocs_config): 124 | logger.debug("Installed theme is not 'material'. Integration disabled.") 125 | return False 126 | 127 | if not mkdocs_config.plugins.get("material/social"): 128 | logger.debug("Material Social plugin not listed in configuration.") 129 | return False 130 | 131 | social_plugin_cfg = mkdocs_config.plugins.get("material/social") 132 | 133 | if not social_plugin_cfg.config.enabled: 134 | logger.debug("Material Social plugin is installed but disabled.") 135 | self.IS_SOCIAL_PLUGIN_ENABLED = False 136 | return False 137 | 138 | logger.debug("Material Social plugin is enabled in Mkdocs configuration.") 139 | self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True 140 | return True 141 | 142 | def is_social_plugin_and_cards_enabled_mkdocs( 143 | self, mkdocs_config: MkDocsConfig 144 | ) -> bool: 145 | """Check if social cards plugin is enabled. 146 | 147 | Args: 148 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 149 | 150 | Returns: 151 | bool: True if the theme material and the plugin social cards is enabled. 152 | """ 153 | if not self.is_social_plugin_enabled_mkdocs(mkdocs_config=mkdocs_config): 154 | return False 155 | 156 | social_plugin_cfg = mkdocs_config.plugins.get("material/social") 157 | 158 | if not social_plugin_cfg.config.cards: 159 | logger.debug( 160 | "Material Social plugin is installed, present but cards are disabled." 161 | ) 162 | self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = False 163 | return False 164 | 165 | logger.debug("Material Social cards are enabled in Mkdocs configuration.") 166 | self.IS_SOCIAL_PLUGIN_CARDS_ENABLED = True 167 | return True 168 | 169 | def is_social_plugin_enabled_page( 170 | self, mkdocs_page: Page, fallback_value: bool = True 171 | ) -> bool: 172 | """Check if the social plugin is enabled or disabled for a specific page. Plugin 173 | has to be enabled in Mkdocs configuration before. 174 | 175 | Args: 176 | mkdocs_page (Page): Mkdocs page object. 177 | fallback_value (bool, optional): fallback value. It might be the 178 | 'plugins.social.cards.enabled' option in Mkdocs config. Defaults to True. 179 | 180 | Returns: 181 | bool: True if the social cards are enabled for a page. 182 | """ 183 | return mkdocs_page.meta.get("social", {"cards": fallback_value}).get( 184 | "cards", fallback_value 185 | ) 186 | 187 | def load_cache_cards_manifest(self) -> Optional[dict]: 188 | """Load social cards manifest if the file exists. 189 | 190 | Returns: 191 | dict | None: manifest as dict or None if the file does not exist 192 | """ 193 | cache_cards_manifest = Path(self.social_cards_cache_dir).joinpath( 194 | "manifest.json" 195 | ) 196 | if not cache_cards_manifest.is_file(): 197 | logger.debug( 198 | "Material Social Cards cache manifest file not found: " 199 | f"{cache_cards_manifest}" 200 | ) 201 | return None 202 | 203 | with cache_cards_manifest.open(mode="r", encoding="UTF-8") as manifest: 204 | self.CARDS_MANIFEST = json.load(manifest) 205 | logger.debug( 206 | f"Material Social Cards cache manifest loaded from {cache_cards_manifest}" 207 | ) 208 | 209 | return self.CARDS_MANIFEST 210 | 211 | def get_social_cards_dir(self, mkdocs_config: MkDocsConfig) -> str: 212 | """Get Social Cards folder relative to Mkdocs site_dir. 213 | See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir 214 | 215 | Args: 216 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 217 | 218 | Returns: 219 | str: The cards_dir if the theme material and the plugin social cards is enabled. 220 | """ 221 | social_plugin_cfg = mkdocs_config.plugins.get("material/social") 222 | 223 | logger.debug( 224 | "Material Social cards folder in Mkdocs build directory: " 225 | f"{social_plugin_cfg.config.cards_dir}." 226 | ) 227 | 228 | return social_plugin_cfg.config.cards_dir 229 | 230 | def get_social_cards_build_dir(self, mkdocs_config: MkDocsConfig) -> Path: 231 | """Get Social Cards folder within Mkdocs site_dir. 232 | See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir 233 | 234 | Args: 235 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 236 | 237 | Returns: 238 | Path: Absolute path of the assets dir if the theme material and the plugin 239 | social cards is enabled. 240 | """ 241 | cards_dir = self.get_social_cards_dir(mkdocs_config=mkdocs_config) 242 | 243 | return Path(cards_dir).resolve() 244 | 245 | def get_social_cards_cache_dir(self, mkdocs_config: MkDocsConfig) -> Path: 246 | """Get Social Cards folder within Mkdocs site_dir. 247 | See: https://squidfunk.github.io/mkdocs-material/plugins/social/#config.cards_dir 248 | 249 | Args: 250 | mkdocs_config (MkDocsConfig): Mkdocs website configuration object. 251 | 252 | Returns: 253 | Path: The cache dir if the theme material and the plugin social cards is enabled. 254 | """ 255 | social_plugin_cfg = mkdocs_config.plugins.get("material/social") 256 | self.social_cards_cache_dir = Path(social_plugin_cfg.config.cache_dir).resolve() 257 | 258 | logger.debug( 259 | "Material Social cards cache folder: " f"{self.social_cards_cache_dir}." 260 | ) 261 | 262 | return self.social_cards_cache_dir 263 | 264 | def get_social_card_build_path_for_page( 265 | self, mkdocs_page: Page, mkdocs_site_dir: Optional[str] = None 266 | ) -> Optional[Path]: 267 | """Get social card path in Mkdocs build dir for a specific page. 268 | 269 | Args: 270 | mkdocs_page (Page): Mkdocs page object. 271 | mkdocs_site_dir (Optional[str], optional): Mkdocs build site dir. If None, the 272 | 'class.mkdocs_site_build_dir' is used. is Defaults to None. 273 | 274 | Returns: 275 | Path: path to the image once published 276 | """ 277 | if mkdocs_site_dir is None and self.mkdocs_site_build_dir: 278 | mkdocs_site_dir = self.mkdocs_site_build_dir 279 | 280 | # if page is a blog post 281 | if ( 282 | self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED 283 | and self.integration_material_blog.is_page_a_blog_post(mkdocs_page) 284 | ): 285 | expected_built_card_path = Path( 286 | f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/" 287 | f"{Path(mkdocs_page.file.dest_uri).parent}.png" 288 | ) 289 | else: 290 | expected_built_card_path = Path( 291 | f"{mkdocs_site_dir}/{self.social_cards_assets_dir}/" 292 | f"{Path(mkdocs_page.file.src_uri).with_suffix('.png')}" 293 | ) 294 | 295 | if expected_built_card_path.is_file(): 296 | logger.debug( 297 | f"Social card file found in build folder: {expected_built_card_path}" 298 | ) 299 | return expected_built_card_path 300 | else: 301 | logger.debug( 302 | f"Social card not found in build folder: {expected_built_card_path}" 303 | ) 304 | return None 305 | 306 | def get_social_card_cache_path_for_page(self, mkdocs_page: Page) -> Optional[Path]: 307 | """Get social card path in social plugin cache folder for a specific page. 308 | 309 | Note: 310 | As we write this code (June 2024), the cache mechanism in Insiders edition 311 | has stores images directly with the corresponding Page's path and name and 312 | keep a correspondance matrix with hashes in a manifest.json; 313 | the cache mechanism in Community edition uses the hash as file names without 314 | any exposed matching criteria. 315 | 316 | Args: 317 | mkdocs_page (Page): Mkdocs page object. 318 | 319 | Returns: 320 | Path: path to the image in local cache folder if it exists 321 | """ 322 | if self.IS_INSIDERS: 323 | 324 | # if page is a blog post 325 | if ( 326 | self.integration_material_blog.IS_BLOG_PLUGIN_ENABLED 327 | and self.integration_material_blog.is_page_a_blog_post(mkdocs_page) 328 | ): 329 | expected_cached_card_path = self.social_cards_cache_dir.joinpath( 330 | f"assets/images/social/{Path(mkdocs_page.file.dest_uri).parent}.png" 331 | ) 332 | else: 333 | expected_cached_card_path = self.social_cards_cache_dir.joinpath( 334 | f"assets/images/social/{Path(mkdocs_page.file.src_uri).with_suffix('.png')}" 335 | ) 336 | 337 | if expected_cached_card_path.is_file(): 338 | logger.debug( 339 | f"Social card file found in cache folder: {expected_cached_card_path}" 340 | ) 341 | return expected_cached_card_path 342 | else: 343 | logger.debug( 344 | f"Social card not found in cache folder: {expected_cached_card_path}" 345 | ) 346 | 347 | else: 348 | if "description" in mkdocs_page.meta: 349 | description = mkdocs_page.meta["description"] 350 | else: 351 | description = self.site_description 352 | 353 | page_hash = md5( 354 | "".join( 355 | [ 356 | self.site_name, 357 | str(mkdocs_page.meta.get("title", mkdocs_page.title)), 358 | description, 359 | ] 360 | ).encode("utf-8") 361 | ) 362 | expected_cached_card_path = self.social_cards_cache_dir.joinpath( 363 | f"{page_hash.hexdigest()}.png" 364 | ) 365 | 366 | if expected_cached_card_path.is_file(): 367 | logger.debug( 368 | f"Social card file found in cache folder: {expected_cached_card_path}" 369 | ) 370 | return expected_cached_card_path 371 | else: 372 | logger.debug(f"Not found: {expected_cached_card_path}") 373 | return None 374 | 375 | def get_social_card_url_for_page( 376 | self, 377 | mkdocs_page: Page, 378 | mkdocs_site_url: Optional[str] = None, 379 | ) -> str: 380 | """Get social card URL for a specific page in documentation. 381 | 382 | Args: 383 | mkdocs_page (Page): Mkdocs page object. 384 | mkdocs_site_url (Optional[str], optional): Mkdocs site URL. If None, the 385 | 'class.mkdocs_site_url' is used. is Defaults to None. 386 | 387 | Returns: 388 | str: URL to the image once published 389 | """ 390 | if mkdocs_site_url is None and self.mkdocs_site_url: 391 | mkdocs_site_url = self.mkdocs_site_url 392 | 393 | # As of mkdocs-material 9.6.5, social cards are always stored in the 394 | # matching src path in the build folder, regardless of the page type. 395 | page_social_card = ( 396 | f"{mkdocs_site_url}{self.social_cards_dir}/" 397 | f"{Path(mkdocs_page.file.src_uri).with_suffix('.png')}" 398 | ) 399 | logger.debug(f"Use social card url: {page_social_card}") 400 | 401 | return page_social_card 402 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/models.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard 8 | from dataclasses import dataclass, field 9 | from datetime import datetime 10 | from pathlib import Path 11 | from typing import Optional 12 | 13 | # package modules 14 | from mkdocs_rss_plugin.__about__ import __title__, __version__ 15 | 16 | # ############################################################################ 17 | # ########## Classes ############### 18 | # ################################## 19 | 20 | 21 | @dataclass 22 | class PageInformation: 23 | """Object describing a page information gathered from Mkdocs and used as feed's item.""" 24 | 25 | abs_path: Optional[Path] = None 26 | categories: Optional[list] = None 27 | authors: Optional[tuple] = None 28 | created: Optional[datetime] = None 29 | description: Optional[str] = None 30 | guid: Optional[str] = None 31 | image: Optional[str] = None 32 | title: Optional[str] = None 33 | updated: Optional[datetime] = None 34 | url_comments: Optional[str] = None 35 | url_full: Optional[str] = None 36 | 37 | 38 | @dataclass 39 | class RssFeedBase: 40 | """Object describing a feed.""" 41 | 42 | author: Optional[str] = None 43 | buildDate: Optional[str] = None 44 | copyright: Optional[str] = None 45 | description: Optional[str] = None 46 | entries: list[PageInformation] = field(default_factory=list) 47 | generator: str = f"{__title__} - v{__version__}" 48 | html_url: Optional[str] = None 49 | json_url: Optional[str] = None 50 | language: Optional[str] = None 51 | logo_url: Optional[str] = None 52 | pubDate: Optional[str] = None 53 | repo_url: Optional[str] = None 54 | rss_url: Optional[str] = None 55 | title: Optional[str] = None 56 | ttl: Optional[int] = None 57 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/templates/rss.xml.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {# Mandatory elements #} 5 | {% if feed.title is not none %}{{ feed.title|e }}{% endif %} 6 | {% if feed.description is not none %}{{ feed.description|e }}{% endif %} 7 | {% if feed.html_url is not none %}{{ feed.html_url }}{% endif %} 8 | {% if feed.rss_url is not none %}{% endif %} 9 | 10 | {# Optional elements #} 11 | {% if feed.author is not none %}{{ feed.author }}{% endif %} 12 | {% if feed.repo_url is not none %}{{ feed.repo_url }}{% endif %} 13 | {% if feed.language is not none %}{{ feed.language }}{% endif %} 14 | 15 | {# Timestamps and frequency #} 16 | {{ feed.pubDate }} 17 | {{ feed.buildDate }} 18 | {{ feed.ttl }} 19 | 20 | {# Credits #} 21 | {{ feed.generator }} 22 | 23 | {# Feed illustration #} 24 | {% if feed.logo_url is defined %} 25 | 26 | {{ feed.logo_url }} 27 | {{ feed.title }} 28 | {% if feed.html_url is not none %}{{ feed.html_url }}{% endif %} 29 | 30 | {% endif %} 31 | 32 | {# Entries #} 33 | {% for item in feed.entries %} 34 | 35 | {{ item.title|e }} 36 | {# Authors loop #} 37 | {% if item.authors is not none %} 38 | {% for author in item.authors %} 39 | {{ author }} 40 | {% endfor %} 41 | {% endif %} 42 | {# Categories loop #} 43 | {% if item.categories is not none %} 44 | {% for categorie in item.categories %} 45 | {{ categorie }} 46 | {% endfor %} 47 | {% endif %} 48 | {{ item.description|e }} 49 | {% if item.link is not none %}{{ item.link|e }}{% endif %} 50 | {{ item.pubDate }} 51 | {% if item.link is not none %}{{ feed.title }}{% endif %} 52 | {% if item.comments_url is not none %}{{ item.comments_url|e }}{% endif %} 53 | {% if item.guid is not none %}{{ item.guid }}{% endif %} 54 | {% if item.image is not none %} 55 | 56 | {% endif %} 57 | 58 | {% endfor %} 59 | 60 | 61 | -------------------------------------------------------------------------------- /mkdocs_rss_plugin/timezoner.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | 4 | """ 5 | Manage timezones for pages date(time)s using zoneinfo module, added in Python 3.9. 6 | 7 | """ 8 | 9 | # ############################################################################ 10 | # ########## Libraries ############# 11 | # ################################## 12 | 13 | # standard library 14 | from datetime import datetime, timezone 15 | from zoneinfo import ZoneInfo 16 | 17 | # 3rd party 18 | from mkdocs.plugins import get_plugin_logger 19 | 20 | # package 21 | from mkdocs_rss_plugin.constants import MKDOCS_LOGGER_NAME 22 | 23 | # ############################################################################ 24 | # ########## Globals ############# 25 | # ################################ 26 | 27 | 28 | logger = get_plugin_logger(MKDOCS_LOGGER_NAME) 29 | 30 | 31 | # ############################################################################ 32 | # ########## Functions ########### 33 | # ################################ 34 | 35 | 36 | def set_datetime_zoneinfo( 37 | input_datetime: datetime, config_timezone: str = "UTC" 38 | ) -> datetime: 39 | """Apply timezone to a naive datetime. 40 | 41 | Args: 42 | input_datetime (datetime): offset-naive datetime 43 | config_timezone (str, optional): name of timezone as registered in IANA 44 | database. Defaults to "UTC". Example : Europe/Paris. 45 | 46 | Returns: 47 | datetime: offset-aware datetime 48 | """ 49 | if input_datetime.tzinfo: 50 | return input_datetime 51 | elif not config_timezone: 52 | return input_datetime.replace(tzinfo=timezone.utc) 53 | else: 54 | config_tz = ZoneInfo(config_timezone) 55 | return input_datetime.replace(tzinfo=config_tz) 56 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/base.txt 2 | 3 | -e "." 4 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | # Common requirements 2 | # ----------------------- 3 | 4 | cachecontrol[filecache] >=0.14,<1 5 | GitPython>=3.1.43,<3.2 6 | mkdocs>=1.6.1,<2 7 | requests>=2.31,<3 8 | tzdata==2024.* ; python_version >= "3.9" and sys_platform == "win32" 9 | -------------------------------------------------------------------------------- /requirements/development.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | 3 | # Development 4 | # ----------------------- 5 | black 6 | flake8>=7.1,<8 7 | flake8-bugbear>=24.10 8 | flake8-builtins>=2.5 9 | flake8-eradicate>=1.5 10 | flake8-isort>=6.1.1 11 | pre-commit>=3.7,<5 12 | -------------------------------------------------------------------------------- /requirements/documentation.txt: -------------------------------------------------------------------------------- 1 | # Documentation 2 | # ----------------------- 3 | 4 | mkdocs-git-committers-plugin-2>=2.4.1,<2.6 5 | mkdocs-git-revision-date-localized-plugin>=1.3,<1.5 6 | mkdocs-material[imaging]>=9.5.47,<10 7 | mkdocs-minify-plugin>=0.8,<0.9 8 | mkdocstrings-python>=1.16.2,<1.17 9 | termynal>=0.12.2,<0.14 10 | -------------------------------------------------------------------------------- /requirements/testing.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | 3 | # Testing 4 | # ------- 5 | 6 | feedparser>=6.0.11,<6.1 7 | jsonfeed-util>=1.2,<2 8 | mkdocs-material[imaging]>=9.5.47 9 | pytest-cov>=6,<7 10 | validator-collection>=1.5,<1.6 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # -- Packaging -------------------------------------- 2 | [bdist_wheel] 3 | universal = 1 4 | 5 | [metadata] 6 | description-file = README.md 7 | 8 | # -- Code quality ------------------------------------ 9 | [flake8] 10 | count = True 11 | docstring-convention = google 12 | exclude = 13 | # No need to traverse our git directory 14 | .git, 15 | # There's no value in checking cache directories 16 | __pycache__, 17 | # The conf file is mostly autogenerated, ignore it 18 | docs/conf.py, 19 | # The old directory contains Flake8 2.0 20 | old, 21 | # This contains our built documentation 22 | build, 23 | # This contains builds of flake8 that we don't want to check 24 | dist, 25 | # This contains local virtual environments 26 | .venv*, 27 | # do not watch on tests 28 | tests 29 | ignore = E121,E123,E126,E203,E226,E24,E704,W503,W504 30 | max-complexity = 15 31 | max-doc-length = 130 32 | max-line-length = 100 33 | statistics = True 34 | tee = True 35 | 36 | [isort] 37 | ensure_newline_before_comments = True 38 | force_grid_wrap = 0 39 | include_trailing_comma = True 40 | line_length = 88 41 | multi_line_output = 3 42 | profile = black 43 | use_parentheses = True 44 | 45 | # -- Tests ---------------------------------------------- 46 | [tool:pytest] 47 | addopts = 48 | --junitxml=junit/test-results.xml 49 | --cov-config=setup.cfg 50 | --cov=mkdocs_rss_plugin 51 | --cov-report=html 52 | --cov-report=term 53 | --cov-report=xml 54 | --ignore=tests/_wip/ 55 | junit_family = xunit2 56 | minversion = 5.0 57 | norecursedirs = .* build dev development dist docs CVS fixtures _darcs {arch} *.egg venv _wip 58 | python_files = test_*.py 59 | testpaths = tests 60 | 61 | [coverage:run] 62 | disable_warnings=include-ignored 63 | branch = True 64 | include = 65 | mkdocs_rss_plugin/* 66 | omit = 67 | .venv/* 68 | docs/* 69 | *tests* 70 | 71 | [coverage:report] 72 | exclude_lines = 73 | if self.debug: 74 | pragma: no cover 75 | raise NotImplementedError 76 | if __name__ == .__main__.: 77 | 78 | ignore_errors = True 79 | show_missing = True 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa: E265 2 | 3 | # ############################################################################ 4 | # ########## Libraries ############# 5 | # ################################## 6 | 7 | # standard library 8 | from pathlib import Path 9 | from typing import Union 10 | 11 | # 3rd party 12 | from setuptools import find_packages, setup 13 | 14 | # package (to get version) 15 | from mkdocs_rss_plugin import __about__ 16 | 17 | # ############################################################################ 18 | # ########## Globals ############# 19 | # ################################ 20 | 21 | HERE = Path(__file__).parent 22 | README = (HERE / "README.md").read_text() 23 | 24 | # ############################################################################ 25 | # ########### Functions ############ 26 | # ################################## 27 | 28 | 29 | def load_requirements(requirements_files: Union[Path, list[Path]]) -> list: 30 | """Helper to load requirements list from a path or a list of paths. 31 | 32 | Args: 33 | requirements_files (Union[Path, List[Path]]): path or list to paths of 34 | requirements file(s) 35 | 36 | Returns: 37 | list: list of requirements loaded from file(s) 38 | """ 39 | out_requirements = [] 40 | 41 | if isinstance(requirements_files, Path): 42 | requirements_files = [ 43 | requirements_files, 44 | ] 45 | 46 | for requirements_file in requirements_files: 47 | with requirements_file.open(encoding="UTF-8") as f: 48 | out_requirements += [ 49 | line 50 | for line in f.read().splitlines() 51 | if not line.startswith(("#", "-")) and len(line) 52 | ] 53 | 54 | return out_requirements 55 | 56 | 57 | # ############################################################################ 58 | # ########## Setup ############# 59 | # ############################## 60 | setup( 61 | name="mkdocs-rss-plugin", 62 | version=__about__.__version__, 63 | author=__about__.__author__, 64 | author_email=__about__.__email__, 65 | description=__about__.__summary__, 66 | license=__about__.__license__, 67 | long_description=README, 68 | long_description_content_type="text/markdown", 69 | project_urls={ 70 | "Docs": "https://guts.github.io/mkdocs-rss-plugin/", 71 | "Bug Reports": f"{__about__.__uri__}issues/", 72 | "Source": __about__.__uri__, 73 | }, 74 | # packaging 75 | packages=find_packages( 76 | exclude=["contrib", "docs", "*.tests", "*.tests.*", "tests.*", "tests"] 77 | ), 78 | include_package_data=True, 79 | # run 80 | entry_points={"mkdocs.plugins": ["rss = mkdocs_rss_plugin.plugin:GitRssPlugin"]}, 81 | # dependencies 82 | python_requires=">=3.9, <4", 83 | extras_require={ 84 | # tooling 85 | "dev": load_requirements(HERE / "requirements/development.txt"), 86 | "doc": load_requirements(HERE / "requirements/documentation.txt"), 87 | "test": load_requirements(HERE / "requirements/testing.txt"), 88 | }, 89 | install_requires=load_requirements(HERE / "requirements/base.txt"), 90 | # metadata 91 | keywords="mkdocs rss git plugin", 92 | classifiers=[ 93 | "Intended Audience :: Developers", 94 | "Intended Audience :: Information Technology", 95 | "Programming Language :: Python :: 3", 96 | "Programming Language :: Python :: 3.9", 97 | "Programming Language :: Python :: 3.10", 98 | "Programming Language :: Python :: 3.11", 99 | "Programming Language :: Python :: 3.12", 100 | "Programming Language :: Python :: 3.13", 101 | "Programming Language :: Python :: Implementation :: CPython", 102 | "Development Status :: 5 - Production/Stable", 103 | "License :: OSI Approved :: MIT License", 104 | "Operating System :: OS Independent", 105 | "Topic :: Documentation", 106 | "Topic :: Text Processing :: Markup :: Markdown", 107 | ], 108 | ) 109 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=Guts_mkdocs-rss-plugin 2 | 3 | # Because Community Edition doesn't support multiple branches, 4 | # you should only analyze your main branch. 5 | # You can restrict analysis to your main branch by adding the branch name to the only parameter. 6 | # only=main 7 | 8 | # Python versions 9 | sonar.python.version=3.9, 3.10, 3.11, 3.12, 3.13 10 | 11 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 12 | sonar.sources=mkdocs_rss_plugin 13 | 14 | # Encoding of the source code. Default is default system encoding 15 | sonar.sourceEncoding=UTF-8 16 | 17 | # Python configuration 18 | sonar.coverage.exclusions=**__init__**,tests/**,*.py,docs/** 19 | sonar.cpd.exclusions=tests/** 20 | sonar.exclusions=*.xml,doc/**,tests/dev/**,tests/fixtures/** 21 | sonar.language=python3 22 | sonar.python.coverage.reportPaths=coverage.xml 23 | sonar.python.file.suffixes=py 24 | sonar.python.xunit.reportPath=junit/test-results.xml 25 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guts/mkdocs-rss-plugin/2352783ec50273bb0f27ee916654fe4f3c69fcfb/tests/__init__.py -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Base class for unit tests.""" 4 | 5 | # ############################################################################# 6 | # ########## Libraries ############# 7 | # ################################## 8 | 9 | # Standard library 10 | import logging 11 | import shutil 12 | import unittest 13 | from pathlib import Path 14 | 15 | # 3rd party 16 | from click.testing import CliRunner 17 | 18 | # MkDocs 19 | from mkdocs.__main__ import build_command 20 | from mkdocs.config import load_config 21 | from mkdocs.config.base import Config 22 | 23 | # package 24 | from mkdocs_rss_plugin.plugin import GitRssPlugin 25 | 26 | # ############################################################################# 27 | # ########## Classes ############### 28 | # ################################## 29 | 30 | 31 | class BaseTest(unittest.TestCase): 32 | """Base test class to be inherited by MkDocs plugins tests.""" 33 | 34 | def get_plugin_config_from_mkdocs( 35 | self, mkdocs_yml_filepath: Path, plugin_name: str 36 | ) -> Config: 37 | """Load a mkdocs.yml and returns the configuration for the specified plugin. 38 | 39 | :param mkdocs_yml_filepath: path to MkDocs configuration file 40 | :type mkdocs_yml_filepath: Path 41 | :param plugin_name: plugin name (as mentionned into the mkdocs.yml) 42 | :type plugin_name: str 43 | 44 | :return: plugin configuration loaded by MkDocs. Empty if specified plugin is \ 45 | not enabled into the mkdocs.yml. 46 | :rtype: Config 47 | """ 48 | # instantiate plugin 49 | cfg_mkdocs = load_config(str(mkdocs_yml_filepath.resolve())) 50 | 51 | plugins = cfg_mkdocs.plugins 52 | rss_plugin_instances = [ 53 | plg for plg in plugins.items() if isinstance(plg[1], GitRssPlugin) 54 | ] 55 | if not len(rss_plugin_instances): 56 | logging.warning( 57 | f"Plugin {plugin_name} is not part of enabled plugin in the MkDocs " 58 | "configuration file: {mkdocs_yml_filepath}" 59 | ) 60 | return cfg_mkdocs 61 | 62 | if len(rss_plugin_instances) == 1: 63 | plugin = rss_plugin_instances[0][1] 64 | self.assertIsInstance(plugin, GitRssPlugin) 65 | elif len(rss_plugin_instances) >= 1: 66 | plugin = rss_plugin_instances[1][1] 67 | self.assertIsInstance(plugin, GitRssPlugin) 68 | 69 | logging.info(f"Fixture configuration loaded: {plugin.on_config(cfg_mkdocs)}") 70 | 71 | return plugin.config 72 | 73 | def build_docs_setup( 74 | self, 75 | testproject_path: Path, 76 | mkdocs_yml_filepath: Path, 77 | output_path: Path, 78 | strict: bool = True, 79 | ): 80 | """Runs the `mkdocs build` command. 81 | 82 | :param testproject_path: Path to test project 83 | :type testproject_path: Path 84 | :param mkdocs_yml_filepath: filepath to the MkDocs configuration file (.yml) \ 85 | passed as parameter to the `--config-file` argument. 86 | :type mkdocs_yml_filepath: Path 87 | :param output_path: folder path where to store the built website \ 88 | passed as parameter to the `--site-dir` argument. 89 | :type output_path: Path 90 | 91 | :return: Object with results of command 92 | :rtype: click.testing.Result 93 | """ 94 | cmd_args = [ 95 | "--clean", 96 | "--config-file", 97 | f"{mkdocs_yml_filepath}", 98 | "--site-dir", 99 | f"{output_path}", 100 | "--verbose", 101 | ] 102 | if strict: 103 | cmd_args.append("--strict") 104 | 105 | try: 106 | runner = CliRunner() 107 | run = runner.invoke(build_command, cmd_args) 108 | return run 109 | except Exception as err: 110 | logging.critical(err) 111 | return False 112 | 113 | def setup_clean_mkdocs_folder( 114 | self, mkdocs_yml_filepath: Path, output_path: Path 115 | ) -> Path: 116 | """Sets up a clean mkdocs directory: 117 | 118 | outputpath/testproject 119 | ├── docs/ 120 | └── mkdocs.yml 121 | 122 | :param mkdocs_yml_filepath: Path of mkdocs.yml file to use 123 | :type mkdocs_yml_filepath: Path 124 | :param output_path: Path of folder in which to create mkdocs project 125 | :type output_path: Path 126 | 127 | :return: Path to test project 128 | :rtype: Path 129 | """ 130 | testproject_path = output_path / "testproject" 131 | 132 | # Create empty 'testproject' folder 133 | if testproject_path.exists(): 134 | logging.warning( 135 | """This command does not work on windows. 136 | Refactor your test to use setup_clean_mkdocs_folder() only once""" 137 | ) 138 | shutil.rmtree(testproject_path) 139 | 140 | # Copy correct mkdocs.yml file and our test 'docs/' 141 | shutil.copytree("tests/fixtures/docs", testproject_path / "docs") 142 | shutil.copyfile(mkdocs_yml_filepath, testproject_path / "mkdocs.yml") 143 | 144 | return testproject_path 145 | 146 | # def validate_mkdocs_file(self, temp_path: Path, mkdocs_yml_filepath: str): 147 | # """ 148 | # Creates a clean mkdocs project 149 | # for a mkdocs YML file, builds and validates it. 150 | 151 | # Args: 152 | # temp_path (PosixPath): Path to temporary folder 153 | # mkdocs_yml_filepath (PosixPath): Path to mkdocs.yml file 154 | # """ 155 | # testproject_path = self.setup_clean_mkdocs_folder( 156 | # mkdocs_yml_filepath=mkdocs_yml_filepath, output_path=temp_path 157 | # ) 158 | # # setup_commit_history(testproject_path) 159 | # # result = build_docs_setup(testproject_path) 160 | # # assert result.exit_code == 0, "'mkdocs build' command failed" 161 | 162 | # # # validate build with locale retrieved from mkdocs config file 163 | # # validate_build( 164 | # # testproject_path, plugin_config=get_plugin_config_from_mkdocs(mkdocs_yml_filepath) 165 | # # ) 166 | 167 | # return testproject_path 168 | 169 | 170 | # ############################################################################## 171 | # ##### Stand alone program ######## 172 | # ################################## 173 | if __name__ == "__main__": 174 | """Quick and dirty tests""" 175 | import feedparser 176 | 177 | base = BaseTest() 178 | plg_cfg = base.get_plugin_config_from_mkdocs(Path("mkdocs.yml"), "rss") 179 | print(type(plg_cfg)) 180 | # truc = base.setup_clean_mkdocs_folder( 181 | # mkdocs_yml_filepath=Path("mkdocs.yml"), output_path=Path("tests/fixtures/") 182 | # ) 183 | # print(truc) 184 | run_result = base.build_docs_setup( 185 | testproject_path="docs", 186 | mkdocs_yml_filepath=Path("mkdocs.yml"), 187 | output_path=Path("zoubi"), 188 | ) 189 | print(type(run_result), run_result.exit_code) 190 | d = feedparser.parse("zoubi/feed_rss_created.xml") 191 | print(d.version) 192 | -------------------------------------------------------------------------------- /tests/dev/dev_cached_http.py: -------------------------------------------------------------------------------- 1 | import http.client 2 | import logging 3 | from pathlib import Path 4 | 5 | import requests 6 | from cachecontrol import CacheControl 7 | from cachecontrol.caches.file_cache import FileCache 8 | 9 | http.client.HTTPConnection.debuglevel = 1 10 | logging.basicConfig() 11 | logging.getLogger().setLevel(logging.DEBUG) 12 | req_log = logging.getLogger("requests.packages.urllib3") 13 | req_log.setLevel(logging.DEBUG) 14 | req_log.propagate = True 15 | 16 | 17 | sess = CacheControl( 18 | requests.Session(), cache=FileCache(".web_cache"), cacheable_methods=("HEAD", "GET") 19 | ) 20 | 21 | 22 | # get requests 23 | resp = sess.get("https://geotribu.fr") 24 | resp_img = sess.get( 25 | "https://cdn.geotribu.fr/img/articles-blog-rdp/capture-ecran/kevish_Air-Traffic.png" 26 | ) 27 | 28 | # try again, cache hit expected 29 | resp = sess.get("https://geotribu.fr") 30 | resp_img = sess.get( 31 | "https://cdn.geotribu.fr/img/articles-blog-rdp/capture-ecran/kevish_Air-Traffic.png" 32 | ) 33 | 34 | # head requests 35 | resp_img = sess.head( 36 | "https://cdn.geotribu.fr/img/articles-blog-rdp/capture-ecran/kevish_Air-Traffic.png" 37 | ) 38 | 39 | 40 | # try again, cache hit expected 41 | resp_img = sess.head( 42 | "https://cdn.geotribu.fr/img/articles-blog-rdp/capture-ecran/kevish_Air-Traffic.png" 43 | ) 44 | 45 | print(list(Path(".web_cache").iterdir())) 46 | -------------------------------------------------------------------------------- /tests/dev/dev_json_feed_validation.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | import jsonfeed as jf 5 | import requests 6 | from jsonfeedvalidator import ErrorTree, format_errors, validate_feed 7 | 8 | # read 9 | 10 | # Requesting a valid JSON feed! 11 | r = requests.get("https://arxiv-feeds.appspot.com/json/test") 12 | # Parse from raw text... 13 | feed_from_text = jf.Feed.parse_string(r.text) 14 | # ...or parse JSON separately. 15 | r_json = r.json() 16 | feed_from_json = jf.Feed.parse(r_json) 17 | 18 | print(feed_from_text.title) 19 | 20 | # # write 21 | # me = jf.Author(name="Lukas Schwab", url="https://github.com/lukasschwab") 22 | # feed = jf.Feed("My Feed Title", authors=[me]) 23 | # item = jf.Item("some_item_id") 24 | # feed.items.append(item) 25 | 26 | # print(feed.toJSON()) 27 | 28 | # read from file 29 | path_json_feed = Path(__file__).parent.joinpath("json_feed_sample.json") 30 | 31 | with path_json_feed.open("r", encoding="UTF-8") as in_json: 32 | feed_data = json.load(in_json) 33 | 34 | 35 | feed_from_file = jf.Feed.parse(feed_data) 36 | print(feed_from_file.title) 37 | 38 | errors = validate_feed(feed_from_file) 39 | format_errors(feed_from_file, ErrorTree(errors)) 40 | -------------------------------------------------------------------------------- /tests/dev/dev_load_config.py: -------------------------------------------------------------------------------- 1 | from mkdocs.config import load_config 2 | 3 | from mkdocs_rss_plugin.plugin import GitRssPlugin 4 | 5 | mkdocs_cfg = load_config(config_file="tests/fixtures/mkdocs_multiple_instances.yml") 6 | 7 | print(mkdocs_cfg.plugins.keys()) 8 | rss_instances = [ 9 | plg for plg in mkdocs_cfg.plugins.items() if isinstance(plg[1], GitRssPlugin) 10 | ] 11 | print(len(rss_instances)) 12 | 13 | for plg in mkdocs_cfg.plugins.items(): 14 | print(plg) 15 | print(isinstance(plg[1], GitRssPlugin)) 16 | print(type(plg)) 17 | 18 | rss_instance_1 = plg[1] 19 | print(dir(rss_instance_1)) 20 | print(rss_instance_1.on_config(mkdocs_cfg)) 21 | -------------------------------------------------------------------------------- /tests/dev/dev_markdown_extension_rss.py: -------------------------------------------------------------------------------- 1 | """ 2 | RSS Extension 3 | ============= 4 | 5 | Summary 6 | ------- 7 | 8 | An extension to Python-Markdown that outputs a markdown document as RSS. 9 | Each item in the RSS document is the content following a heading (``) 10 | with the "title" being the heading itself. 11 | 12 | This extension previous was included with Python-Markdown and was removed in version 3.0. 13 | 14 | Usage 15 | ----- 16 | 17 | From the Python interpreter: 18 | 19 | >>> import markdown 20 | >>> text = "Some markdown document." 21 | >>> rss = markdown.markdown(text, ['rss']) 22 | 23 | Configuring the Output 24 | ---------------------- 25 | 26 | An RSS document includes some data about the document (URI, author, title) that 27 | will likely need to be configured for your needs. Therefore, three configuration 28 | options are available: 29 | 30 | * **URL** : The Main URL for the document. 31 | * **CREATOR** : The Feed creator's name. 32 | * **TITLE** : The title for the feed. 33 | 34 | An example: 35 | 36 | >>> rss = markdown.markdown(text, extensions = \ 37 | ... ['rss(URL=http://example.com,CREATOR=JOHN DOE,TITLE=My Document)'] 38 | ... ) 39 | 40 | """ 41 | 42 | import markdown 43 | from markdown.util import etree 44 | 45 | DEFAULT_URL = "http://packages.python.org/Markdown/" 46 | DEFAULT_CREATOR = "Yuri Takhteyev" 47 | DEFAULT_TITLE = "Markdown in Python" 48 | GENERATOR = "http://packages.python.org/Markdown/extensions/rss.html" 49 | 50 | month_map = { 51 | "Jan": "01", 52 | "Feb": "02", 53 | "March": "03", 54 | "April": "04", 55 | "May": "05", 56 | "June": "06", 57 | "July": "07", 58 | "August": "08", 59 | "September": "09", 60 | "October": "10", 61 | "November": "11", 62 | "December": "12", 63 | } 64 | 65 | 66 | def get_time(heading): 67 | heading = heading.split("-")[0] 68 | heading = heading.strip().replace(",", " ").replace(".", " ") 69 | 70 | month, date, year = heading.split() 71 | month = month_map[month] 72 | 73 | return rdftime(" ".join((month, date, year, "12:00:00 AM"))) 74 | 75 | 76 | def rdftime(time): 77 | time = time.replace(":", " ") 78 | time = time.replace("/", " ") 79 | time = time.split() 80 | return "%s-%s-%sT%s:%s:%s-08:00" % ( 81 | time[0], 82 | time[1], 83 | time[2], 84 | time[3], 85 | time[4], 86 | time[5], 87 | ) 88 | 89 | 90 | def get_date(text): 91 | return "date" 92 | 93 | 94 | class RssExtension(markdown.Extension): 95 | def extendMarkdown(self, md, md_globals): 96 | self.config = { 97 | "URL": [DEFAULT_URL, "Main URL"], 98 | "CREATOR": [DEFAULT_CREATOR, "Feed creator's name"], 99 | "TITLE": [DEFAULT_TITLE, "Feed title"], 100 | } 101 | 102 | md.xml_mode = True 103 | 104 | # Insert a tree-processor that would actually add the title tag 105 | treeprocessor = RssTreeProcessor(md) 106 | treeprocessor.ext = self 107 | md.treeprocessors["rss"] = treeprocessor 108 | md.stripTopLevelTags = 0 109 | md.docType = '\n' 110 | 111 | 112 | class RssTreeProcessor(markdown.treeprocessors.Treeprocessor): 113 | def run(self, root): 114 | rss = etree.Element("rss") 115 | rss.set("version", "2.0") 116 | 117 | channel = etree.SubElement(rss, "channel") 118 | 119 | for tag, text in ( 120 | ("title", self.ext.getConfig("TITLE")), 121 | ("link", self.ext.getConfig("URL")), 122 | ("description", None), 123 | ): 124 | element = etree.SubElement(channel, tag) 125 | element.text = text 126 | 127 | for child in root: 128 | if child.tag in ["h1", "h2", "h3", "h4", "h5"]: 129 | heading = child.text.strip() 130 | item = etree.SubElement(channel, "item") 131 | link = etree.SubElement(item, "link") 132 | link.text = self.ext.getConfig("URL") 133 | title = etree.SubElement(item, "title") 134 | title.text = heading 135 | 136 | guid = "".join([x for x in heading if x.isalnum()]) 137 | guidElem = etree.SubElement(item, "guid") 138 | guidElem.text = guid 139 | guidElem.set("isPermaLink", "false") 140 | 141 | elif child.tag in ["p"]: 142 | try: 143 | description = etree.SubElement(item, "description") 144 | except UnboundLocalError: 145 | # Item not defined - moving on 146 | pass 147 | else: 148 | if len(child): 149 | content = "\n".join([etree.tostring(node) for node in child]) 150 | else: 151 | content = child.text 152 | pholder = self.markdown.htmlStash.store("" % content) 153 | description.text = pholder 154 | 155 | return rss 156 | 157 | 158 | def makeExtension(configs): 159 | return RssExtension(configs) 160 | -------------------------------------------------------------------------------- /tests/fixtures/docs/blog/.authors.yml: -------------------------------------------------------------------------------- 1 | authors: 2 | squidfunk: 3 | name: Martin Donath 4 | description: Creator 5 | avatar: https://github.com/squidfunk.png 6 | alexvoss: 7 | name: Alex Voss 8 | description: Weltenwanderer 9 | avatar: https://github.com/alexvoss.png 10 | guts: 11 | avatar: https://cdn.geotribu.fr/img/internal/contributeurs/jmou.jfif 12 | description: GIS Watchman 13 | name: Julien Moura 14 | slug: julien-moura 15 | url: https://geotribu.fr/team/julien-moura/ 16 | email: guts@gmail.com 17 | -------------------------------------------------------------------------------- /tests/fixtures/docs/blog/index.md: -------------------------------------------------------------------------------- 1 | # Blog 2 | 3 | -------------------------------------------------------------------------------- /tests/fixtures/docs/blog/posts/firstpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | authors: 3 | - alexvoss 4 | - guts 5 | date: 2023-10-11 6 | categories: 7 | - meta 8 | --- 9 | 10 | # My first blog post 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nec 13 | maximus ex. Sed consequat, nulla quis malesuada dapibus, elit metus vehicula 14 | erat, ut egestas tellus eros at risus. In hac habitasse platea dictumst. 15 | Phasellus id lacus pulvinar erat consequat pretium. Morbi malesuada arcu mauris 16 | Nam vel justo sem. Nam placerat purus non varius luctus. Integer pretium leo in 17 | sem rhoncus, quis gravida orci mollis. Proin id aliquam est. Vivamus in nunc ac 18 | metus tristique pellentesque. Suspendisse viverra urna in accumsan aliquet. 19 | 20 | 21 | 22 | Donec volutpat, elit ac volutpat laoreet, turpis dolor semper nibh, et dictum 23 | massa ex pulvinar elit. Curabitur commodo sit amet dolor sed mattis. Etiam 24 | tempor odio eu nisi gravida cursus. Maecenas ante enim, fermentum sit amet 25 | molestie nec, mollis ac libero. Vivamus sagittis suscipit eros ut luctus. 26 | 27 | Nunc vehicula sagittis condimentum. Cras facilisis bibendum lorem et feugiat. 28 | In auctor accumsan ligula, at consectetur erat commodo quis. Morbi ac nunc 29 | pharetra, pellentesque risus in, consectetur urna. Nulla id enim facilisis 30 | arcu tincidunt pulvinar. Vestibulum laoreet risus scelerisque porta congue. 31 | In velit purus, dictum quis neque nec, molestie viverra risus. Nam pellentesque 32 | tellus id elit ultricies, vel finibus erat cursus. 33 | -------------------------------------------------------------------------------- /tests/fixtures/docs/blog/posts/sample_blog_post.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2023-02-12 3 | authors: 4 | - guts 5 | categories: 6 | - Blog 7 | --- 8 | 9 | # Blog sample 10 | 11 | I'm a really short intro. 12 | 13 | 14 | 15 | ## This part won't show up in RSS feed 16 | 17 | ### What is Lorem Ipsum? 18 | 19 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. 20 | -------------------------------------------------------------------------------- /tests/fixtures/docs/blog/posts/secondpost.md: -------------------------------------------------------------------------------- 1 | --- 2 | authors: 3 | - squidfunk 4 | date: 2023-10-12 5 | categories: 6 | - hello 7 | --- 8 | 9 | # A second post 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque nec 12 | maximus ex. Sed consequat, nulla quis malesuada dapibus, elit metus vehicula 13 | erat, ut egestas tellus eros at risus. In hac habitasse platea dictumst. 14 | Phasellus id lacus pulvinar erat consequat pretium. Morbi malesuada arcu mauris 15 | Nam vel justo sem. Nam placerat purus non varius luctus. Integer pretium leo in 16 | sem rhoncus, quis gravida orci mollis. Proin id aliquam est. Vivamus in nunc ac 17 | metus tristique pellentesque. Suspendisse viverra urna in accumsan aliquet. 18 | 19 | 20 | 21 | Donec volutpat, elit ac volutpat laoreet, turpis dolor semper nibh, et dictum 22 | massa ex pulvinar elit. Curabitur commodo sit amet dolor sed mattis. Etiam 23 | tempor odio eu nisi gravida cursus. Maecenas ante enim, fermentum sit amet 24 | molestie nec, mollis ac libero. Vivamus sagittis suscipit eros ut luctus. 25 | 26 | Nunc vehicula sagittis condimentum. Cras facilisis bibendum lorem et feugiat. 27 | In auctor accumsan ligula, at consectetur erat commodo quis. Morbi ac nunc 28 | pharetra, pellentesque risus in, consectetur urna. Nulla id enim facilisis 29 | arcu tincidunt pulvinar. Vestibulum laoreet risus scelerisque porta congue. 30 | In velit purus, dictum quis neque nec, molestie viverra risus. Nam pellentesque 31 | tellus id elit ultricies, vel finibus erat cursus. 32 | 33 | -------------------------------------------------------------------------------- /tests/fixtures/docs/folder_ignored/page_should_be_ignored_by_pathmatch.md: -------------------------------------------------------------------------------- 1 | # Page to test feature 2 | 3 | See: 4 | -------------------------------------------------------------------------------- /tests/fixtures/docs/index.md: -------------------------------------------------------------------------------- 1 | # Test home page 2 | 3 | Sed ut perspiciatis *unde* omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? 4 | 5 | Eos **aliquid incidunt non**. Possimus ipsam id reiciendis enim. Laudantium eligendi deleniti rem delectus corrupti sit doloremque quod. Dolorem est laborum enim a ut aut. 6 | 7 | Laudantium minima cumque mollitia alias voluptatibus sit doloribus natus. Nam earum dolores est velit optio ipsa sit. Veritatis aut iusto distinctio veritatis aperiam in. Nemo aperiam ipsum dolorem ea officiis pariatur sapiente. Quibusdam est ut eligendi vitae ducimus fugiat. Harum ea necessitatibus inventore rem voluptatem est quaerat incidunt. 8 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page with complete meta 3 | authors: 4 | - Guts 5 | - Tim Vink 6 | - liang2kl 7 | date: 2020-03-20 10:20 8 | update: 2022-03-30 10:20 9 | description: First test page of mkdocs-rss-plugin test suite 10 | image: "https://svgsilh.com/png-512/97849.png" 11 | tags: 12 | - test 13 | --- 14 | 15 | # Test page with meta 16 | 17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 18 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_as_string.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: Julien Moura 3 | category: blog 4 | date: 2022-03-30 5 | description: First test page of mkdocs-rss-plugin test suite 6 | tags: 7 | - test 8 | --- 9 | 10 | # Test page with meta values as string 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 13 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_bad_date.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2022-03-30 15:09:34 3 | description: Test page with meta keys as singular and values as iterable 4 | --- 5 | 6 | # Test page with meta keys as singular but bad date 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 9 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_date_in_dot_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page with meta date in dot key 3 | authors: 4 | - Guts 5 | - Tim Vink 6 | - liang2kl 7 | date: 8 | created: 2023-10-07 10:20 9 | update: 2023-10-08 10:20 10 | description: First test page of mkdocs-rss-plugin test suite 11 | image: "https://svgsilh.com/png-512/97849.png" 12 | tags: 13 | - test 14 | --- 15 | 16 | # Test page with meta 17 | 18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 19 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_draft_true.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page with partial meta but draft 3 | draft: true 4 | --- 5 | 6 | # Test page with partial meta but draft 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 9 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_no_creation_date.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page with complete meta 3 | authors: 4 | - Guts 5 | - Tim Vink 6 | - liang2kl 7 | update: 2022-10-07 10:20 8 | description: First test page of mkdocs-rss-plugin test suite 9 | image: "https://svgsilh.com/png-512/97849.png" 10 | tags: 11 | - test 12 | --- 13 | 14 | # Test page with meta 15 | 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 17 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_override_rss_description.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page with overridden rss feed description 3 | authors: 4 | - Guts 5 | - Tim Vink 6 | - liang2kl 7 | date: 2020-03-20 10:20 8 | update: 2022-03-30 10:20 9 | description: First test page of mkdocs-rss-plugin test suite 10 | image: "https://svgsilh.com/png-512/97849.png" 11 | rss: 12 | feed_description: This is a custom override of the feed description 13 | tags: 14 | - test 15 | --- 16 | 17 | # Test page with meta 18 | 19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 20 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_with_meta_singulars.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: 3 | - Julien Moura 4 | - Tim Vink 5 | tags: 6 | - blog 7 | - tests 8 | date: 2022-03-30 9 | description: Test page with meta keys as singular and values as iterable 10 | tags: 11 | - test 12 | --- 13 | 14 | # Test page with meta keys as singular 15 | 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 17 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_without_meta_early_delimiter.md: -------------------------------------------------------------------------------- 1 | # Page without meta with early delimiter 2 | 3 | 4 | 5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 6 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_without_meta_long.md: -------------------------------------------------------------------------------- 1 | # Page without meta and long text 2 | 3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 4 | 5 | ## And second level 6 | 7 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 8 | -------------------------------------------------------------------------------- /tests/fixtures/docs/page_without_meta_short.md: -------------------------------------------------------------------------------- 1 | # Page without meta with short text 2 | 3 | Lorem ipsum. 4 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_bad_config.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin - Bad config 2 | use_directory_urls: true 3 | 4 | plugins: 5 | - search 6 | - rss: 7 | type: somethingwrong 8 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_complete.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | abstract_chars_count: 160 # -1 for full content 17 | categories: 18 | - tags 19 | comments_path: "#__comments" 20 | date_from_meta: 21 | as_creation: "date" 22 | datetime_format: "%Y-%m-%d %H:%M" 23 | default_timezone: Europe/Paris 24 | default_time: "09:30" 25 | enabled: true 26 | feed_ttl: 1440 27 | image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png 28 | json_feed_enabled: true 29 | length: 20 30 | match_path: ".*" 31 | pretty_print: false 32 | rss_feed_enabled: true 33 | url_parameters: 34 | utm_source: "documentation" 35 | utm_medium: "RSS" 36 | utm_campaign: "feed-syndication" 37 | use_git: true 38 | use_material_social_cards: true 39 | theme: 40 | name: readthedocs 41 | locale: fr 42 | 43 | # Extensions to enhance markdown 44 | markdown_extensions: 45 | - meta 46 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_complete_no_git.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | abstract_chars_count: 160 # -1 for full content 17 | categories: 18 | - tags 19 | comments_path: "#__comments" 20 | date_from_meta: 21 | as_creation: "date" 22 | datetime_format: "%Y-%m-%d %H:%M" 23 | default_timezone: Europe/Paris 24 | default_time: "09:30" 25 | enabled: true 26 | feed_ttl: 1440 27 | image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png 28 | length: 20 29 | pretty_print: false 30 | match_path: ".*" 31 | url_parameters: 32 | utm_source: "documentation" 33 | utm_medium: "RSS" 34 | utm_campaign: "feed-syndication" 35 | use_git: false 36 | theme: 37 | name: readthedocs 38 | locale: fr 39 | 40 | # Extensions to enhance markdown 41 | markdown_extensions: 42 | - meta 43 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_custom_feeds_filenames.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin with custom RSS and JSON feeds output 2 | site_description: Test RSS Plugin with customized output_basename 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss: 7 | feeds_filenames: 8 | json_created: json 9 | json_updated: json-updated 10 | rss_created: rss 11 | rss_updated: rss-updated 12 | 13 | theme: 14 | name: mkdocs 15 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_custom_title_description.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin with custom titles and descriptions 2 | site_description: Test RSS Plugin with customized descriptions 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss: 7 | feed_title: My custom RSS title 8 | feed_description: My custom RSS description 9 | 10 | theme: 11 | name: mkdocs 12 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_dates_overridden.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: 'Guts - In Geo Veritas' 7 | 8 | # Repository 9 | repo_name: 'guts/mkdocs-rss-plugin' 10 | repo_url: 'https://github.com/guts/mkdocs-rss-plugin' 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | date_from_meta: 17 | as_creation: "date" 18 | as_update: "update" 19 | datetime_format: "%Y-%m-%d %H:%M" 20 | 21 | theme: 22 | name: readthedocs 23 | 24 | # Extensions to enhance markdown 25 | markdown_extensions: 26 | - meta 27 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_dates_overridden_in_dot_key.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: 'Guts - In Geo Veritas' 7 | 8 | # Repository 9 | repo_name: 'guts/mkdocs-rss-plugin' 10 | repo_url: 'https://github.com/guts/mkdocs-rss-plugin' 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | date_from_meta: 17 | # date location given as dot key 18 | as_creation: "date.created" 19 | as_update: "update" 20 | datetime_format: "%Y-%m-%d %H:%M" 21 | 22 | theme: 23 | name: readthedocs 24 | 25 | # Extensions to enhance markdown 26 | markdown_extensions: 27 | - meta 28 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_disabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | 4 | use_directory_urls: true 5 | 6 | plugins: 7 | - search 8 | - rss: 9 | enabled: False 10 | 11 | theme: 12 | name: mkdocs 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_feed_length_custom.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | abstract_chars_count: 200 17 | comments_path: "#__comments" 18 | feed_ttl: 90 19 | length: 3 20 | 21 | theme: 22 | name: readthedocs 23 | 24 | # Extensions to enhance markdown 25 | markdown_extensions: 26 | - meta 27 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_feed_ttl_custom.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | feed_ttl: 90 17 | 18 | theme: 19 | name: readthedocs 20 | 21 | # Extensions to enhance markdown 22 | markdown_extensions: 23 | - meta 24 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_categories.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | categories: 17 | - tags 18 | 19 | theme: 20 | name: readthedocs 21 | language: de 22 | 23 | # Extensions to enhance markdown 24 | markdown_extensions: 25 | - meta 26 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_comments.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: 'Guts - In Geo Veritas' 7 | 8 | # Repository 9 | repo_name: 'guts/mkdocs-rss-plugin' 10 | repo_url: 'https://github.com/guts/mkdocs-rss-plugin' 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | comments_path: "#__comments" 17 | 18 | theme: 19 | name: readthedocs 20 | 21 | # Extensions to enhance markdown 22 | markdown_extensions: 23 | - meta 24 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_delimiter_empty.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | abstract_delimiter: "" 17 | 18 | theme: 19 | name: readthedocs 20 | 21 | # Extensions to enhance markdown 22 | markdown_extensions: 23 | - meta 24 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_cards_blog.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test social cards support in 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 | - rss 9 | - social: 10 | enabled: true 11 | cards: true 12 | 13 | theme: 14 | name: material 15 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_cards_blog_directory_url_disabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin with social cards, blog plugin and directory URL disabled 2 | site_description: Test RSS with social and blog plugins enabled but directory URLS disabled. Related to https://github.com/Guts/mkdocs-rss-plugin/issues/319. 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: false 6 | 7 | plugins: 8 | - blog: 9 | blog_dir: blog 10 | authors_profiles: true 11 | - rss: 12 | match_path: blog/posts/.* 13 | - social: 14 | enabled: true 15 | cards: true 16 | 17 | theme: 18 | name: material 19 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_cards_disabled_site.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code set in with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss 7 | - social: 8 | enabled: false 9 | cards: true 10 | 11 | theme: 12 | name: material 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_cards_enabled_but_integration_disabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code set in with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss: 7 | use_material_social_cards: false 8 | - social: 9 | enabled: true 10 | cards: true 11 | 12 | theme: 13 | name: material 14 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_cards_enabled_site.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code set in with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss 7 | - social: 8 | enabled: true 9 | cards: true 10 | 11 | theme: 12 | name: material 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_image_social_enabled_but_cards_disabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code set in with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss 7 | - social: 8 | enabled: true 9 | cards: false 10 | 11 | theme: 12 | name: material 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_length_default.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | docs_dir: docs 13 | 14 | use_directory_urls: true 15 | 16 | plugins: 17 | - rss 18 | 19 | theme: 20 | name: readthedocs 21 | 22 | # Extensions to enhance markdown 23 | markdown_extensions: 24 | - meta 25 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_length_unlimited.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin" 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss: 16 | abstract_chars_count: -1 17 | 18 | theme: 19 | name: readthedocs 20 | 21 | # Extensions to enhance markdown 22 | markdown_extensions: 23 | - meta 24 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_item_no_comments.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin - TEST 3 | site_description: Basic setup to test against MkDocs RSS plugin 4 | site_author: Julien Moura (Guts) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin 6 | copyright: 'Guts - In Geo Veritas' 7 | 8 | # Repository 9 | repo_name: 'guts/mkdocs-rss-plugin' 10 | repo_url: 'https://github.com/guts/mkdocs-rss-plugin' 11 | 12 | use_directory_urls: true 13 | 14 | plugins: 15 | - rss 16 | 17 | theme: 18 | name: readthedocs 19 | 20 | # Extensions to enhance markdown 21 | markdown_extensions: 22 | - meta 23 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_items_material_blog_enabled.yml: -------------------------------------------------------------------------------- 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 | 11 | theme: 12 | name: material 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_items_material_blog_enabled_but_integration_disabled.yml: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_jsonfeed_enabled_not_rss.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss: 7 | enabled: true 8 | rss_feed_enabled: false 9 | json_feed_enabled: true 10 | 11 | theme: 12 | name: mkdocs 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_language_specific_material.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code set in with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss 7 | 8 | theme: 9 | name: material 10 | locale: en_US 11 | language: fr # custom setting for historical reason - see: https://github.com/squidfunk/mkdocs-material/discussions/6453 12 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_locale_with_territory.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: true 6 | 7 | plugins: 8 | - rss 9 | 10 | theme: 11 | name: mkdocs 12 | locale: en_US 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_locale_without_territory.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test a language code with territory 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: true 6 | 7 | plugins: 8 | - rss 9 | 10 | theme: 11 | name: mkdocs 12 | locale: fr 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_minimal.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | # site_description: Test RSS Plugin 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: true 6 | 7 | plugins: 8 | - rss 9 | 10 | theme: 11 | name: mkdocs 12 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_minimal_no_site_url.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | # site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: true 6 | 7 | plugins: 8 | - rss 9 | 10 | theme: 11 | name: mkdocs 12 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_multiple_instances.yml: -------------------------------------------------------------------------------- 1 | site_name: Test Mkdocs with multiple RSS plugin instances 2 | site_description: Multiple RSS plugin in a single mkdocs 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss 7 | - rss: 8 | feeds_filenames: 9 | json_created: blog.json 10 | json_updated: blog-updated.json 11 | rss_created: blog.xml 12 | rss_updated: blog-updated.xml 13 | match_path: "blog/.*" 14 | 15 | theme: 16 | name: material 17 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_pretty_print_disabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | use_directory_urls: true 5 | 6 | plugins: 7 | - search 8 | - rss: 9 | pretty_print: False 10 | 11 | theme: 12 | name: mkdocs 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_pretty_print_enabled.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | use_directory_urls: true 6 | 7 | plugins: 8 | - search 9 | - rss: 10 | pretty_print: True 11 | 12 | theme: 13 | name: mkdocs 14 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_rss_enabled_not_jsonfeed.yml: -------------------------------------------------------------------------------- 1 | site_name: Test RSS Plugin 2 | site_description: Test RSS Plugin 3 | site_url: https://guts.github.io/mkdocs-rss-plugin 4 | 5 | plugins: 6 | - rss: 7 | enabled: true 8 | rss_feed_enabled: true 9 | json_feed_enabled: false 10 | 11 | theme: 12 | name: mkdocs 13 | -------------------------------------------------------------------------------- /tests/fixtures/mkdocs_simple.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: MkDocs RSS Plugin 3 | site_description: Documentation about the MkDocs RSS Plugin 4 | site_author: dev@ingeoveritas.com (Julien M.) 5 | site_url: https://guts.github.io/mkdocs-rss-plugin/ 6 | copyright: "Guts - In Geo Veritas" 7 | 8 | # Repository 9 | repo_name: "guts/mkdocs-rss-plugin" 10 | repo_url: "https://github.com/guts/mkdocs-rss-plugin/" 11 | edit_uri: "blob/main/docs/" 12 | 13 | docs_dir: "docs/" 14 | use_directory_urls: true 15 | 16 | plugins: 17 | - rss: 18 | date_from_meta: 19 | as_creation: "date" 20 | datetime_format: "%Y-%m-%d %H:%M" 21 | default_timezone: "Europe/Paris" 22 | default_time: "22:00" 23 | abstract_chars_count: 160 24 | abstract_delimiter: 25 | image: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png 26 | match_path: ".*" 27 | pretty_print: false 28 | url_parameters: 29 | utm_source: "documentation" 30 | utm_medium: "RSS" 31 | utm_campaign: "feed-syndication" 32 | - search 33 | 34 | theme: 35 | name: mkdocs 36 | language: en 37 | 38 | # Extensions to enhance markdown 39 | markdown_extensions: 40 | - admonition 41 | - attr_list 42 | - codehilite 43 | - meta 44 | - toc: 45 | permalink: "#" 46 | 47 | nav: 48 | - "Home": "index.md" 49 | - "Settings": "configuration.md" 50 | - "Changelog": "changelog.md" 51 | -------------------------------------------------------------------------------- /tests/fixtures/test-build-material.dockerfile: -------------------------------------------------------------------------------- 1 | # Reference: https://squidfunk.github.io/mkdocs-material/getting-started/#with-docker 2 | FROM squidfunk/mkdocs-material 3 | 4 | COPY . . 5 | 6 | RUN ls -a -r 7 | 8 | RUN \ 9 | apk upgrade --update-cache -a \ 10 | && apk add --no-cache --virtual .build gcc musl-dev 11 | 12 | RUN python3 -m pip install --no-cache-dir . 13 | 14 | RUN apk del .build \ 15 | && rm -rf /tmp/* /root/.cache 16 | -------------------------------------------------------------------------------- /tests/fixtures/test-build-min-python.dockerfile: -------------------------------------------------------------------------------- 1 | # Reference: https://squidfunk.github.io/mkdocs-material/getting-started/#with-docker 2 | FROM python:3.8.19 3 | 4 | COPY . . 5 | 6 | RUN python3 -m pip install --no-cache-dir -U pip setuptools wheel \ 7 | && python3 -m pip install --no-cache-dir -U -r requirements/documentation.txt \ 8 | && python3 -m pip install --no-cache-dir . 9 | 10 | RUN mkdocs build 11 | -------------------------------------------------------------------------------- /tests/test_build_no_git.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_build_no_git 9 | # for specific test 10 | python -m unittest tests.test_build_no_git.TestBuildRssNoGit.test_not_git_repo_without_option 11 | """ 12 | 13 | # ############################################################################# 14 | # ########## Libraries ############# 15 | # ################################## 16 | 17 | # Standard library 18 | import tempfile 19 | import unittest 20 | 21 | # logging 22 | from logging import DEBUG, getLogger 23 | from pathlib import Path 24 | from traceback import format_exception 25 | 26 | # test suite 27 | from tests.base import BaseTest 28 | 29 | logger = getLogger(__name__) 30 | logger.setLevel(DEBUG) 31 | 32 | # ############################################################################# 33 | # ########## Classes ############### 34 | # ################################## 35 | 36 | 37 | class TestBuildRssNoGit(BaseTest): 38 | """Test MkDocs build with RSS plugin but without git.""" 39 | 40 | # -- Standard methods -------------------------------------------------------- 41 | @classmethod 42 | def setUpClass(cls): 43 | """Executed when module is loaded before any test.""" 44 | pass 45 | 46 | def setUp(self): 47 | """Executed before each test.""" 48 | pass 49 | 50 | def tearDown(self): 51 | """Executed after each test.""" 52 | pass 53 | 54 | @classmethod 55 | def tearDownClass(cls): 56 | """Executed after the last test.""" 57 | # # In case of some tests failure, ensure that everything is cleaned up 58 | # temp_page = Path("tests/fixtures/docs/temp_page_not_in_git_log.md") 59 | # # if temp_page.exists(): 60 | # # temp_page.unlink() 61 | 62 | git_dir = Path("_git") 63 | if git_dir.exists(): 64 | git_dir.replace(git_dir.with_name(".git")) 65 | 66 | # -- TESTS --------------------------------------------------------- 67 | def test_not_git_repo(self): 68 | # temporarily rename the git folder to fake a non-git repo 69 | git_dir = Path(".git") 70 | git_dir_tmp = git_dir.with_name("_git") 71 | git_dir.replace(git_dir_tmp) 72 | 73 | with tempfile.TemporaryDirectory() as tmpdirname: 74 | cli_result = self.build_docs_setup( 75 | testproject_path="docs", 76 | mkdocs_yml_filepath=Path("tests/fixtures/mkdocs_complete_no_git.yml"), 77 | output_path=tmpdirname, 78 | strict=True, 79 | ) 80 | 81 | self.assertIsNone(cli_result.exception) 82 | self.assertEqual(cli_result.exit_code, 0, cli_result) 83 | 84 | # restore name 85 | git_dir_tmp.replace(git_dir) 86 | 87 | def test_not_git_repo_without_option(self): 88 | # temporarily rename the git folder to fake a non-git repo 89 | git_dir = Path(".git") 90 | git_dir_tmp = git_dir.with_name("_git") 91 | git_dir.replace(git_dir_tmp) 92 | 93 | with tempfile.TemporaryDirectory() as tmpdirname: 94 | cli_result = self.build_docs_setup( 95 | testproject_path="docs", 96 | mkdocs_yml_filepath=Path("tests/fixtures/mkdocs_complete.yml"), 97 | output_path=tmpdirname, 98 | strict=True, 99 | ) 100 | 101 | self.assertIsNotNone(cli_result.exception) 102 | self.assertEqual( 103 | cli_result.exit_code, 104 | 1, 105 | format_exception( 106 | type(cli_result.exception), 107 | cli_result.exception, 108 | cli_result.exception.__traceback__, 109 | ), 110 | ) 111 | 112 | # restore name 113 | git_dir_tmp.replace(git_dir) 114 | 115 | 116 | # ############################################################################## 117 | # ##### Stand alone program ######## 118 | # ################################## 119 | if __name__ == "__main__": 120 | unittest.main() 121 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_config 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import unittest 18 | from datetime import datetime 19 | from pathlib import Path 20 | 21 | # 3rd party 22 | from mkdocs.config.base import Config 23 | 24 | from mkdocs_rss_plugin.config import RssPluginConfig 25 | 26 | # plugin target 27 | from mkdocs_rss_plugin.constants import DEFAULT_CACHE_FOLDER 28 | from mkdocs_rss_plugin.plugin import GitRssPlugin 29 | 30 | # test suite 31 | from tests.base import BaseTest 32 | 33 | 34 | # ############################################################################# 35 | # ########## Classes ############### 36 | # ################################## 37 | class TestConfig(BaseTest): 38 | """Test plugin configuration.""" 39 | 40 | # -- Standard methods -------------------------------------------------------- 41 | @classmethod 42 | def setUpClass(cls): 43 | """Executed when module is loaded before any test.""" 44 | cls.config_files = sorted(Path("tests/fixtures/").glob("**/mkdocs_*.yml")) 45 | cls.feed_image = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png" 46 | 47 | def setUp(self): 48 | """Executed before each test.""" 49 | pass 50 | 51 | def tearDown(self): 52 | """Executed after each test.""" 53 | pass 54 | 55 | @classmethod 56 | def tearDownClass(cls): 57 | """Executed after the last test.""" 58 | pass 59 | 60 | # -- TESTS --------------------------------------------------------- 61 | def test_plugin_config_defaults(self): 62 | # default reference 63 | expected = { 64 | "abstract_chars_count": 160, 65 | "abstract_delimiter": "", 66 | "categories": None, 67 | "cache_dir": f"{DEFAULT_CACHE_FOLDER.resolve()}", 68 | "comments_path": None, 69 | "date_from_meta": { 70 | "as_creation": "git", 71 | "as_update": "git", 72 | "datetime_format": "%Y-%m-%d %H:%M", 73 | "default_time": datetime.min.strftime("%H:%M"), 74 | "default_timezone": "UTC", 75 | }, 76 | "enabled": True, 77 | "feed_description": None, 78 | "feed_title": None, 79 | "feed_ttl": 1440, 80 | "image": None, 81 | "json_feed_enabled": True, 82 | "length": 20, 83 | "match_path": ".*", 84 | "feeds_filenames": { 85 | "json_created": "feed_json_created.json", 86 | "json_updated": "feed_json_updated.json", 87 | "rss_created": "feed_rss_created.xml", 88 | "rss_updated": "feed_rss_updated.xml", 89 | }, 90 | "pretty_print": False, 91 | "rss_feed_enabled": True, 92 | "url_parameters": None, 93 | "use_git": True, 94 | "use_material_blog": True, 95 | "use_material_social_cards": True, 96 | } 97 | 98 | # load 99 | plugin = GitRssPlugin() 100 | errors, warnings = plugin.load_config({}) 101 | self.assertIsInstance(plugin.config, RssPluginConfig) 102 | self.assertIsInstance(plugin.config, Config) 103 | self.assertEqual(plugin.config, expected) 104 | self.assertEqual(errors, []) 105 | self.assertEqual(warnings, []) 106 | 107 | def test_plugin_config_image(self): 108 | # reference 109 | expected = { 110 | "abstract_chars_count": 160, 111 | "abstract_delimiter": "", 112 | "cache_dir": f"{DEFAULT_CACHE_FOLDER.resolve()}", 113 | "categories": None, 114 | "comments_path": None, 115 | "date_from_meta": { 116 | "as_creation": "git", 117 | "as_update": "git", 118 | "datetime_format": "%Y-%m-%d %H:%M", 119 | "default_time": datetime.min.strftime("%H:%M"), 120 | "default_timezone": "UTC", 121 | }, 122 | "enabled": True, 123 | "feed_description": None, 124 | "feed_title": None, 125 | "feed_ttl": 1440, 126 | "image": self.feed_image, 127 | "json_feed_enabled": True, 128 | "length": 20, 129 | "match_path": ".*", 130 | "feeds_filenames": { 131 | "json_created": "feed_json_created.json", 132 | "json_updated": "feed_json_updated.json", 133 | "rss_created": "feed_rss_created.xml", 134 | "rss_updated": "feed_rss_updated.xml", 135 | }, 136 | "pretty_print": False, 137 | "rss_feed_enabled": True, 138 | "url_parameters": None, 139 | "use_git": True, 140 | "use_material_blog": True, 141 | "use_material_social_cards": True, 142 | } 143 | 144 | # custom config 145 | custom_cfg = {"image": self.feed_image} 146 | 147 | # load 148 | plugin = GitRssPlugin() 149 | errors, warnings = plugin.load_config(custom_cfg) 150 | self.assertIsInstance(plugin.config, RssPluginConfig) 151 | self.assertIsInstance(plugin.config, Config) 152 | self.assertEqual(plugin.config, expected) 153 | self.assertEqual(errors, []) 154 | self.assertEqual(warnings, []) 155 | 156 | def test_plugin_config_through_mkdocs(self): 157 | for config_filepath in self.config_files: 158 | plg_cfg = self.get_plugin_config_from_mkdocs(config_filepath, "rss") 159 | print(config_filepath) 160 | self.assertIsInstance(plg_cfg, Config) 161 | 162 | 163 | # ############################################################################## 164 | # ##### Stand alone program ######## 165 | # ################################## 166 | if __name__ == "__main__": 167 | unittest.main() 168 | -------------------------------------------------------------------------------- /tests/test_integrations_material.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_build 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import unittest 18 | from logging import DEBUG, getLogger 19 | from pathlib import Path 20 | 21 | # 3rd party 22 | from mkdocs.config import load_config 23 | 24 | # package 25 | from mkdocs_rss_plugin.integrations.theme_material_base import ( 26 | IntegrationMaterialThemeBase, 27 | ) 28 | 29 | # test suite 30 | from tests.base import BaseTest 31 | 32 | # ############################################################################# 33 | # ########## Classes ############### 34 | # ################################## 35 | 36 | logger = getLogger(__name__) 37 | logger.setLevel(DEBUG) 38 | 39 | 40 | class TestRssPluginIntegrationsMaterialThem(BaseTest): 41 | """Test integration of Material theme.""" 42 | 43 | # -- TESTS --------------------------------------------------------- 44 | def test_plugin_config_theme_material(self): 45 | # default reference 46 | cfg_mkdocs = load_config( 47 | str(Path("tests/fixtures/mkdocs_language_specific_material.yml").resolve()) 48 | ) 49 | 50 | integration_social_cards = IntegrationMaterialThemeBase( 51 | mkdocs_config=cfg_mkdocs 52 | ) 53 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 54 | 55 | def test_plugin_config_theme_not_material(self): 56 | # default reference 57 | cfg_mkdocs = load_config( 58 | str(Path("tests/fixtures/mkdocs_complete.yml").resolve()) 59 | ) 60 | 61 | integration_social_cards = IntegrationMaterialThemeBase( 62 | mkdocs_config=cfg_mkdocs 63 | ) 64 | self.assertFalse(integration_social_cards.IS_THEME_MATERIAL) 65 | 66 | 67 | # ############################################################################## 68 | # ##### Stand alone program ######## 69 | # ################################## 70 | if __name__ == "__main__": 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /tests/test_integrations_material_blog.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_build 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import tempfile 18 | import unittest 19 | from logging import DEBUG, getLogger 20 | from pathlib import Path 21 | from traceback import format_exception 22 | 23 | # 3rd party 24 | import feedparser 25 | from mkdocs.config import load_config 26 | 27 | # package 28 | from mkdocs_rss_plugin.integrations.theme_material_blog_plugin import ( 29 | IntegrationMaterialBlog, 30 | ) 31 | 32 | # test suite 33 | from tests.base import BaseTest 34 | 35 | # ############################################################################# 36 | # ########## Classes ############### 37 | # ################################## 38 | 39 | logger = getLogger(__name__) 40 | logger.setLevel(DEBUG) 41 | 42 | 43 | class TestRssPluginIntegrationsMaterialBlog(BaseTest): 44 | """Test integration of Material Blog plugin with RSS plugin.""" 45 | 46 | # -- 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 | 65 | def test_plugin_config_blog_enabled(self): 66 | # default reference 67 | cfg_mkdocs = load_config( 68 | str(Path("tests/fixtures/mkdocs_items_material_blog_enabled.yml").resolve()) 69 | ) 70 | 71 | integration_social_cards = IntegrationMaterialBlog(mkdocs_config=cfg_mkdocs) 72 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 73 | self.assertTrue(integration_social_cards.IS_BLOG_PLUGIN_ENABLED) 74 | self.assertTrue(integration_social_cards.IS_ENABLED) 75 | 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 | 127 | 128 | # ############################################################################## 129 | # ##### Stand alone program ######## 130 | # ################################## 131 | if __name__ == "__main__": 132 | unittest.main() 133 | -------------------------------------------------------------------------------- /tests/test_integrations_material_social_cards.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_build 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import tempfile 18 | import unittest 19 | from logging import DEBUG, getLogger 20 | from pathlib import Path 21 | from traceback import format_exception 22 | 23 | # 3rd party 24 | import feedparser 25 | from mkdocs.config import load_config 26 | 27 | # package 28 | from mkdocs_rss_plugin.__about__ import __title_clean__ 29 | from mkdocs_rss_plugin.integrations.theme_material_social_plugin import ( 30 | IntegrationMaterialSocialCards, 31 | ) 32 | 33 | # test suite 34 | from tests.base import BaseTest 35 | 36 | # ############################################################################# 37 | # ########## Classes ############### 38 | # ################################## 39 | 40 | logger = getLogger(__name__) 41 | logger.setLevel(DEBUG) 42 | 43 | 44 | class TestRssPluginIntegrationsMaterialSocialCards(BaseTest): 45 | """Test integration of Social Cards plugin with RSS plugin.""" 46 | 47 | # -- TESTS --------------------------------------------------------- 48 | def test_plugin_config_social_cards_enabled_but_integration_disabled(self): 49 | # default reference 50 | cfg_mkdocs = load_config( 51 | str( 52 | Path( 53 | "tests/fixtures/mkdocs_item_image_social_cards_enabled_but_integration_disabled.yml" 54 | ).resolve() 55 | ) 56 | ) 57 | 58 | integration_social_cards = IntegrationMaterialSocialCards( 59 | mkdocs_config=cfg_mkdocs, 60 | switch_force=cfg_mkdocs.plugins.get("rss").config.use_material_social_cards, 61 | ) 62 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 63 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 64 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 65 | self.assertFalse(integration_social_cards.IS_ENABLED) 66 | 67 | def test_plugin_config_social_cards_enabled(self): 68 | # default reference 69 | cfg_mkdocs = load_config( 70 | str( 71 | Path( 72 | "tests/fixtures/mkdocs_item_image_social_cards_enabled_site.yml" 73 | ).resolve() 74 | ) 75 | ) 76 | 77 | integration_social_cards = IntegrationMaterialSocialCards( 78 | mkdocs_config=cfg_mkdocs 79 | ) 80 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 81 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 82 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 83 | self.assertTrue(integration_social_cards.IS_ENABLED) 84 | 85 | def test_plugin_config_social_cards_disabled(self): 86 | # default reference 87 | cfg_mkdocs = load_config( 88 | str( 89 | Path( 90 | "tests/fixtures/mkdocs_item_image_social_cards_disabled_site.yml" 91 | ).resolve() 92 | ) 93 | ) 94 | 95 | integration_social_cards = IntegrationMaterialSocialCards( 96 | mkdocs_config=cfg_mkdocs 97 | ) 98 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 99 | self.assertFalse(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 100 | self.assertFalse(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 101 | self.assertFalse(integration_social_cards.IS_ENABLED) 102 | 103 | def test_plugin_config_social_plugin_enabled_but_cards_disabled(self): 104 | # default reference 105 | cfg_mkdocs = load_config( 106 | str( 107 | Path( 108 | "tests/fixtures/mkdocs_item_image_social_enabled_but_cards_disabled.yml" 109 | ).resolve() 110 | ) 111 | ) 112 | 113 | integration_social_cards = IntegrationMaterialSocialCards( 114 | mkdocs_config=cfg_mkdocs 115 | ) 116 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 117 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 118 | self.assertFalse(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 119 | self.assertFalse(integration_social_cards.IS_ENABLED) 120 | 121 | def test_plugin_config_social_cards_enabled_with_blog_plugin(self): 122 | # default reference 123 | cfg_mkdocs = load_config( 124 | str( 125 | Path("tests/fixtures/mkdocs_item_image_social_cards_blog.yml").resolve() 126 | ) 127 | ) 128 | 129 | integration_social_cards = IntegrationMaterialSocialCards( 130 | mkdocs_config=cfg_mkdocs 131 | ) 132 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 133 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 134 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 135 | self.assertTrue( 136 | integration_social_cards.integration_material_blog.IS_BLOG_PLUGIN_ENABLED 137 | ) 138 | self.assertTrue(integration_social_cards.IS_ENABLED) 139 | 140 | def test_plugin_config_social_cards_enabled_with_directory_urls_disabled(self): 141 | """Test case described in https://github.com/Guts/mkdocs-rss-plugin/issues/319.""" 142 | # default reference 143 | cfg_mkdocs = load_config( 144 | str( 145 | Path( 146 | "tests/fixtures/mkdocs_item_image_social_cards_blog_directory_url_disabled.yml" 147 | ).resolve() 148 | ) 149 | ) 150 | 151 | integration_social_cards = IntegrationMaterialSocialCards( 152 | mkdocs_config=cfg_mkdocs 153 | ) 154 | self.assertTrue(integration_social_cards.IS_THEME_MATERIAL) 155 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_ENABLED) 156 | self.assertTrue(integration_social_cards.IS_SOCIAL_PLUGIN_CARDS_ENABLED) 157 | self.assertIsInstance(integration_social_cards.social_cards_dir, str) 158 | # self.assertTrue(integration_social_cards.social_cards_cache_dir.is_dir()) 159 | 160 | with tempfile.TemporaryDirectory( 161 | prefix=f"{__title_clean__.lower()}_" 162 | ) as tmpdirname: 163 | cli_result = self.build_docs_setup( 164 | testproject_path="docs", 165 | mkdocs_yml_filepath=Path( 166 | "tests/fixtures/mkdocs_item_image_social_cards_blog_directory_url_disabled.yml" 167 | ), 168 | output_path=tmpdirname, 169 | strict=False, 170 | ) 171 | print(tmpdirname) 172 | if cli_result.exception is not None: 173 | e = cli_result.exception 174 | logger.debug(format_exception(type(e), e, e.__traceback__)) 175 | 176 | self.assertEqual(cli_result.exit_code, 0) 177 | self.assertIsNone(cli_result.exception) 178 | 179 | # created items 180 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml") 181 | self.assertEqual(feed_parsed.bozo, 0) 182 | for feed_item in feed_parsed.entries: 183 | self.assertTrue(hasattr(feed_item, "enclosures")) 184 | 185 | # updated items 186 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml") 187 | self.assertEqual(feed_parsed.bozo, 0) 188 | 189 | def test_simple_build(self): 190 | with tempfile.TemporaryDirectory() as tmpdirname: 191 | cli_result = self.build_docs_setup( 192 | testproject_path="docs", 193 | mkdocs_yml_filepath=Path( 194 | "tests/fixtures/mkdocs_item_image_social_cards_enabled_site.yml" 195 | ), 196 | output_path=tmpdirname, 197 | strict=False, 198 | ) 199 | 200 | if cli_result.exception is not None: 201 | e = cli_result.exception 202 | logger.debug(format_exception(type(e), e, e.__traceback__)) 203 | 204 | self.assertEqual(cli_result.exit_code, 0) 205 | self.assertIsNone(cli_result.exception) 206 | 207 | # created items 208 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml") 209 | self.assertEqual(feed_parsed.bozo, 0) 210 | 211 | # updated items 212 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml") 213 | self.assertEqual(feed_parsed.bozo, 0) 214 | 215 | with tempfile.TemporaryDirectory() as tmpdirname: 216 | cli_result = self.build_docs_setup( 217 | testproject_path="docs", 218 | mkdocs_yml_filepath=Path( 219 | "tests/fixtures/mkdocs_item_image_social_cards_disabled_site.yml" 220 | ), 221 | output_path=tmpdirname, 222 | strict=False, 223 | ) 224 | 225 | if cli_result.exception is not None: 226 | e = cli_result.exception 227 | logger.debug(format_exception(type(e), e, e.__traceback__)) 228 | 229 | self.assertEqual(cli_result.exit_code, 0) 230 | self.assertIsNone(cli_result.exception) 231 | 232 | # created items 233 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_created.xml") 234 | self.assertEqual(feed_parsed.bozo, 0) 235 | 236 | # updated items 237 | feed_parsed = feedparser.parse(Path(tmpdirname) / "feed_rss_updated.xml") 238 | self.assertEqual(feed_parsed.bozo, 0) 239 | 240 | 241 | # ############################################################################## 242 | # ##### Stand alone program ######## 243 | # ################################## 244 | if __name__ == "__main__": 245 | unittest.main() 246 | -------------------------------------------------------------------------------- /tests/test_rss_util.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_rss_util 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import unittest 18 | from pathlib import Path 19 | 20 | # 3rd party 21 | from validator_collection import checkers 22 | 23 | # plugin target 24 | from mkdocs_rss_plugin.util import Util 25 | 26 | 27 | # ############################################################################# 28 | # ########## Classes ############### 29 | # ################################## 30 | class TestRssUtil(unittest.TestCase): 31 | """Test plugin tools.""" 32 | 33 | # -- Standard methods -------------------------------------------------------- 34 | @classmethod 35 | def setUpClass(cls): 36 | """Executed when module is loaded before any test.""" 37 | cls.config_files = sorted(Path("tests/fixtures/").glob("**/*.yml")) 38 | cls.feed_image = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png" 39 | cls.plg_utils = Util() 40 | 41 | def setUp(self): 42 | """Executed before each test.""" 43 | pass 44 | 45 | def tearDown(self): 46 | """Executed after each test.""" 47 | pass 48 | 49 | @classmethod 50 | def tearDownClass(cls): 51 | """Executed after the last test.""" 52 | pass 53 | 54 | # -- TESTS --------------------------------------------------------- 55 | def test_build_url(self): 56 | """Test URL builder.""" 57 | # without URL parameters 58 | item_url = self.plg_utils.build_url( 59 | base_url="https://guts.github.io/mkdocs-rss-plugin/", path="changelog" 60 | ) 61 | self.assertTrue(checkers.is_url(item_url)) 62 | 63 | # with URL parameters 64 | item_url = self.plg_utils.build_url( 65 | base_url="https://guts.github.io/mkdocs-rss-plugin/", 66 | path="changelog", 67 | args_dict={"utm_source": "test_rss"}, 68 | ) 69 | print(item_url) 70 | self.assertTrue(checkers.is_url(item_url)) 71 | 72 | def test_local_image_ok(self): 73 | """Test local image length calculation.""" 74 | img_length = self.plg_utils.get_local_image_length( 75 | page_path="docs/index.md", path_to_append="assets/rss_icon.svg" 76 | ) 77 | self.assertIsInstance(img_length, int) 78 | 79 | def test_local_image_none(self): 80 | """Test local image length calculation on non-existing image.""" 81 | img_length = self.plg_utils.get_local_image_length( 82 | page_path="docs/index.md", path_to_append="inexisting_image.svg" 83 | ) 84 | self.assertIsNone(img_length) 85 | 86 | def test_remote_image_ok(self): 87 | """Test remote image length calculation.""" 88 | img_length = self.plg_utils.get_remote_image_length(image_url=self.feed_image) 89 | self.assertIsInstance(img_length, int) 90 | 91 | def test_remote_image_none(self): 92 | """Test remote image length calculation on unreachable image.""" 93 | img_length = self.plg_utils.get_remote_image_length( 94 | image_url="https://guts.github.io/mkdocs-rss-plugin/inexisting.svg" 95 | ) 96 | self.assertIsNone(img_length) 97 | 98 | def test_get_value_from_dot_key(self): 99 | param_list = [ 100 | { 101 | "meta": {"date": "2021-09-01"}, 102 | "value_location": "date", 103 | "value": "2021-09-01", 104 | }, 105 | { 106 | "meta": {"date": {"created": "2021-09-01"}}, 107 | "value_location": "date.created", 108 | "value": "2021-09-01", 109 | }, 110 | { 111 | "meta": {"date": "2021-09-01"}, 112 | "value_location": "date.created", 113 | "value": None, 114 | }, 115 | { 116 | "meta": {True: "bool_as_key"}, 117 | "value_location": True, 118 | "value": "bool_as_key", 119 | }, 120 | { 121 | "meta": {"date": "2021-09-01"}, 122 | "value_location": True, 123 | "value": None, 124 | }, 125 | ] 126 | for param in param_list: 127 | with self.subTest(param=param): 128 | result = self.plg_utils.get_value_from_dot_key( 129 | param["meta"], param["value_location"] 130 | ) 131 | self.assertEqual(result, param["value"]) 132 | 133 | 134 | # ############################################################################## 135 | # ##### Stand alone program ######## 136 | # ################################## 137 | if __name__ == "__main__": 138 | unittest.main() 139 | -------------------------------------------------------------------------------- /tests/test_timezoner.py: -------------------------------------------------------------------------------- 1 | #! python3 # noqa E265 2 | 3 | """Usage from the repo root folder: 4 | 5 | .. code-block:: python 6 | 7 | # for whole test 8 | python -m unittest tests.test_timezoner 9 | 10 | """ 11 | 12 | # ############################################################################# 13 | # ########## Libraries ############# 14 | # ################################## 15 | 16 | # Standard library 17 | import unittest 18 | from datetime import datetime 19 | 20 | # plugin target 21 | from mkdocs_rss_plugin.util import set_datetime_zoneinfo 22 | 23 | 24 | # ############################################################################# 25 | # ########## Classes ############### 26 | # ################################## 27 | class TestTimezoner(unittest.TestCase): 28 | """Test timezone handler.""" 29 | 30 | # -- Standard methods -------------------------------------------------------- 31 | @classmethod 32 | def setUpClass(cls): 33 | """Executed when module is loaded before any test.""" 34 | cls.fmt_date = "%Y-%m-%d" 35 | cls.fmt_datetime_aware = "%Y-%m-%d %H:%M:%S%z" 36 | cls.fmt_datetime_naive = "%Y-%m-%d %H:%M" 37 | 38 | def setUp(self): 39 | """Executed before each test.""" 40 | pass 41 | 42 | def tearDown(self): 43 | """Executed after each test.""" 44 | pass 45 | 46 | @classmethod 47 | def tearDownClass(cls): 48 | """Executed after the last test.""" 49 | pass 50 | 51 | # -- TESTS --------------------------------------------------------- 52 | def test_tz_dates(self): 53 | """Test timezone set for dates.""" 54 | 55 | test_date_summer = datetime.strptime("2022-07-14", self.fmt_date) 56 | test_date_winter = datetime.strptime("2022-12-25", self.fmt_date) 57 | 58 | self.assertEqual( 59 | set_datetime_zoneinfo(test_date_summer), 60 | datetime.strptime("2022-07-14 00:00:00+00:00", self.fmt_datetime_aware), 61 | ) 62 | self.assertEqual( 63 | set_datetime_zoneinfo(test_date_winter), 64 | datetime.strptime("2022-12-25 00:00:00+00:00", self.fmt_datetime_aware), 65 | ) 66 | 67 | def test_tz_datetimes_naive(self): 68 | """Test timezone set for naive datetimes.""" 69 | 70 | test_datetime_summer_naive = datetime.strptime( 71 | "2022-07-14 12:00", self.fmt_datetime_naive 72 | ) 73 | test_datetime_winter_naive = datetime.strptime( 74 | "2022-12-25 22:00", self.fmt_datetime_naive 75 | ) 76 | 77 | # without timezone = UTC 78 | self.assertEqual( 79 | set_datetime_zoneinfo(test_datetime_summer_naive), 80 | datetime.strptime("2022-07-14 12:00:00+00:00", self.fmt_datetime_aware), 81 | ) 82 | self.assertEqual( 83 | set_datetime_zoneinfo(test_datetime_winter_naive), 84 | datetime.strptime("2022-12-25 22:00:00+00:00", self.fmt_datetime_aware), 85 | ) 86 | 87 | # with timezone 88 | self.assertEqual( 89 | set_datetime_zoneinfo(test_datetime_summer_naive, "Europe/Paris"), 90 | datetime.strptime("2022-07-14 12:00:00+02:00", self.fmt_datetime_aware), 91 | ) 92 | self.assertEqual( 93 | set_datetime_zoneinfo(test_datetime_winter_naive, "Europe/Paris"), 94 | datetime.strptime("2022-12-25 22:00:00+01:00", self.fmt_datetime_aware), 95 | ) 96 | 97 | def test_tz_datetimes_aware(self): 98 | """Test timezone set for aware datetimes.""" 99 | 100 | test_datetime_summer_aware = datetime.strptime( 101 | "2022-07-14 12:00:00+0400", self.fmt_datetime_aware 102 | ) 103 | test_datetime_winter_aware = datetime.strptime( 104 | "2022-12-25 22:00:00-0800", self.fmt_datetime_aware 105 | ) 106 | 107 | # without timezone = UTC 108 | self.assertEqual( 109 | set_datetime_zoneinfo(test_datetime_summer_aware), 110 | datetime.strptime("2022-07-14 12:00:00+04:00", self.fmt_datetime_aware), 111 | ) 112 | self.assertEqual( 113 | set_datetime_zoneinfo(test_datetime_winter_aware), 114 | datetime.strptime("2022-12-25 22:00:00-08:00", self.fmt_datetime_aware), 115 | ) 116 | 117 | # with timezone 118 | self.assertEqual( 119 | set_datetime_zoneinfo(test_datetime_summer_aware, "Europe/Paris"), 120 | datetime.strptime("2022-07-14 12:00:00+04:00", self.fmt_datetime_aware), 121 | ) 122 | self.assertEqual( 123 | set_datetime_zoneinfo(test_datetime_winter_aware, "Europe/Paris"), 124 | datetime.strptime("2022-12-25 22:00:00-08:00", self.fmt_datetime_aware), 125 | ) 126 | 127 | 128 | # ############################################################################## 129 | # ##### Stand alone program ######## 130 | # ################################## 131 | if __name__ == "__main__": 132 | unittest.main() 133 | --------------------------------------------------------------------------------