├── MANIFEST.in ├── docs ├── _vale │ ├── ignore_words.txt │ ├── custom │ │ └── Spelling.yml │ └── write-good │ │ ├── meta.json │ │ ├── So.yml │ │ ├── ThereIs.yml │ │ ├── Illusions.yml │ │ ├── E-Prime.yml │ │ ├── README.md │ │ ├── Passive.yml │ │ ├── Weasel.yml │ │ ├── TooWordy.yml │ │ └── Cliches.yml ├── source │ ├── changelog.rst │ ├── getting-started.rst │ ├── _static │ │ └── sitemap-icon.svg │ ├── search-optimization.rst │ ├── contributing.rst │ ├── configuration-values.rst │ ├── index.rst │ ├── conf.py │ └── advanced-configuration.rst ├── requirements.txt └── Makefile ├── tests ├── roots │ └── test-root │ │ ├── conf.py │ │ ├── bar.rst │ │ ├── foo.rst │ │ ├── dolor.rst │ │ ├── elitr.rst │ │ ├── ipsum.rst │ │ ├── lorem.rst │ │ └── index.rst ├── conftest.py ├── test_parallel_mode.py └── test_simple.py ├── requirements_dev.txt ├── readthedocs.yml ├── .vale.ini ├── .gitignore ├── tox.ini ├── .pre-commit-config.yaml ├── MAINTENANCE.md ├── .github ├── dependabot.yml ├── FUNDING.yml └── workflows │ └── continuous-integration.yml ├── LICENSE ├── pyproject.toml ├── README.rst ├── CHANGELOG.rst └── sphinx_sitemap └── __init__.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | -------------------------------------------------------------------------------- /docs/_vale/ignore_words.txt: -------------------------------------------------------------------------------- 1 | Conda 2 | Algolia 3 | -------------------------------------------------------------------------------- /docs/source/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /tests/roots/test-root/conf.py: -------------------------------------------------------------------------------- 1 | extensions = ["sphinx_sitemap"] 2 | -------------------------------------------------------------------------------- /tests/roots/test-root/bar.rst: -------------------------------------------------------------------------------- 1 | bar 2 | === 3 | 4 | This is a bar page 5 | -------------------------------------------------------------------------------- /tests/roots/test-root/foo.rst: -------------------------------------------------------------------------------- 1 | foo 2 | === 3 | 4 | This is a foo page 5 | -------------------------------------------------------------------------------- /tests/roots/test-root/dolor.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Dolor 4 | ===== 5 | 6 | This is the dolor page 7 | -------------------------------------------------------------------------------- /tests/roots/test-root/elitr.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Elitr 4 | ===== 5 | 6 | This is the elitr page 7 | -------------------------------------------------------------------------------- /tests/roots/test-root/ipsum.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Ipsum 4 | ===== 5 | 6 | This is the ipsum page 7 | -------------------------------------------------------------------------------- /tests/roots/test-root/lorem.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Lorem 4 | ===== 5 | 6 | This is the lorem page 7 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | tox 2 | build 3 | pre-commit 4 | flake8 5 | sphinx 6 | sphinx-last-updated-by-git 7 | pytest 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | furo 2 | esbonio 3 | sphinx-contributors 4 | sphinx 5 | sphinx-sitemap 6 | sphinxemoji 7 | sphinxext-opengraph 8 | -------------------------------------------------------------------------------- /tests/roots/test-root/index.rst: -------------------------------------------------------------------------------- 1 | test for basic sitemap 2 | ====================== 3 | 4 | .. toctree:: 5 | 6 | foo 7 | bar 8 | -------------------------------------------------------------------------------- /docs/_vale/custom/Spelling.yml: -------------------------------------------------------------------------------- 1 | extends: spelling 2 | message: "Did you really mean '%s'?" 3 | level: error 4 | ignore: 5 | - ignore_words.txt 6 | -------------------------------------------------------------------------------- /docs/_vale/write-good/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": "https://github.com/errata-ai/write-good/releases.atom", 3 | "vale_version": ">=1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /docs/_vale/write-good/So.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't start a sentence with '%s'." 3 | level: error 4 | raw: 5 | - '(?:[;-]\s)so[\s,]|\bSo[\s,]' 6 | -------------------------------------------------------------------------------- /docs/_vale/write-good/ThereIs.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Don't start a sentence with '%s'." 3 | ignorecase: false 4 | level: error 5 | raw: 6 | - '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b' 7 | -------------------------------------------------------------------------------- /docs/_vale/write-good/Illusions.yml: -------------------------------------------------------------------------------- 1 | extends: repetition 2 | message: "'%s' is repeated!" 3 | level: warning 4 | alpha: true 5 | action: 6 | name: edit 7 | params: 8 | - truncate 9 | - " " 10 | tokens: 11 | - '[^\s]+' 12 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-20.04" 5 | tools: 6 | python: "3.10" 7 | 8 | sphinx: 9 | configuration: docs/source/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | - method: pip 15 | path: . 16 | -------------------------------------------------------------------------------- /.vale.ini: -------------------------------------------------------------------------------- 1 | StylesPath = ./docs/_vale 2 | MinAlertLevel = suggestion 3 | 4 | # SphinxBuildPath = docs/_build 5 | # SphinxAutoBuild = make html 6 | 7 | Packages = write-good, Microsoft 8 | 9 | [*.rst] 10 | BasedOnStyles = Vale, custom 11 | 12 | Vale.Redundancy = YES 13 | Vale.Repetition = YES 14 | Vale.GenderBias = YES 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | *.code-workspace 4 | 5 | # Unit test / coverage reports 6 | .tox 7 | 8 | # Distribution / packaging 9 | *.egg-info/ 10 | dist/ 11 | build/ 12 | 13 | # Environments 14 | .venv 15 | 16 | # Sphinx documentation 17 | docs/_build/ 18 | 19 | # vale packages 20 | docs/_vale/Microsoft/ 21 | docs/_vale/write-good/ 22 | -------------------------------------------------------------------------------- /docs/_vale/write-good/E-Prime.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Try to avoid using '%s'." 3 | ignorecase: true 4 | level: suggestion 5 | tokens: 6 | - am 7 | - are 8 | - aren't 9 | - be 10 | - been 11 | - being 12 | - he's 13 | - here's 14 | - here's 15 | - how's 16 | - i'm 17 | - is 18 | - isn't 19 | - it's 20 | - she's 21 | - that's 22 | - there's 23 | - they're 24 | - was 25 | - wasn't 26 | - we're 27 | - were 28 | - weren't 29 | - what's 30 | - where's 31 | - who's 32 | - you're 33 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py39-sphinx{5,6,7,last} 4 | # Python 3.10 is unsupported below Sphinx4 5 | # See https://github.com/sphinx-doc/sphinx/issues/9816 6 | py3{10,11,12}-sphinx{5,6,7,last} 7 | 8 | [testenv] 9 | deps = 10 | gitpython 11 | pytest 12 | sphinx5: Sphinx[test]~=5.0 13 | sphinx6: Sphinx[test]~=6.0 14 | sphinx7: Sphinx[test]~=7.0 15 | sphinxlast: Sphinx[test] 16 | commands = 17 | pytest -W ignore::DeprecationWarning 18 | 19 | [flake8] 20 | max-line-length = 100 21 | extend-ignore = E203 22 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Install pre-commit hooks via 2 | # pre-commit install 3 | 4 | repos: 5 | 6 | - repo: https://github.com/PyCQA/isort 7 | rev: 7.0.0 8 | hooks: 9 | - id: isort 10 | 11 | - repo: https://github.com/psf/black-pre-commit-mirror 12 | rev: 25.11.0 13 | hooks: 14 | - id: black 15 | 16 | - repo: https://github.com/PyCQA/flake8 17 | rev: 7.3.0 18 | hooks: 19 | - id: flake8 20 | 21 | - repo: https://github.com/sphinx-contrib/sphinx-lint 22 | rev: v1.0.2 23 | hooks: 24 | - id: sphinx-lint 25 | args: [--jobs=1] 26 | files: ^docs/|CHANGELOG.rst|README.rst 27 | types: [rst] 28 | -------------------------------------------------------------------------------- /MAINTENANCE.md: -------------------------------------------------------------------------------- 1 | # Maintaining PyPI Version 2 | 3 | These are the steps, to be run by the maintainer, for making a new Python 4 | package release. 5 | 6 | 1. Rev `__version__` in **sphinx_sitemap/\_\_init\_\_.py**. 7 | 2. Update **CHANGELOG.rst** 8 | 3. Create a tag and push to GitHub: 9 | 10 | git tag -a vX.Y.Z -m "Release vX.Y.Z" 11 | git push --tags origin master 12 | 13 | 4. Build the latest distribution locally: 14 | 15 | python -m build 16 | 17 | 5. Upload to the test pypi.org repository: 18 | 19 | twine upload --repository testpypi dist/* 20 | 21 | 6. Upload to the production pypi.org repository: 22 | 23 | twine upload dist/* 24 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = SphinxSitemap 8 | SOURCEDIR = source 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.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://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | groups: 13 | # Name for the group, which will be used in PR titles and branch names 14 | all-github-actions: 15 | # Group all updates together 16 | patterns: 17 | - "*" 18 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import sphinx 5 | 6 | pytest_plugins = "sphinx.testing.fixtures" 7 | # Exclude 'roots' dirs for pytest test collector 8 | collect_ignore = ["roots"] 9 | 10 | 11 | def pytest_configure(config): 12 | # before Sphinx 3.3.0, the `sphinx` marker is not registered by 13 | # the extension (but by Sphinx's internal pytest config) 14 | config.addinivalue_line("markers", "sphinx") 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def rootdir(): 19 | if sphinx.version_info[:2] < (7, 2): 20 | from sphinx.testing.path import path 21 | 22 | return path(__file__).parent.abspath() / "roots" 23 | 24 | return Path(__file__).resolve().parent / "roots" 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [jdillard] 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: # Replace with a single Liberapay username 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jared Dillard 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 | -------------------------------------------------------------------------------- /docs/source/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | Directly install via ``pip`` by using: 8 | 9 | .. code:: 10 | 11 | pip install sphinx-sitemap 12 | 13 | Or with ``conda`` via ``conda-forge``: 14 | 15 | .. code:: 16 | 17 | conda install -c conda-forge sphinx-sitemap 18 | 19 | Usage 20 | ----- 21 | 22 | Add ``sphinx_sitemap`` to :confval:`extensions` in your Sphinx **conf.py**. 23 | For example: 24 | 25 | .. code:: python 26 | 27 | extensions = ['sphinx_sitemap'] 28 | 29 | Set the value of :confval:`html_baseurl` in your Sphinx **conf.py** to the current 30 | base URL of your documentation. For example: 31 | 32 | .. code:: python 33 | 34 | html_baseurl = 'https://my-site.com/docs/' 35 | 36 | After the HTML finishes building, **sphinx-sitemap** will output the location of the sitemap:: 37 | 38 | sitemap.xml was generated for URL https://my-site.com/docs/ in /path/to/_build/sitemap.xml 39 | 40 | .. tip:: Make sure to confirm the accuracy of the sitemap after installs and upgrades. 41 | 42 | See :doc:`advanced-configuration` for more information about how to use **sphinx-sitemap**. 43 | -------------------------------------------------------------------------------- /docs/source/_static/sitemap-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/test_parallel_mode.py: -------------------------------------------------------------------------------- 1 | import os 2 | from xml.etree import ElementTree as etree 3 | 4 | import pytest 5 | from git import Repo 6 | 7 | 8 | @pytest.fixture(autouse=True, scope="function") 9 | def git_setup(app): 10 | repo = Repo.init(app.srcdir) 11 | repo.index.add(os.listdir(app.srcdir)) 12 | repo.index.commit("test: creating git record for files") 13 | yield 14 | 15 | 16 | @pytest.mark.sphinx( 17 | "html", 18 | freshenv=True, 19 | confoverrides={"html_baseurl": "https://example.org/docs/", "language": "en"}, 20 | ) 21 | def test_parallel(app, status, warning): 22 | app.parallel = 2 23 | app.warningiserror = True 24 | app.build() 25 | assert "sitemap.xml" in os.listdir(app.outdir) 26 | doc = etree.parse(app.outdir / "sitemap.xml") 27 | urls = { 28 | e.text 29 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 30 | } 31 | 32 | assert urls == { 33 | f"https://example.org/docs/en/{d}.html" 34 | for d in [ 35 | "index", 36 | "foo", 37 | "bar", 38 | "lorem", 39 | "ipsum", 40 | "dolor", 41 | "elitr", 42 | "genindex", 43 | "search", 44 | ] 45 | } 46 | assert not warning.getvalue() 47 | -------------------------------------------------------------------------------- /docs/_vale/write-good/README.md: -------------------------------------------------------------------------------- 1 | Based on [write-good](https://github.com/btford/write-good). 2 | 3 | > Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too. 4 | 5 | ``` 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Brian Ford 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ``` 28 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | 7 | [project] 8 | name = "sphinx-sitemap" 9 | description = "Sitemap generator for Sphinx" 10 | authors = [ 11 | {name = "Jared Dillard", email = "jared.dillard@gmail.com"}, 12 | ] 13 | maintainers = [ 14 | {name = "Jared Dillard", email = "jared.dillard@gmail.com"}, 15 | ] 16 | classifiers = [ 17 | "Framework :: Sphinx :: Extension", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Topic :: Documentation :: Sphinx", 22 | ] 23 | license = "MIT" 24 | license-files = ["LICENSE"] 25 | readme = "README.rst" 26 | dependencies = [ 27 | "sphinx-last-updated-by-git", 28 | ] 29 | dynamic = [ 30 | "optional-dependencies", 31 | "version", 32 | ] 33 | 34 | [project.urls] 35 | documentation = "https://sphinx-sitemap.readthedocs.io/en/latest/index.html" 36 | download = "https://pypi.org/project/sphinx-sitemap/" 37 | source = "https://github.com/jdillard/sphinx-sitemap" 38 | changelog = "https://github.com/jdillard/sphinx-sitemap/blob/master/CHANGELOG.rst" 39 | 40 | [tool.setuptools.dynamic] 41 | optional-dependencies = {dev = { file = ["requirements_dev.txt"] }} 42 | version = {attr = "sphinx_sitemap.__version__"} 43 | 44 | [tool.isort] 45 | profile = "black" 46 | -------------------------------------------------------------------------------- /docs/source/search-optimization.rst: -------------------------------------------------------------------------------- 1 | Getting the Most out of the Sitemap 2 | =================================== 3 | 4 | Search Engine Optimization 5 | -------------------------- 6 | 7 | Using robots.txt 8 | ^^^^^^^^^^^^^^^^ 9 | 10 | Add a **robots.txt** file in the **source** directory which has a link to the ``sitemap.xml`` or ``sitemapindex.xml`` file. For example:: 11 | 12 | User-agent: * 13 | 14 | Sitemap: https://my-site.com/docs/sitemap.xml 15 | 16 | Then, add **robots.txt** to :confval:`html_extra_path` in **conf.py**: 17 | 18 | .. code-block:: python 19 | 20 | html_extra_path = ['robots.txt'] 21 | 22 | Submit Sitemap to Search Engines 23 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | Submit the ``sitemap.xml`` or ``sitemapindex.xml`` to the appropriate search engine tools. 26 | 27 | Site Search Optimization 28 | ------------------------ 29 | 30 | Site search crawlers can also take advantage of sitemaps as starting points for crawling. 31 | 32 | Examples: 33 | 34 | - `Algolia`_ 35 | 36 | .. _Algolia: https://www.algolia.com/doc/tools/crawler/apis/configuration/sitemaps/ 37 | 38 | .. _rag-ingestion: 39 | 40 | RAG (Retrieval-Augmented Generation) Ingestion 41 | ----------------------------------------------- 42 | 43 | The sitemap can be used as a structured data source for RAG systems to efficiently discover and ingest documentation content. 44 | 45 | - **Comprehensive Discovery**: The sitemap provides a complete list of all documentation pages, ensuring no content is missed during ingestion 46 | - **Incremental Updates**: Use the ```` timestamps to identify recently updated content and refresh only those embeddings in your RAG system. 47 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | You will need to set up a development environment to make and test your changes 5 | before submitting them. 6 | 7 | Local development 8 | ----------------- 9 | 10 | #. Clone the `sphinx-sitemap repository`_. 11 | 12 | #. Create and activate a virtual environment: 13 | 14 | .. code-block:: console 15 | 16 | python3 -m venv .venv 17 | source .venv/bin/activate 18 | 19 | #. Install development dependencies: 20 | 21 | .. code-block:: console 22 | 23 | pip3 install -r requirements_dev.txt 24 | 25 | #. Install pre-commit Git hook scripts: 26 | 27 | .. code-block:: console 28 | 29 | pre-commit install 30 | 31 | Install a local copy of the extension 32 | ------------------------------------- 33 | 34 | Add **sphinx-sitemap** as a `third party extension`_. 35 | 36 | #. If your project doesn't have an extensions directory, create ``_exts`` and 37 | point **conf.py** to it: 38 | 39 | .. code-block:: python 40 | 41 | sys.path.append(os.path.abspath('../_exts')) 42 | 43 | #. Copy ``sphinx_sitemap`` as a directory in your project's extensions 44 | directory, and rename it to ``sphinx_sitemap_dev``. 45 | 46 | #. Add ``sphinx_sitemap_dev`` to :confval:`extensions`, or if already installed via ``pip``, change ``sphinx_sitemap`` to ``sphinx_sitemap_dev`` in **conf.py**: 47 | 48 | .. code-block:: python 49 | 50 | extensions = ['sphinx_sitemap_dev'] 51 | 52 | You can now make changes to ``sphinx_sitemap_dev``. 53 | 54 | Testing changes 55 | --------------- 56 | 57 | Run ``tox`` before committing changes. 58 | 59 | Current contributors 60 | -------------------- 61 | 62 | Thanks to all who have contributed! 63 | The people that have improved the code: 64 | 65 | .. contributors:: jdillard/sphinx-sitemap 66 | :avatars: 67 | :limit: 100 68 | :exclude: pre-commit-ci[bot],dependabot[bot] 69 | :order: ASC 70 | 71 | 72 | .. _sphinx-sitemap repository: https://github.com/jdillard/sphinx-sitemap 73 | .. _third party extension: http://www.sphinx-doc.org/en/master/ext/thirdparty.html 74 | -------------------------------------------------------------------------------- /docs/source/configuration-values.rst: -------------------------------------------------------------------------------- 1 | Project Configuration Values 2 | ============================ 3 | 4 | A list of of possible configuration values to configure in **conf.py**: 5 | 6 | .. confval:: sitemap_url_scheme 7 | 8 | - **Type**: string 9 | - **Default**: ``'{lang}{version}{link}'`` 10 | - **Description**: The scheme used for URL structure. 11 | See :ref:`configuration_customizing_url_scheme` for more information. 12 | 13 | .. versionadded:: 2.0.0 14 | 15 | .. confval:: sitemap_filename 16 | 17 | - **Type**: string 18 | - **Default**: ``'sitemap.xml'`` 19 | - **Description**: The filename used for the sitemap. 20 | See :ref:`configuration_changing_filename` for more information. 21 | 22 | .. versionadded:: 2.2.0 23 | 24 | .. confval:: sitemap_locales 25 | 26 | - **Type**: list of strings 27 | - **Default**: ``[]`` (empty list) 28 | - **Description**: The list of locales to include in the sitemap. 29 | See :ref:`configuration_supporting_multiple_languages` for more information. 30 | 31 | .. versionadded:: 2.2.0 32 | 33 | .. confval:: sitemap_excludes 34 | 35 | - **Type**: list of strings 36 | - **Default**: ``[]`` (empty list) 37 | - **Description**: The list of pages to exclude from the sitemap. 38 | Supports wildcard patterns. 39 | See :ref:`configuration_excluding_pages` for more information. 40 | 41 | .. versionadded:: 2.6.0 42 | 43 | .. versionchanged:: 2.8.0 44 | Added support for Unix-style wildcard patterns. 45 | 46 | .. confval:: sitemap_show_lastmod 47 | 48 | - **Type**: boolean 49 | - **Default**: ``False`` 50 | - **Description**: Add ```` to sitemap based on last updated time according to Git for each page. 51 | See :ref:`configuration_lastmod` for more information. 52 | 53 | .. versionadded:: 2.7.0 54 | 55 | .. confval:: sitemap_indent 56 | 57 | - **Type**: integer 58 | - **Default**: ``0`` 59 | - **Description**: Number of spaces to use for indentation in the sitemap XML output. 60 | See :ref:`configuration_indent` for more information. 61 | 62 | .. versionadded:: 2.9.0 63 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | sphinx-sitemap 2 | ============== 3 | 4 | A `Sphinx`_ extension to generate multi-version and multi-language 5 | `sitemaps.org`_ compliant sitemaps for the HTML version of your Sphinx 6 | documentation. 7 | 8 | |PyPI version| |Conda Forge| |Downloads| |Parallel Safe| |GitHub Stars| 9 | 10 | Demo 11 | ---- 12 | 13 | You can see this Sphinx project's `sitemap.xml`_ as a simple example. 14 | 15 | Highlights 16 | ---------- 17 | 18 | 1. **Multi-version Support**: Generates sitemaps for multiple documentation versions 19 | 2. **Multi-language Support**: Creates sitemaps for multiple language variants of your documentation 20 | 3. **Content Exclusion**: Allows you to exclude specific pages or patterns from the sitemap 21 | 4. **Last Modified Support**: Includes lastmod timestamps to help understand content freshness 22 | 5. **AI & RAG Optimization**: Helps AI systems discover and index your documentation content 23 | 6. **SEO Optimization**: Improves search engine discoverability of your documentation 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | 28 | getting-started 29 | advanced-configuration 30 | search-optimization 31 | configuration-values 32 | contributing 33 | changelog 34 | 35 | 36 | .. _sitemaps.org: https://www.sitemaps.org/protocol.html 37 | .. _sitemap.xml: https://sphinx-sitemap.readthedocs.io/en/latest/sitemap.xml 38 | .. _Sphinx: http://sphinx-doc.org/ 39 | 40 | .. |PyPI version| image:: https://img.shields.io/pypi/v/sphinx-sitemap.svg 41 | :target: https://pypi.python.org/pypi/sphinx-sitemap 42 | :alt: Latest PyPi Version 43 | .. |Conda Forge| image:: https://img.shields.io/conda/vn/conda-forge/sphinx-sitemap.svg 44 | :target: https://anaconda.org/conda-forge/sphinx-sitemap 45 | :alt: Latest Conda Forge version 46 | .. |Downloads| image:: https://static.pepy.tech/badge/sphinx-sitemap/month 47 | :target: https://pepy.tech/project/sphinx-sitemap 48 | :alt: PyPi Downloads per month 49 | .. |Parallel Safe| image:: https://img.shields.io/badge/parallel%20safe-true-brightgreen 50 | :target: # 51 | :alt: Parallel read/write safe 52 | .. |GitHub Stars| image:: https://img.shields.io/github/stars/jdillard/sphinx-sitemap?style=social 53 | :target: https://github.com/jdillard/sphinx-sitemap 54 | :alt: GitHub Repository stars 55 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Sphinx Sitemap Generator Extension 2 | ================================== 3 | 4 | A `Sphinx`_ extension to generate multiversion and multilanguage 5 | `sitemaps.org`_ compliant sitemaps for the HTML version of your Sphinx 6 | documentation. 7 | 8 | | |PyPI version| |Conda Forge| |Downloads| |Parallel Safe| 9 | | |Code style: Black| |Docs Build| |CI Workflow| 10 | 11 | Documentation 12 | ------------- 13 | 14 | See `sphinx-sitemap documentation`_ for installation and configuration instructions. 15 | 16 | Contributing 17 | ------------ 18 | 19 | Pull Requests welcome! See `Contributing`_ for instructions on how best to contribute. 20 | 21 | License 22 | ------- 23 | 24 | **sphinx-sitemap** is made available under a **MIT license**; see `LICENSE`_ for 25 | details. 26 | 27 | Originally based on the sitemap generator in the `guzzle_sphinx_theme`_ project, 28 | also licensed under the MIT license. 29 | 30 | .. _Contributing: https://sphinx-sitemap.readthedocs.io/en/latest/contributing.html 31 | .. _guzzle_sphinx_theme: https://github.com/guzzle/guzzle_sphinx_theme 32 | .. _LICENSE: LICENSE 33 | .. _Sphinx: http://sphinx-doc.org/ 34 | .. _sitemaps.org: https://www.sitemaps.org/protocol.html 35 | .. _sphinx-sitemap documentation: https://sphinx-sitemap.readthedocs.io/en/latest/index.html 36 | 37 | .. |PyPI version| image:: https://img.shields.io/pypi/v/sphinx-sitemap.svg 38 | :target: https://pypi.python.org/pypi/sphinx-sitemap 39 | .. |Conda Forge| image:: https://img.shields.io/conda/vn/conda-forge/sphinx-sitemap.svg 40 | :target: https://anaconda.org/conda-forge/sphinx-sitemap 41 | .. |Downloads| image:: https://static.pepy.tech/badge/sphinx-sitemap/month 42 | :target: https://pepy.tech/project/sphinx-sitemap 43 | .. |Code style: Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg 44 | :target: https://github.com/psf/black 45 | .. |Parallel Safe| image:: https://img.shields.io/badge/parallel%20safe-true-brightgreen 46 | :target: # 47 | .. |Docs Build| image:: https://readthedocs.org/projects/sphinx-sitemap/badge/?version=latest 48 | :target: https://sphinx-sitemap.readthedocs.io/en/latest/?badge=latest 49 | :alt: Documentation Status 50 | .. |CI Workflow| image:: https://github.com/jdillard/sphinx-sitemap/actions/workflows/continuous-integration.yml/badge.svg 51 | :target: https://github.com/jdillard/sphinx-sitemap/actions/workflows/continuous-integration.yml 52 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | 2 | name: continuous-integration 3 | on: 4 | push: 5 | branches: [master] 6 | tags: 7 | - "v[0-9]+.[0-9]+.[0-9]+*" 8 | pull_request: 9 | jobs: 10 | pre-commit: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - name: Set up Python 3.10 15 | uses: actions/setup-python@v6 16 | with: 17 | python-version: "3.10" 18 | - uses: pre-commit/action@v3.0.1 19 | tests: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | python-version: ['3.9', '3.10', '3.11', '3.12'] 24 | sphinx-version: [''] 25 | include: 26 | - python-version: '3.12' 27 | sphinx-version: 'dev' 28 | - python-version: '3.11' 29 | sphinx-version: '7' 30 | - python-version: '3.10' 31 | sphinx-version: '6' 32 | - python-version: '3.9' 33 | sphinx-version: '5' 34 | steps: 35 | - uses: actions/checkout@v6 36 | - name: Setup Python versions 37 | uses: actions/setup-python@v6 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | - name: Install Python dependencies 41 | run: | 42 | set -eo pipefail 43 | if [[ "${{ matrix.sphinx-version }}" != "" ]]; then 44 | if [[ "${{ matrix.sphinx-version }}" == "dev" ]]; then 45 | SPHINX_INSTALL="git+https://github.com/sphinx-doc/sphinx.git" 46 | else 47 | SPHINX_INSTALL="sphinx==${{ matrix.sphinx-version }}.*" 48 | fi 49 | fi 50 | set -x 51 | python -VV 52 | python -m site 53 | python -m pip install --upgrade pip setuptools wheel 54 | pip install -r requirements_dev.txt $SPHINX_INSTALL 55 | - name: Install Package 56 | run: | 57 | python -m pip install . 58 | - name: Run Tests for ${{ matrix.python-version }} 59 | run: | 60 | python -m tox 61 | vale: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout repository 65 | uses: actions/checkout@v6 66 | 67 | - name: Install docutils 68 | run: sudo apt-get install -y docutils 69 | 70 | - name: Lint with Vale 71 | uses: errata-ai/vale-action@reviewdog 72 | env: 73 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 74 | with: 75 | files: docs 76 | # github-pr-check, github-pr-review, github-check 77 | reporter: github-pr-check 78 | -------------------------------------------------------------------------------- /docs/_vale/write-good/Passive.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' may be passive voice. Use active voice if you can." 3 | ignorecase: true 4 | level: warning 5 | raw: 6 | - \b(am|are|were|being|is|been|was|be)\b\s* 7 | tokens: 8 | - '[\w]+ed' 9 | - awoken 10 | - beat 11 | - become 12 | - been 13 | - begun 14 | - bent 15 | - beset 16 | - bet 17 | - bid 18 | - bidden 19 | - bitten 20 | - bled 21 | - blown 22 | - born 23 | - bought 24 | - bound 25 | - bred 26 | - broadcast 27 | - broken 28 | - brought 29 | - built 30 | - burnt 31 | - burst 32 | - cast 33 | - caught 34 | - chosen 35 | - clung 36 | - come 37 | - cost 38 | - crept 39 | - cut 40 | - dealt 41 | - dived 42 | - done 43 | - drawn 44 | - dreamt 45 | - driven 46 | - drunk 47 | - dug 48 | - eaten 49 | - fallen 50 | - fed 51 | - felt 52 | - fit 53 | - fled 54 | - flown 55 | - flung 56 | - forbidden 57 | - foregone 58 | - forgiven 59 | - forgotten 60 | - forsaken 61 | - fought 62 | - found 63 | - frozen 64 | - given 65 | - gone 66 | - gotten 67 | - ground 68 | - grown 69 | - heard 70 | - held 71 | - hidden 72 | - hit 73 | - hung 74 | - hurt 75 | - kept 76 | - knelt 77 | - knit 78 | - known 79 | - laid 80 | - lain 81 | - leapt 82 | - learnt 83 | - led 84 | - left 85 | - lent 86 | - let 87 | - lighted 88 | - lost 89 | - made 90 | - meant 91 | - met 92 | - misspelt 93 | - mistaken 94 | - mown 95 | - overcome 96 | - overdone 97 | - overtaken 98 | - overthrown 99 | - paid 100 | - pled 101 | - proven 102 | - put 103 | - quit 104 | - read 105 | - rid 106 | - ridden 107 | - risen 108 | - run 109 | - rung 110 | - said 111 | - sat 112 | - sawn 113 | - seen 114 | - sent 115 | - set 116 | - sewn 117 | - shaken 118 | - shaven 119 | - shed 120 | - shod 121 | - shone 122 | - shorn 123 | - shot 124 | - shown 125 | - shrunk 126 | - shut 127 | - slain 128 | - slept 129 | - slid 130 | - slit 131 | - slung 132 | - smitten 133 | - sold 134 | - sought 135 | - sown 136 | - sped 137 | - spent 138 | - spilt 139 | - spit 140 | - split 141 | - spoken 142 | - spread 143 | - sprung 144 | - spun 145 | - stolen 146 | - stood 147 | - stridden 148 | - striven 149 | - struck 150 | - strung 151 | - stuck 152 | - stung 153 | - stunk 154 | - sung 155 | - sunk 156 | - swept 157 | - swollen 158 | - sworn 159 | - swum 160 | - swung 161 | - taken 162 | - taught 163 | - thought 164 | - thrived 165 | - thrown 166 | - thrust 167 | - told 168 | - torn 169 | - trodden 170 | - understood 171 | - upheld 172 | - upset 173 | - wed 174 | - wept 175 | - withheld 176 | - withstood 177 | - woken 178 | - won 179 | - worn 180 | - wound 181 | - woven 182 | - written 183 | - wrung 184 | -------------------------------------------------------------------------------- /docs/_vale/write-good/Weasel.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' is a weasel word!" 3 | ignorecase: true 4 | level: warning 5 | tokens: 6 | - absolutely 7 | - accidentally 8 | - additionally 9 | - allegedly 10 | - alternatively 11 | - angrily 12 | - anxiously 13 | - approximately 14 | - awkwardly 15 | - badly 16 | - barely 17 | - beautifully 18 | - blindly 19 | - boldly 20 | - bravely 21 | - brightly 22 | - briskly 23 | - bristly 24 | - bubbly 25 | - busily 26 | - calmly 27 | - carefully 28 | - carelessly 29 | - cautiously 30 | - cheerfully 31 | - clearly 32 | - closely 33 | - coldly 34 | - completely 35 | - consequently 36 | - correctly 37 | - courageously 38 | - crinkly 39 | - cruelly 40 | - crumbly 41 | - cuddly 42 | - currently 43 | - daily 44 | - daringly 45 | - deadly 46 | - definitely 47 | - deliberately 48 | - doubtfully 49 | - dumbly 50 | - eagerly 51 | - early 52 | - easily 53 | - elegantly 54 | - enormously 55 | - enthusiastically 56 | - equally 57 | - especially 58 | - eventually 59 | - exactly 60 | - exceedingly 61 | - exclusively 62 | - extremely 63 | - fairly 64 | - faithfully 65 | - fatally 66 | - fiercely 67 | - finally 68 | - fondly 69 | - few 70 | - foolishly 71 | - fortunately 72 | - frankly 73 | - frantically 74 | - generously 75 | - gently 76 | - giggly 77 | - gladly 78 | - gracefully 79 | - greedily 80 | - happily 81 | - hardly 82 | - hastily 83 | - healthily 84 | - heartily 85 | - helpfully 86 | - honestly 87 | - hourly 88 | - hungrily 89 | - hurriedly 90 | - immediately 91 | - impatiently 92 | - inadequately 93 | - ingeniously 94 | - innocently 95 | - inquisitively 96 | - interestingly 97 | - irritably 98 | - jiggly 99 | - joyously 100 | - justly 101 | - kindly 102 | - largely 103 | - lately 104 | - lazily 105 | - likely 106 | - literally 107 | - lonely 108 | - loosely 109 | - loudly 110 | - loudly 111 | - luckily 112 | - madly 113 | - many 114 | - mentally 115 | - mildly 116 | - monthly 117 | - mortally 118 | - mostly 119 | - mysteriously 120 | - neatly 121 | - nervously 122 | - nightly 123 | - noisily 124 | - normally 125 | - obediently 126 | - occasionally 127 | - only 128 | - openly 129 | - painfully 130 | - particularly 131 | - patiently 132 | - perfectly 133 | - politely 134 | - poorly 135 | - powerfully 136 | - presumably 137 | - previously 138 | - promptly 139 | - punctually 140 | - quarterly 141 | - quickly 142 | - quietly 143 | - rapidly 144 | - rarely 145 | - really 146 | - recently 147 | - recklessly 148 | - regularly 149 | - remarkably 150 | - relatively 151 | - reluctantly 152 | - repeatedly 153 | - rightfully 154 | - roughly 155 | - rudely 156 | - sadly 157 | - safely 158 | - selfishly 159 | - sensibly 160 | - seriously 161 | - sharply 162 | - shortly 163 | - shyly 164 | - significantly 165 | - silently 166 | - simply 167 | - sleepily 168 | - slowly 169 | - smartly 170 | - smelly 171 | - smoothly 172 | - softly 173 | - solemnly 174 | - sparkly 175 | - speedily 176 | - stealthily 177 | - sternly 178 | - stupidly 179 | - substantially 180 | - successfully 181 | - suddenly 182 | - surprisingly 183 | - suspiciously 184 | - swiftly 185 | - tenderly 186 | - tensely 187 | - thoughtfully 188 | - tightly 189 | - timely 190 | - truthfully 191 | - unexpectedly 192 | - unfortunately 193 | - usually 194 | - very 195 | - victoriously 196 | - violently 197 | - vivaciously 198 | - warmly 199 | - waverly 200 | - weakly 201 | - wearily 202 | - weekly 203 | - wildly 204 | - wisely 205 | - worldly 206 | - wrinkly 207 | - yearly 208 | -------------------------------------------------------------------------------- /docs/_vale/write-good/TooWordy.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "'%s' is too wordy." 3 | ignorecase: true 4 | level: warning 5 | tokens: 6 | - a number of 7 | - abundance 8 | - accede to 9 | - accelerate 10 | - accentuate 11 | - accompany 12 | - accomplish 13 | - accorded 14 | - accrue 15 | - acquiesce 16 | - acquire 17 | - additional 18 | - adjacent to 19 | - adjustment 20 | - admissible 21 | - advantageous 22 | - adversely impact 23 | - advise 24 | - aforementioned 25 | - aggregate 26 | - aircraft 27 | - all of 28 | - all things considered 29 | - alleviate 30 | - allocate 31 | - along the lines of 32 | - already existing 33 | - alternatively 34 | - amazing 35 | - ameliorate 36 | - anticipate 37 | - apparent 38 | - appreciable 39 | - as a matter of fact 40 | - as a means of 41 | - as far as I'm concerned 42 | - as of yet 43 | - as to 44 | - as yet 45 | - ascertain 46 | - assistance 47 | - at the present time 48 | - at this time 49 | - attain 50 | - attributable to 51 | - authorize 52 | - because of the fact that 53 | - belated 54 | - benefit from 55 | - bestow 56 | - by means of 57 | - by virtue of 58 | - by virtue of the fact that 59 | - cease 60 | - close proximity 61 | - commence 62 | - comply with 63 | - concerning 64 | - consequently 65 | - consolidate 66 | - constitutes 67 | - demonstrate 68 | - depart 69 | - designate 70 | - discontinue 71 | - due to the fact that 72 | - each and every 73 | - economical 74 | - eliminate 75 | - elucidate 76 | - employ 77 | - endeavor 78 | - enumerate 79 | - equitable 80 | - equivalent 81 | - evaluate 82 | - evidenced 83 | - exclusively 84 | - expedite 85 | - expend 86 | - expiration 87 | - facilitate 88 | - factual evidence 89 | - feasible 90 | - finalize 91 | - first and foremost 92 | - for all intents and purposes 93 | - for the most part 94 | - for the purpose of 95 | - forfeit 96 | - formulate 97 | - have a tendency to 98 | - honest truth 99 | - however 100 | - if and when 101 | - impacted 102 | - implement 103 | - in a manner of speaking 104 | - in a timely manner 105 | - in a very real sense 106 | - in accordance with 107 | - in addition 108 | - in all likelihood 109 | - in an effort to 110 | - in between 111 | - in excess of 112 | - in lieu of 113 | - in light of the fact that 114 | - in many cases 115 | - in my opinion 116 | - in order to 117 | - in regard to 118 | - in some instances 119 | - in terms of 120 | - in the case of 121 | - in the event that 122 | - in the final analysis 123 | - in the nature of 124 | - in the near future 125 | - in the process of 126 | - inception 127 | - incumbent upon 128 | - indicate 129 | - indication 130 | - initiate 131 | - irregardless 132 | - is applicable to 133 | - is authorized to 134 | - is responsible for 135 | - it is 136 | - it is essential 137 | - it seems that 138 | - it was 139 | - magnitude 140 | - maximum 141 | - methodology 142 | - minimize 143 | - minimum 144 | - modify 145 | - monitor 146 | - multiple 147 | - necessitate 148 | - nevertheless 149 | - not certain 150 | - not many 151 | - not often 152 | - not unless 153 | - not unlike 154 | - notwithstanding 155 | - null and void 156 | - numerous 157 | - objective 158 | - obligate 159 | - obtain 160 | - on the contrary 161 | - on the other hand 162 | - one particular 163 | - optimum 164 | - overall 165 | - owing to the fact that 166 | - participate 167 | - particulars 168 | - pass away 169 | - pertaining to 170 | - point in time 171 | - portion 172 | - possess 173 | - preclude 174 | - previously 175 | - prior to 176 | - prioritize 177 | - procure 178 | - proficiency 179 | - provided that 180 | - purchase 181 | - put simply 182 | - readily apparent 183 | - refer back 184 | - regarding 185 | - relocate 186 | - remainder 187 | - remuneration 188 | - requirement 189 | - reside 190 | - residence 191 | - retain 192 | - satisfy 193 | - shall 194 | - should you wish 195 | - similar to 196 | - solicit 197 | - span across 198 | - strategize 199 | - subsequent 200 | - substantial 201 | - successfully complete 202 | - sufficient 203 | - terminate 204 | - the month of 205 | - the point I am trying to make 206 | - therefore 207 | - time period 208 | - took advantage of 209 | - transmit 210 | - transpire 211 | - type of 212 | - until such time as 213 | - utilization 214 | - utilize 215 | - validate 216 | - various different 217 | - what I mean to say is 218 | - whether or not 219 | - with respect to 220 | - with the exception of 221 | - witnessed 222 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for the Sphinx documentation builder. 3 | # 4 | # This file does only contain a selection of the most common options. For a 5 | # full list see the documentation: 6 | # http://www.sphinx-doc.org/en/master/config 7 | 8 | # -- Path setup -------------------------------------------------------------- 9 | 10 | # If extensions (or modules to document with autodoc) are in another directory, 11 | # add these directories to sys.path here. If the directory is relative to the 12 | # documentation root, use os.path.abspath to make it absolute, like shown here. 13 | # 14 | import re 15 | import subprocess 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = "Sphinx Sitemap" 20 | copyright = "Jared Dillard" 21 | author = "Jared Dillard" 22 | 23 | # check if the current commit is tagged as a release (vX.Y.Z) 24 | GIT_TAG_OUTPUT = subprocess.check_output(["git", "tag", "--points-at", "HEAD"]) 25 | current_tag = GIT_TAG_OUTPUT.decode().strip() 26 | if re.match(r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$", current_tag): 27 | version = current_tag 28 | else: 29 | version = "latest" 30 | # The full version, including alpha/beta/rc tags 31 | release = "" 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | "sphinx_sitemap", 45 | "sphinxemoji.sphinxemoji", 46 | "sphinx_contributors", 47 | "sphinx.ext.intersphinx", 48 | "sphinxext.opengraph", 49 | ] 50 | 51 | # Add any paths that contain templates here, relative to this directory. 52 | templates_path = ["_templates"] 53 | 54 | # The suffix(es) of source filenames. 55 | # You can specify multiple suffix as a list of string: 56 | # 57 | # source_suffix = ['.rst', '.md'] 58 | source_suffix = ".rst" 59 | 60 | # The master toctree document. 61 | master_doc = "index" 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = "en" 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This pattern also affects html_static_path and html_extra_path . 73 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = "sphinx" 77 | 78 | intersphinx_mapping = { 79 | "sphinx": ("https://www.sphinx-doc.org/en/master/", None), 80 | } 81 | 82 | 83 | # -- Options for HTML output ------------------------------------------------- 84 | 85 | # The theme to use for HTML and HTML Help pages. See the documentation for 86 | # a list of builtin themes. 87 | # 88 | html_theme = "furo" 89 | 90 | # Theme options are theme-specific and customize the look and feel of a theme 91 | # further. For a list of options available for each theme, see the 92 | # documentation. 93 | # 94 | html_theme_options = {} 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ["_static"] 100 | html_logo = "_static/sitemap-icon.svg" 101 | html_title = "Sphinx Sitemap" 102 | 103 | ogp_site_url = "https://sphinx-sitemap.readthedocs.io/" 104 | ogp_image = "https://sphinx-sitemap.readthedocs.io/en/latest/_static/sitemap-icon.svg" 105 | 106 | # Custom sidebar templates, must be a dictionary that maps document names 107 | # to template names. 108 | # 109 | # The default sidebars (for documents that don't match any pattern) are 110 | # defined by theme itself. Builtin themes are using these templates by 111 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 112 | # 'searchbox.html']``. 113 | # 114 | # html_sidebars = {} 115 | 116 | html_baseurl = "https://sphinx-sitemap.readthedocs.org/" 117 | 118 | sitemap_show_lastmod = True 119 | 120 | # -- Options for HTMLHelp output --------------------------------------------- 121 | 122 | # Output file base name for HTML help builder. 123 | htmlhelp_basename = "SphinxSitemapdoc" 124 | 125 | 126 | # -- Options for LaTeX output ------------------------------------------------ 127 | 128 | latex_elements = { 129 | # The paper size ('letterpaper' or 'a4paper'). 130 | # 131 | # 'papersize': 'letterpaper', 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 134 | # 'pointsize': '10pt', 135 | # Additional stuff for the LaTeX preamble. 136 | # 137 | # 'preamble': '', 138 | # Latex figure (float) alignment 139 | # 140 | # 'figure_align': 'htbp', 141 | } 142 | 143 | # Grouping the document tree into LaTeX files. List of tuples 144 | # (source start file, target name, title, 145 | # author, documentclass [howto, manual, or own class]). 146 | latex_documents = [ 147 | ( 148 | master_doc, 149 | "SphinxSitemap.tex", 150 | "Sphinx Sitemap Documentation", 151 | "Jared Dillard", 152 | "manual", 153 | ) 154 | ] 155 | 156 | 157 | # -- Options for manual page output ------------------------------------------ 158 | 159 | # One entry per manual page. List of tuples 160 | # (source start file, name, description, authors, manual section). 161 | man_pages = [(master_doc, "sphinxsitemap", "Sphinx Sitemap Documentation", [author], 1)] 162 | 163 | 164 | # -- Options for Texinfo output ---------------------------------------------- 165 | 166 | # Grouping the document tree into Texinfo files. List of tuples 167 | # (source start file, target name, title, author, 168 | # dir menu entry, description, category) 169 | texinfo_documents = [ 170 | ( 171 | master_doc, 172 | "SphinxSitemap", 173 | "Sphinx Sitemap Documentation", 174 | author, 175 | "SphinxSitemap", 176 | "One line description of project.", 177 | "Miscellaneous", 178 | ) 179 | ] 180 | 181 | 182 | def setup(app): 183 | app.add_object_type( 184 | "confval", 185 | "confval", 186 | objname="configuration value", 187 | indextemplate="pair: %s; configuration value", 188 | ) 189 | -------------------------------------------------------------------------------- /tests/test_simple.py: -------------------------------------------------------------------------------- 1 | import os 2 | from xml.etree import ElementTree as etree 3 | 4 | import pytest 5 | from git import Repo 6 | 7 | 8 | @pytest.fixture(autouse=True, scope="function") 9 | def git_setup(app): 10 | repo = Repo.init(app.srcdir) 11 | repo.index.add(os.listdir(app.srcdir)) 12 | repo.index.commit("test: creating git record for files") 13 | yield 14 | 15 | 16 | @pytest.mark.sphinx( 17 | "html", 18 | freshenv=True, 19 | confoverrides={"html_baseurl": "https://example.org/docs/", "language": "en"}, 20 | ) 21 | def test_simple_html(app, status, warning): 22 | """Tests basic HTML sitemap generation with all pages included.""" 23 | app.warningiserror = True 24 | app.build() 25 | assert "sitemap.xml" in os.listdir(app.outdir) 26 | doc = etree.parse(app.outdir / "sitemap.xml") 27 | urls = { 28 | e.text 29 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 30 | } 31 | 32 | assert urls == { 33 | f"https://example.org/docs/en/{d}.html" 34 | for d in [ 35 | "index", 36 | "foo", 37 | "bar", 38 | "lorem", 39 | "ipsum", 40 | "dolor", 41 | "elitr", 42 | "genindex", 43 | "search", 44 | ] 45 | } 46 | 47 | 48 | @pytest.mark.sphinx( 49 | "html", 50 | freshenv=True, 51 | confoverrides={ 52 | "html_baseurl": "https://example.org/docs/", 53 | "language": "en", 54 | "html_file_suffix": ".htm", 55 | }, 56 | ) 57 | def test_html_file_suffix(app, status, warning): 58 | """Tests sitemap generation with custom HTML file suffix (.htm).""" 59 | app.warningiserror = True 60 | app.build() 61 | assert "sitemap.xml" in os.listdir(app.outdir) 62 | doc = etree.parse(app.outdir / "sitemap.xml") 63 | urls = { 64 | e.text 65 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 66 | } 67 | 68 | assert urls == { 69 | f"https://example.org/docs/en/{d}.htm" 70 | for d in [ 71 | "index", 72 | "foo", 73 | "bar", 74 | "lorem", 75 | "ipsum", 76 | "dolor", 77 | "elitr", 78 | "genindex", 79 | "search", 80 | ] 81 | } 82 | 83 | 84 | @pytest.mark.sphinx( 85 | "dirhtml", 86 | freshenv=True, 87 | confoverrides={"html_baseurl": "https://example.org/docs/", "language": "en"}, 88 | ) 89 | def test_simple_dirhtml(app, status, warning): 90 | """Tests sitemap generation with DirectoryHTMLBuilder (clean URLs).""" 91 | app.warningiserror = True 92 | app.build() 93 | assert "sitemap.xml" in os.listdir(app.outdir) 94 | doc = etree.parse(app.outdir / "sitemap.xml") 95 | urls = { 96 | e.text 97 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 98 | } 99 | 100 | assert urls == { 101 | f"https://example.org/docs/en/{d}" 102 | for d in [ 103 | "", 104 | "foo/", 105 | "bar/", 106 | "lorem/", 107 | "ipsum/", 108 | "dolor/", 109 | "elitr/", 110 | "genindex/", 111 | "search/", 112 | ] 113 | } 114 | 115 | 116 | @pytest.mark.sphinx( 117 | "html", 118 | freshenv=True, 119 | confoverrides={ 120 | "html_baseurl": "https://example.org/docs/", 121 | "language": "en", 122 | "sitemap_excludes": ["search.html", "genindex.html"], 123 | }, 124 | ) 125 | def test_simple_excludes(app, status, warning): 126 | """Tests exact string matching for sitemap exclusions (backward compatibility).""" 127 | app.warningiserror = True 128 | app.build() 129 | assert "sitemap.xml" in os.listdir(app.outdir) 130 | doc = etree.parse(app.outdir / "sitemap.xml") 131 | urls = { 132 | e.text 133 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 134 | } 135 | 136 | assert urls == { 137 | f"https://example.org/docs/en/{d}.html" 138 | for d in [ 139 | "index", 140 | "foo", 141 | "bar", 142 | "lorem", 143 | "ipsum", 144 | "dolor", 145 | "elitr", 146 | ] 147 | } 148 | 149 | 150 | @pytest.mark.sphinx( 151 | "html", 152 | freshenv=True, 153 | confoverrides={ 154 | "html_baseurl": "https://example.org/docs/", 155 | "language": "en", 156 | "sitemap_excludes": ["*index*.html", "search.html"], 157 | }, 158 | ) 159 | def test_wildcard_excludes(app, status, warning): 160 | """Tests that *index*.html wildcard pattern excludes both "index.html" and "genindex.html".""" 161 | app.warningiserror = True 162 | app.build() 163 | assert "sitemap.xml" in os.listdir(app.outdir) 164 | doc = etree.parse(app.outdir / "sitemap.xml") 165 | urls = { 166 | e.text 167 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 168 | } 169 | 170 | # *index*.html should exclude both "genindex.html" and "index.html" 171 | assert urls == { 172 | f"https://example.org/docs/en/{d}.html" 173 | for d in [ 174 | "foo", 175 | "bar", 176 | "lorem", 177 | "ipsum", 178 | "dolor", 179 | "elitr", 180 | ] 181 | } 182 | 183 | 184 | @pytest.mark.sphinx( 185 | "html", 186 | freshenv=True, 187 | confoverrides={ 188 | "html_baseurl": "https://example.org/docs/", 189 | "language": "en", 190 | "sitemap_excludes": ["l*.html"], # Excludes lorem.html but not other files 191 | }, 192 | ) 193 | def test_pattern_excludes(app, status, warning): 194 | """Tests that l*.html wildcard pattern excludes only "lorem.html".""" 195 | app.warningiserror = True 196 | app.build() 197 | assert "sitemap.xml" in os.listdir(app.outdir) 198 | doc = etree.parse(app.outdir / "sitemap.xml") 199 | urls = { 200 | e.text 201 | for e in doc.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc") 202 | } 203 | 204 | # l*.html should exclude "lorem.html" 205 | assert urls == { 206 | f"https://example.org/docs/en/{d}.html" 207 | for d in [ 208 | "index", 209 | "foo", 210 | "bar", 211 | "ipsum", 212 | "dolor", 213 | "elitr", 214 | "genindex", 215 | "search", 216 | ] 217 | } 218 | 219 | 220 | @pytest.mark.sphinx( 221 | "html", 222 | freshenv=True, 223 | confoverrides={ 224 | "html_baseurl": "https://example.org/docs/", 225 | "language": "en", 226 | "sitemap_indent": 2, 227 | }, 228 | ) 229 | def test_indent(app, status, warning): 230 | """Tests that xml output is indented""" 231 | app.warningiserror = True 232 | app.build() 233 | assert "sitemap.xml" in os.listdir(app.outdir) 234 | with open(app.outdir / "sitemap.xml", "r") as fd: 235 | lines = fd.readlines() 236 | 237 | assert lines[0][0] == "<" 238 | assert lines[1][0] == "<" 239 | assert lines[2][0:3] == " <" 240 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | .. vale off 2 | 3 | Changelog 4 | ========= 5 | 6 | 2.9.0 7 | ----- 8 | 9 | - |:sparkles:| NEW: Add :confval:`sitemap_indent` configuration value to control XML indentation 10 | `#112 `_ 11 | 12 | 2.8.0 13 | ----- 14 | 15 | - |:sparkles:| NEW: Add support for wildcard patterns to :confval:`sitemap_excludes` 16 | `#113 `_ 17 | 18 | 2.7.2 19 | ----- 20 | 21 | *Release date: 2025-06-26* 22 | 23 | - |:bug:| FIX: Change :confval:`sitemap_show_lastmod` to default of ``False`` 24 | 25 | 2.7.1 26 | ----- 27 | 28 | *Release date: 2025-06-20* 29 | 30 | - Remove support for Python 3.8 31 | 32 | 2.7.0 33 | ----- 34 | 35 | *Release date: 2025-06-20* 36 | 37 | * |:sparkles:| NEW: Add support for ``lastmod`` using `sphinx-last-updated-by-git`_ 38 | `#95 `_ 39 | 40 | 2.6.0 41 | ----- 42 | 43 | *Release date: 2024-04-28* 44 | 45 | * |:wrench:| MAINT: Fix deprecated sphinx.testing.path 46 | `#83 `_ 47 | * Drop test support for Python 3.7 and Sphinx 2, 3, and 4. 48 | * |:sparkles:| NEW: Add sitemap_excludes configuration 49 | `#91 `_ 50 | 51 | 2.5.1 52 | ----- 53 | 54 | *Release date: 2023-08-17* 55 | 56 | * |:bug:| FIX: Fix Path use for Sphinx 7.2 57 | `#70 `_ 58 | * |:bug:| FIX: Fix incremental building by preventing multiprocessing queue from being pickled with environment 59 | `#62 `_ 60 | * |:wrench:| MAINT: Add docstrings and type hints 61 | `#61 `_ 62 | 63 | 2.5.0 64 | ----- 65 | 66 | *Release date: 2023-01-28* 67 | 68 | * |:books:| DOCS: Calculate version for sitemap based on current tag 69 | `#53 `_ 70 | * |:test_tube:| TESTS: Add Sphinx 6 env to tox 71 | `#55 `_ 72 | * |:sparkles:| NEW: Add support for Sphinx config "html_file_suffix" 73 | `#57 `_ 74 | * |:books:| DOCS: Add site search optimization 75 | `#58 `_ 76 | 77 | 2.4.0 78 | ----- 79 | 80 | *Release date: 2022-12-26* 81 | 82 | * |:books:| DOCS: Add ReadTheDocs docs 83 | `#45 `_ 84 | * |:wrench:| MAINT: General code clean up 85 | `#46 `_ 86 | * |:sparkles:| NEW: Add support for parallel mode 87 | `#47 `_ 88 | * |:test_tube:| TESTS: Add tests for ``dirhtml`` builder 89 | `#48 `_ 90 | * |:test_tube:| TESTS: Add vale support for docs 91 | `#49 `_ 92 | * |:bug:| FIX: Fix wheel includes so they don't include docs and tests 93 | `#51 `_ 94 | * |:books:| DOCS: Add write-good and improve writing 95 | `#52 `_ 96 | 97 | 2.3.0 98 | ----- 99 | 100 | *Release date: 2022-12-21* 101 | 102 | * |:wrench:| MAINT: Clean up how package versions are handled 103 | * |:test_tube:| TESTS: Install pre-commit with ``isort``, ``black``, and ``flake8`` 104 | `#35 `_ 105 | * |:books:| DOCS: Improve the wording of the README to help with issues upgrading to Sphinx 5 106 | `#36 `_ 107 | * |:bug:| FIX: Follow correct format for multilingual sitemaps 108 | `#38 `_ 109 | * |:wrench:| MAINT: Update the build process 110 | `#39 `_ 111 | * |:test_tube:| TESTS: Add testing infrastructure 112 | `#41 `_ 113 | `#42 `_ 114 | * |:wrench:| MAINT: Use logging for all logging messages 115 | `#40 `_ 116 | 117 | 2.2.1 118 | ----- 119 | 120 | *Release date: 2022-11-11* 121 | 122 | * |:books:| DOCS: Fix :confval:`sitemap_url_scheme` default value in **README** file 123 | `#32 `_ 124 | * |:wrench:| MAINT: Clean up package classifiers 125 | * |:wrench:| MAINT: Add **LICENSE** to source distributions 126 | `#27 `_ 127 | * |:books:| DOCS: Add Conda Forge badge to **README** file 128 | 129 | 2.2.0 130 | ------ 131 | 132 | *Release date: 2020-08-05* 133 | 134 | * |:wrench:| MAINT: Add ``parallel_write_safe`` flag and set to `False` 135 | `#20 `_. 136 | * |:sparkles:| Add :confval:`sitemap_locales` that creates an allow list of locales 137 | `#25 `_. 138 | * |:sparkles:| Add :confval:`sitemap_filename` that allows for custom sitemap name 139 | `#26 `_. 140 | 141 | 2.1.0 142 | ----- 143 | 144 | *Release date: 2020-02-22* 145 | 146 | * |:bug:| FIX: Make sure the regional variants for the ``hreflang`` attribute are valid 147 | `#19 `_. 148 | 149 | 2.0.0 150 | ----- 151 | 152 | *Release date: 2020-02-19* 153 | 154 | * |:sparkles:| NEW: Add :confval:`sitemap_url_scheme` that allows the URL scheme to be customized with a default of ``{version}{lang}{link}`` 155 | `#22 `_. 156 | 157 | .. note:: This has the potential to be a breaking change depending on how the ``version`` and ``language`` values are set. **Confirm the accuracy of the sitemap after upgrading**. 158 | 159 | 1.1.0 160 | ----- 161 | 162 | *Release date: 2019-12-12* 163 | 164 | * |:sparkles:| NEW: Add support for ``DirectoryHTMLBuilder``. 165 | * |:wrench:| MAINT: Remove unused ``HTMLTranslator`` import. 166 | * |:sparkles:| NEW: Make ``version`` and ``language`` each optional. 167 | * |:wrench:| MAINT: Add license to **setup.py**. 168 | * |:wrench:| MAINT: Mark unsafe for parallel reading. 169 | 170 | 1.0.2 171 | ----- 172 | 173 | *Release date: 2019-02-09* 174 | 175 | * |:wrench:| MAINT: Add ``html_baseurl`` if it doesn't exist for sphinx versions prior to 1.8.0. 176 | 177 | 1.0.1 178 | ----- 179 | 180 | *Release date: 2019-01-17* 181 | 182 | * |:bug:| FIX: Fix for ``AttributeError: No such config value: html_baseurl`` on versions of sphinx older than 1.8.0. 183 | 184 | 1.0.0 185 | ----- 186 | 187 | *Release date: 2019-01-17* 188 | 189 | * |:wrench:| MAINT: Use native ``html_baseurl``, instead of the custom ``site_url``. It checks for both for backwards compatibility. 190 | * |:sparkles:| NEW: Add support for multiple languages. 191 | 192 | 0.3.1 193 | ----- 194 | 195 | *Release date: 2018-03-04* 196 | 197 | * |:books:| DOCS: Add instructions on maintaining PyPI version to the docs 198 | 199 | 0.3.0 200 | ----- 201 | 202 | *Release date: 2018-03-04* 203 | 204 | * |:wrench:| MAINT: Remove unnecessary ``HTMLTranslator``. 205 | * |:books:| DOCS: Improve documentation 206 | 207 | 0.2 208 | --- 209 | 210 | *Release date: 2017-11-28* 211 | 212 | * |:wrench:| MAINT: Fix PyPI description 213 | 214 | 0.1 215 | --- 216 | 217 | *Release date: 2017-11-28* 218 | 219 | * Initial Release |:tada:| 220 | 221 | 222 | .. _sphinx-last-updated-by-git: https://pypi.org/project/sphinx-last-updated-by-git/ 223 | -------------------------------------------------------------------------------- /docs/source/advanced-configuration.rst: -------------------------------------------------------------------------------- 1 | Advanced Configuration 2 | ====================== 3 | 4 | .. _configuration_customizing_url_scheme: 5 | 6 | Customizing the URL Scheme 7 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 8 | 9 | :confval:`sitemap_url_scheme` defaults to ``{lang}{version}{link}``, where ``{lang}`` and ``{version}`` get set by :confval:`language` and :confval:`version` in **conf.py**. 10 | 11 | .. important:: As of Sphinx version 5, ``language`` defaults to ``"en"``, if that 12 | makes the default scheme produce the incorrect URL, then change the default behavior. 13 | 14 | To change the default behavior, set the value of :confval:`sitemap_url_scheme` in **conf.py** to the 15 | desired format. For example: 16 | 17 | .. code-block:: python 18 | 19 | sitemap_url_scheme = "{link}" 20 | 21 | Or for nested deployments, something like: 22 | 23 | .. code-block:: python 24 | 25 | sitemap_url_scheme = "{version}{lang}subdir/{link}" 26 | 27 | .. note:: The extension automatically appends trailing slashes to both the ``language`` and ``version`` values. 28 | You can also omit values from the scheme for desired behavior. 29 | 30 | 31 | .. _configuration_changing_filename: 32 | 33 | Changing the Filename 34 | ^^^^^^^^^^^^^^^^^^^^^ 35 | 36 | Set :confval:`sitemap_filename` in **conf.py** to the desired filename, for example: 37 | 38 | .. code-block:: python 39 | 40 | sitemap_filename = "sitemap.xml" 41 | 42 | Version Support 43 | ^^^^^^^^^^^^^^^ 44 | 45 | :confval:`version` specifies the version of the sitemap. 46 | For multi-version sitemaps, generate a sitemap per version and then manually add each to a `sitemapindex.xml`_ file. 47 | 48 | Tagged Releases 49 | ~~~~~~~~~~~~~~~ 50 | 51 | For a tagged release deploy strategy where the ``latest`` gets created from head of the branch and versions get created from tagged commits, check to see if the current commit matches the release tag regex and set :confval:`version` accordingly. 52 | 53 | .. code-block:: python 54 | 55 | # check if the current commit is tagged as a release (vX.Y.Z) and set the version 56 | GIT_TAG_OUTPUT = subprocess.check_output(["git", "tag", "--points-at", "HEAD"]) 57 | current_tag = GIT_TAG_OUTPUT.decode().strip() 58 | if re.match(r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$", current_tag): 59 | version = current_tag 60 | else: 61 | version = "latest" 62 | 63 | .. tip:: Set the canonical URL in the theme layout of all versions to the latest version of that page, for example: 64 | 65 | .. code-block:: html 66 | 67 | 68 | 69 | .. _configuration_supporting_multiple_languages: 70 | 71 | Language Support 72 | ^^^^^^^^^^^^^^^^ 73 | 74 | :confval:`language` specifies the primary language. Any alternative languages get detected using the contents of :confval:`locale_dirs`. 75 | 76 | For example, with a primary language of **en**, and **es** and **fr** as detected translations, the sitemap look like this: 77 | 78 | .. code-block:: xml 79 | 80 | 81 | 82 | 83 | https://my-site.com/docs/en/index.html 84 | 85 | 86 | 87 | 88 | 89 | https://my-site.com/docs/en/about.html 90 | 91 | 92 | 93 | 94 | 95 | 96 | Use :confval:`sitemap_locales` to manually specify a list of locales to include in the sitemap: 97 | 98 | .. code-block:: python 99 | 100 | sitemap_locales = ['en', 'es'] 101 | 102 | The end result looks something like the following for each language/version build: 103 | 104 | .. code-block:: xml 105 | 106 | 107 | 108 | 109 | https://my-site.com/docs/en/index.html 110 | 111 | 112 | 113 | 114 | https://my-site.com/docs/en/about.html 115 | 116 | 117 | 118 | 119 | 120 | To generate the primary language with no alternatives, set :confval:`sitemap_locales` to ``[None]``: 121 | 122 | .. code-block:: python 123 | 124 | sitemap_locales = [None] 125 | 126 | For multilingual sitemaps, generate a sitemap per language and then manually add each to a `sitemapindex.xml`_ file. 127 | 128 | .. _configuration_excluding_pages: 129 | 130 | Excluding Pages 131 | ^^^^^^^^^^^^^^^ 132 | 133 | To exclude a set of pages, add each page's path to ``sitemap_excludes``. 134 | You can use exact paths or wildcard patterns: 135 | 136 | .. code-block:: python 137 | 138 | sitemap_excludes = [ 139 | "search.html", # Exact match 140 | "genindex.html", # Exact match 141 | "modules/*", # Wildcard pattern - matches files starting with "_modules/" 142 | ] 143 | 144 | Unix-style wildcards are supported: 145 | 146 | - ``*`` matches any number of characters 147 | - ``?`` matches any single character 148 | - ``[seq]`` matches any character in seq 149 | - ``[!seq]`` matches any character not in seq 150 | 151 | .. _configuration_lastmod: 152 | 153 | Adding Last Modified Timestamps 154 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 155 | 156 | To enable last modified timestamps in your sitemap, set :confval:`sitemap_show_lastmod` to ``True`` in **conf.py**: 157 | 158 | .. code-block:: python 159 | 160 | sitemap_show_lastmod = True 161 | 162 | When enabled, the extension uses Git to determine the last modified date for each page based on the most recent commit that modified the source file. 163 | This produces sitemap entries like: 164 | 165 | .. code-block:: xml 166 | 167 | 168 | https://my-site.com/docs/en/index.html 169 | 2024-01-15T10:30:00+00:00 170 | 171 | 172 | .. important:: 173 | 174 | This feature requires Git to be available and your documentation to be in a Git repository. 175 | If Git is not available or the file is not tracked, no ```` element will be added for that page. 176 | Shallow clones, which is the default for GitHub Actions, are not supported at this time. 177 | 178 | .. tip:: The ```` timestamps are particularly useful for :ref:`RAG (Retrieval-Augmented Generation) systems ` that need to identify recently updated content for incremental updates. 179 | 180 | 181 | .. _sitemapindex.xml: https://support.google.com/webmasters/answer/75712?hl=en 182 | .. _sitemaps.org: https://www.sitemaps.org/protocol.html 183 | 184 | .. _configuration_indent: 185 | 186 | Formatting XML Output 187 | ^^^^^^^^^^^^^^^^^^^^^ 188 | 189 | To add indention to the XML output, set :confval:`sitemap_indent` to the number of spaces for indentation in **conf.py**: 190 | 191 | .. code-block:: python 192 | 193 | sitemap_indent = 2 194 | 195 | Set to ``0`` (the default) to disable indentation: 196 | 197 | .. code-block:: python 198 | 199 | sitemap_indent = 0 200 | -------------------------------------------------------------------------------- /sphinx_sitemap/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Michael Dowling 2 | # Copyright (c) 2017 Jared Dillard 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | 14 | import fnmatch 15 | import os 16 | import queue 17 | from datetime import datetime, timezone 18 | from multiprocessing import Manager 19 | from pathlib import Path 20 | from typing import Any, Dict, List, Optional 21 | from xml.etree import ElementTree 22 | 23 | from sphinx.application import Sphinx 24 | from sphinx.errors import ExtensionError 25 | from sphinx.util.logging import getLogger 26 | 27 | __version__ = "2.9.0" 28 | 29 | logger = getLogger(__name__) 30 | 31 | 32 | def setup(app: Sphinx) -> Dict[str, Any]: 33 | """ 34 | Sphinx extension setup function. 35 | It adds config values and connects Sphinx events to the sitemap builder. 36 | 37 | :param app: The Sphinx Application instance 38 | :return: A dict of Sphinx extension options 39 | """ 40 | app.add_config_value("site_url", default=None, rebuild="") 41 | app.add_config_value( 42 | "sitemap_url_scheme", default="{lang}{version}{link}", rebuild="" 43 | ) 44 | app.add_config_value("sitemap_locales", default=[], rebuild="") 45 | 46 | app.add_config_value("sitemap_filename", default="sitemap.xml", rebuild="") 47 | 48 | app.add_config_value("sitemap_excludes", default=[], rebuild="") 49 | 50 | app.add_config_value("sitemap_show_lastmod", default=False, rebuild="") 51 | 52 | app.add_config_value("sitemap_indent", default=0, rebuild="") 53 | 54 | try: 55 | app.add_config_value("html_baseurl", default=None, rebuild="") 56 | except BaseException: 57 | pass 58 | 59 | # install sphinx_last_updated_by_git extension if it exists 60 | if app.config.sitemap_show_lastmod: 61 | try: 62 | app.setup_extension("sphinx_last_updated_by_git") 63 | except ExtensionError as e: 64 | logger.warning( 65 | f"{e}", 66 | type="sitemap", 67 | subtype="configuration", 68 | ) 69 | app.config.sitemap_show_lastmod = False 70 | 71 | app.connect("builder-inited", record_builder_type) 72 | app.connect("html-page-context", add_html_link) 73 | app.connect("build-finished", create_sitemap) 74 | 75 | return { 76 | "parallel_read_safe": True, 77 | "parallel_write_safe": True, 78 | "version": __version__, 79 | } 80 | 81 | 82 | def get_locales(app: Sphinx) -> List[str]: 83 | """ 84 | Get a list of locales from the extension config or automatically detect based 85 | on Sphinx Application config. 86 | 87 | :param app: The Sphinx Application instance 88 | :return: A list of locales 89 | """ 90 | # Manually configured list of locales 91 | sitemap_locales: Optional[List[str]] = app.builder.config.sitemap_locales 92 | if sitemap_locales: 93 | # special value to add nothing -> use primary language only 94 | if sitemap_locales == [None]: 95 | return [] 96 | 97 | # otherwise, add each locale 98 | return [locale for locale in sitemap_locales] 99 | 100 | # Or autodetect locales 101 | locales = [] 102 | for locale_dir in app.builder.config.locale_dirs: 103 | locale_dir = os.path.join(app.confdir, locale_dir) 104 | if os.path.isdir(locale_dir): 105 | for locale in os.listdir(locale_dir): 106 | if os.path.isdir(os.path.join(locale_dir, locale)): 107 | locales.append(locale) 108 | return locales 109 | 110 | 111 | def record_builder_type(app: Sphinx): 112 | """ 113 | Determine if the Sphinx Builder is an instance of DirectoryHTMLBuilder and store that in the 114 | application environment. 115 | 116 | :param app: The Sphinx Application instance 117 | """ 118 | # builder isn't initialized in the setup so we do it here 119 | builder = getattr(app, "builder", None) 120 | if builder is None: 121 | return 122 | builder.env.is_directory_builder = type(builder).__name__ == "DirectoryHTMLBuilder" 123 | builder.env.app.sitemap_links = Manager().Queue() 124 | 125 | 126 | def is_excluded(sitemap_link: str, exclude_patterns: List[str]) -> bool: 127 | """ 128 | Check if a sitemap link should be excluded based on wildcard patterns. 129 | 130 | :param sitemap_link: The sitemap link to check 131 | :param exclude_patterns: List of wildcard patterns to match against 132 | :return: True if the link matches any exclude pattern, False otherwise 133 | """ 134 | return any(fnmatch.fnmatch(sitemap_link, pattern) for pattern in exclude_patterns) 135 | 136 | 137 | def hreflang_formatter(lang: str) -> str: 138 | """ 139 | Format the supplied locale code into a string that is compatible with `hreflang`. 140 | See also: 141 | 142 | - https://en.wikipedia.org/wiki/Hreflang#Common_Mistakes 143 | - https://github.com/readthedocs/readthedocs.org/pull/5638 144 | 145 | :param lang: The locale string to format 146 | :return: The formatted locale string 147 | """ 148 | if "_" in lang: 149 | return lang.replace("_", "-") 150 | return lang 151 | 152 | 153 | def add_html_link(app: Sphinx, pagename: str, templatename, context, doctree): 154 | """ 155 | As each page is built, collect page names for the sitemap 156 | 157 | :param app: The Sphinx Application instance 158 | :param pagename: The current page being built 159 | """ 160 | env = app.builder.env 161 | if app.builder.config.html_file_suffix is None: 162 | file_suffix = ".html" 163 | else: 164 | file_suffix = app.builder.config.html_file_suffix 165 | 166 | last_updated = None 167 | if app.builder.config.sitemap_show_lastmod and pagename in env.git_last_updated: 168 | timestamp, show_sourcelink = env.git_last_updated[pagename] 169 | # TODO verify dates 170 | # TODO handle untracked pages (add option to use current timestamp?) 171 | if timestamp: 172 | utc_date = datetime.fromtimestamp(int(timestamp), timezone.utc) 173 | last_updated = utc_date.strftime("%Y-%m-%dT%H:%M:%SZ") 174 | 175 | # Support DirectoryHTMLBuilder path structure 176 | # where generated links between pages omit the index.html 177 | if env.is_directory_builder: # type: ignore 178 | if pagename == "index": 179 | sitemap_link = "" 180 | elif pagename.endswith("/index"): 181 | sitemap_link = pagename[:-6] + "/" 182 | else: 183 | sitemap_link = pagename + "/" 184 | else: 185 | sitemap_link = pagename + file_suffix 186 | 187 | if not is_excluded(sitemap_link, app.builder.config.sitemap_excludes): 188 | env.app.sitemap_links.put((sitemap_link, last_updated)) # type: ignore 189 | 190 | 191 | def create_sitemap(app: Sphinx, exception): 192 | """ 193 | Generates the sitemap.xml from the collected HTML page links. 194 | 195 | :param app: The Sphinx Application instance 196 | """ 197 | site_url = app.builder.config.site_url or app.builder.config.html_baseurl 198 | if site_url: 199 | site_url.rstrip("/") + "/" 200 | else: 201 | logger.warning( 202 | "sphinx-sitemap: html_baseurl is required in conf.py." "Sitemap not built.", 203 | type="sitemap", 204 | subtype="configuration", 205 | ) 206 | return 207 | 208 | if app.env.app.sitemap_links.empty(): # type: ignore 209 | logger.info( 210 | "sphinx-sitemap: No pages generated for %s" % app.config.sitemap_filename, 211 | type="sitemap", 212 | subtype="information", 213 | ) 214 | return 215 | 216 | ElementTree.register_namespace("xhtml", "http://www.w3.org/1999/xhtml") 217 | 218 | root = ElementTree.Element( 219 | "urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" 220 | ) 221 | 222 | locales = get_locales(app) 223 | 224 | if app.builder.config.version: 225 | version = app.builder.config.version + "/" 226 | else: 227 | version = "" 228 | 229 | while True: 230 | try: 231 | link, last_updated = app.env.app.sitemap_links.get_nowait() # type: ignore 232 | except queue.Empty: 233 | break 234 | 235 | url = ElementTree.SubElement(root, "url") 236 | scheme = app.config.sitemap_url_scheme 237 | if app.builder.config.language: 238 | lang = app.builder.config.language + "/" 239 | else: 240 | lang = "" 241 | 242 | # add page url 243 | ElementTree.SubElement(url, "loc").text = site_url + scheme.format( 244 | lang=lang, version=version, link=link 245 | ) 246 | 247 | # add page lastmode date if it exists 248 | if last_updated: 249 | ElementTree.SubElement(url, "lastmod").text = last_updated 250 | 251 | # add alternate language page urls 252 | for lang in locales: 253 | lang = lang + "/" 254 | ElementTree.SubElement( 255 | url, 256 | "{http://www.w3.org/1999/xhtml}link", 257 | rel="alternate", 258 | hreflang=hreflang_formatter(lang.rstrip("/")), 259 | href=site_url + scheme.format(lang=lang, version=version, link=link), 260 | ) 261 | 262 | filename = Path(app.outdir) / app.config.sitemap_filename 263 | if isinstance(app.config.sitemap_indent, int) and app.config.sitemap_indent > 0: 264 | ElementTree.indent(root, space=app.config.sitemap_indent * " ") 265 | 266 | ElementTree.ElementTree(root).write( 267 | filename, xml_declaration=True, encoding="utf-8", method="xml" 268 | ) 269 | 270 | logger.info( 271 | "sphinx-sitemap: %s was generated for URL %s in %s" 272 | % (app.config.sitemap_filename, site_url, filename), 273 | type="sitemap", 274 | subtype="information", 275 | ) 276 | -------------------------------------------------------------------------------- /docs/_vale/write-good/Cliches.yml: -------------------------------------------------------------------------------- 1 | extends: existence 2 | message: "Try to avoid using clichés like '%s'." 3 | ignorecase: true 4 | level: warning 5 | tokens: 6 | - a chip off the old block 7 | - a clean slate 8 | - a dark and stormy night 9 | - a far cry 10 | - a fine kettle of fish 11 | - a loose cannon 12 | - a penny saved is a penny earned 13 | - a tough row to hoe 14 | - a word to the wise 15 | - ace in the hole 16 | - acid test 17 | - add insult to injury 18 | - against all odds 19 | - air your dirty laundry 20 | - all fun and games 21 | - all in a day's work 22 | - all talk, no action 23 | - all thumbs 24 | - all your eggs in one basket 25 | - all's fair in love and war 26 | - all's well that ends well 27 | - almighty dollar 28 | - American as apple pie 29 | - an axe to grind 30 | - another day, another dollar 31 | - armed to the teeth 32 | - as luck would have it 33 | - as old as time 34 | - as the crow flies 35 | - at loose ends 36 | - at my wits end 37 | - avoid like the plague 38 | - babe in the woods 39 | - back against the wall 40 | - back in the saddle 41 | - back to square one 42 | - back to the drawing board 43 | - bad to the bone 44 | - badge of honor 45 | - bald faced liar 46 | - ballpark figure 47 | - banging your head against a brick wall 48 | - baptism by fire 49 | - barking up the wrong tree 50 | - bat out of hell 51 | - be all and end all 52 | - beat a dead horse 53 | - beat around the bush 54 | - been there, done that 55 | - beggars can't be choosers 56 | - behind the eight ball 57 | - bend over backwards 58 | - benefit of the doubt 59 | - bent out of shape 60 | - best thing since sliced bread 61 | - bet your bottom dollar 62 | - better half 63 | - better late than never 64 | - better mousetrap 65 | - better safe than sorry 66 | - between a rock and a hard place 67 | - beyond the pale 68 | - bide your time 69 | - big as life 70 | - big cheese 71 | - big fish in a small pond 72 | - big man on campus 73 | - bigger they are the harder they fall 74 | - bird in the hand 75 | - bird's eye view 76 | - birds and the bees 77 | - birds of a feather flock together 78 | - bit the hand that feeds you 79 | - bite the bullet 80 | - bite the dust 81 | - bitten off more than he can chew 82 | - black as coal 83 | - black as pitch 84 | - black as the ace of spades 85 | - blast from the past 86 | - bleeding heart 87 | - blessing in disguise 88 | - blind ambition 89 | - blind as a bat 90 | - blind leading the blind 91 | - blood is thicker than water 92 | - blood sweat and tears 93 | - blow off steam 94 | - blow your own horn 95 | - blushing bride 96 | - boils down to 97 | - bolt from the blue 98 | - bone to pick 99 | - bored stiff 100 | - bored to tears 101 | - bottomless pit 102 | - boys will be boys 103 | - bright and early 104 | - brings home the bacon 105 | - broad across the beam 106 | - broken record 107 | - brought back to reality 108 | - bull by the horns 109 | - bull in a china shop 110 | - burn the midnight oil 111 | - burning question 112 | - burning the candle at both ends 113 | - burst your bubble 114 | - bury the hatchet 115 | - busy as a bee 116 | - by hook or by crook 117 | - call a spade a spade 118 | - called onto the carpet 119 | - calm before the storm 120 | - can of worms 121 | - can't cut the mustard 122 | - can't hold a candle to 123 | - case of mistaken identity 124 | - cat got your tongue 125 | - cat's meow 126 | - caught in the crossfire 127 | - caught red-handed 128 | - checkered past 129 | - chomping at the bit 130 | - cleanliness is next to godliness 131 | - clear as a bell 132 | - clear as mud 133 | - close to the vest 134 | - cock and bull story 135 | - cold shoulder 136 | - come hell or high water 137 | - cool as a cucumber 138 | - cool, calm, and collected 139 | - cost a king's ransom 140 | - count your blessings 141 | - crack of dawn 142 | - crash course 143 | - creature comforts 144 | - cross that bridge when you come to it 145 | - crushing blow 146 | - cry like a baby 147 | - cry me a river 148 | - cry over spilt milk 149 | - crystal clear 150 | - curiosity killed the cat 151 | - cut and dried 152 | - cut through the red tape 153 | - cut to the chase 154 | - cute as a bugs ear 155 | - cute as a button 156 | - cute as a puppy 157 | - cuts to the quick 158 | - dark before the dawn 159 | - day in, day out 160 | - dead as a doornail 161 | - devil is in the details 162 | - dime a dozen 163 | - divide and conquer 164 | - dog and pony show 165 | - dog days 166 | - dog eat dog 167 | - dog tired 168 | - don't burn your bridges 169 | - don't count your chickens 170 | - don't look a gift horse in the mouth 171 | - don't rock the boat 172 | - don't step on anyone's toes 173 | - don't take any wooden nickels 174 | - down and out 175 | - down at the heels 176 | - down in the dumps 177 | - down the hatch 178 | - down to earth 179 | - draw the line 180 | - dressed to kill 181 | - dressed to the nines 182 | - drives me up the wall 183 | - dull as dishwater 184 | - dyed in the wool 185 | - eagle eye 186 | - ear to the ground 187 | - early bird catches the worm 188 | - easier said than done 189 | - easy as pie 190 | - eat your heart out 191 | - eat your words 192 | - eleventh hour 193 | - even the playing field 194 | - every dog has its day 195 | - every fiber of my being 196 | - everything but the kitchen sink 197 | - eye for an eye 198 | - face the music 199 | - facts of life 200 | - fair weather friend 201 | - fall by the wayside 202 | - fan the flames 203 | - feast or famine 204 | - feather your nest 205 | - feathered friends 206 | - few and far between 207 | - fifteen minutes of fame 208 | - filthy vermin 209 | - fine kettle of fish 210 | - fish out of water 211 | - fishing for a compliment 212 | - fit as a fiddle 213 | - fit the bill 214 | - fit to be tied 215 | - flash in the pan 216 | - flat as a pancake 217 | - flip your lid 218 | - flog a dead horse 219 | - fly by night 220 | - fly the coop 221 | - follow your heart 222 | - for all intents and purposes 223 | - for the birds 224 | - for what it's worth 225 | - force of nature 226 | - force to be reckoned with 227 | - forgive and forget 228 | - fox in the henhouse 229 | - free and easy 230 | - free as a bird 231 | - fresh as a daisy 232 | - full steam ahead 233 | - fun in the sun 234 | - garbage in, garbage out 235 | - gentle as a lamb 236 | - get a kick out of 237 | - get a leg up 238 | - get down and dirty 239 | - get the lead out 240 | - get to the bottom of 241 | - get your feet wet 242 | - gets my goat 243 | - gilding the lily 244 | - give and take 245 | - go against the grain 246 | - go at it tooth and nail 247 | - go for broke 248 | - go him one better 249 | - go the extra mile 250 | - go with the flow 251 | - goes without saying 252 | - good as gold 253 | - good deed for the day 254 | - good things come to those who wait 255 | - good time was had by all 256 | - good times were had by all 257 | - greased lightning 258 | - greek to me 259 | - green thumb 260 | - green-eyed monster 261 | - grist for the mill 262 | - growing like a weed 263 | - hair of the dog 264 | - hand to mouth 265 | - happy as a clam 266 | - happy as a lark 267 | - hasn't a clue 268 | - have a nice day 269 | - have high hopes 270 | - have the last laugh 271 | - haven't got a row to hoe 272 | - head honcho 273 | - head over heels 274 | - hear a pin drop 275 | - heard it through the grapevine 276 | - heart's content 277 | - heavy as lead 278 | - hem and haw 279 | - high and dry 280 | - high and mighty 281 | - high as a kite 282 | - hit paydirt 283 | - hold your head up high 284 | - hold your horses 285 | - hold your own 286 | - hold your tongue 287 | - honest as the day is long 288 | - horns of a dilemma 289 | - horse of a different color 290 | - hot under the collar 291 | - hour of need 292 | - I beg to differ 293 | - icing on the cake 294 | - if the shoe fits 295 | - if the shoe were on the other foot 296 | - in a jam 297 | - in a jiffy 298 | - in a nutshell 299 | - in a pig's eye 300 | - in a pinch 301 | - in a word 302 | - in hot water 303 | - in the gutter 304 | - in the nick of time 305 | - in the thick of it 306 | - in your dreams 307 | - it ain't over till the fat lady sings 308 | - it goes without saying 309 | - it takes all kinds 310 | - it takes one to know one 311 | - it's a small world 312 | - it's only a matter of time 313 | - ivory tower 314 | - Jack of all trades 315 | - jockey for position 316 | - jog your memory 317 | - joined at the hip 318 | - judge a book by its cover 319 | - jump down your throat 320 | - jump in with both feet 321 | - jump on the bandwagon 322 | - jump the gun 323 | - jump to conclusions 324 | - just a hop, skip, and a jump 325 | - just the ticket 326 | - justice is blind 327 | - keep a stiff upper lip 328 | - keep an eye on 329 | - keep it simple, stupid 330 | - keep the home fires burning 331 | - keep up with the Joneses 332 | - keep your chin up 333 | - keep your fingers crossed 334 | - kick the bucket 335 | - kick up your heels 336 | - kick your feet up 337 | - kid in a candy store 338 | - kill two birds with one stone 339 | - kiss of death 340 | - knock it out of the park 341 | - knock on wood 342 | - knock your socks off 343 | - know him from Adam 344 | - know the ropes 345 | - know the score 346 | - knuckle down 347 | - knuckle sandwich 348 | - knuckle under 349 | - labor of love 350 | - ladder of success 351 | - land on your feet 352 | - lap of luxury 353 | - last but not least 354 | - last hurrah 355 | - last-ditch effort 356 | - law of the jungle 357 | - law of the land 358 | - lay down the law 359 | - leaps and bounds 360 | - let sleeping dogs lie 361 | - let the cat out of the bag 362 | - let the good times roll 363 | - let your hair down 364 | - let's talk turkey 365 | - letter perfect 366 | - lick your wounds 367 | - lies like a rug 368 | - life's a bitch 369 | - life's a grind 370 | - light at the end of the tunnel 371 | - lighter than a feather 372 | - lighter than air 373 | - like clockwork 374 | - like father like son 375 | - like taking candy from a baby 376 | - like there's no tomorrow 377 | - lion's share 378 | - live and learn 379 | - live and let live 380 | - long and short of it 381 | - long lost love 382 | - look before you leap 383 | - look down your nose 384 | - look what the cat dragged in 385 | - looking a gift horse in the mouth 386 | - looks like death warmed over 387 | - loose cannon 388 | - lose your head 389 | - lose your temper 390 | - loud as a horn 391 | - lounge lizard 392 | - loved and lost 393 | - low man on the totem pole 394 | - luck of the draw 395 | - luck of the Irish 396 | - make hay while the sun shines 397 | - make money hand over fist 398 | - make my day 399 | - make the best of a bad situation 400 | - make the best of it 401 | - make your blood boil 402 | - man of few words 403 | - man's best friend 404 | - mark my words 405 | - meaningful dialogue 406 | - missed the boat on that one 407 | - moment in the sun 408 | - moment of glory 409 | - moment of truth 410 | - money to burn 411 | - more power to you 412 | - more than one way to skin a cat 413 | - movers and shakers 414 | - moving experience 415 | - naked as a jaybird 416 | - naked truth 417 | - neat as a pin 418 | - needle in a haystack 419 | - needless to say 420 | - neither here nor there 421 | - never look back 422 | - never say never 423 | - nip and tuck 424 | - nip it in the bud 425 | - no guts, no glory 426 | - no love lost 427 | - no pain, no gain 428 | - no skin off my back 429 | - no stone unturned 430 | - no time like the present 431 | - no use crying over spilled milk 432 | - nose to the grindstone 433 | - not a hope in hell 434 | - not a minute's peace 435 | - not in my backyard 436 | - not playing with a full deck 437 | - not the end of the world 438 | - not written in stone 439 | - nothing to sneeze at 440 | - nothing ventured nothing gained 441 | - now we're cooking 442 | - off the top of my head 443 | - off the wagon 444 | - off the wall 445 | - old hat 446 | - older and wiser 447 | - older than dirt 448 | - older than Methuselah 449 | - on a roll 450 | - on cloud nine 451 | - on pins and needles 452 | - on the bandwagon 453 | - on the money 454 | - on the nose 455 | - on the rocks 456 | - on the spot 457 | - on the tip of my tongue 458 | - on the wagon 459 | - on thin ice 460 | - once bitten, twice shy 461 | - one bad apple doesn't spoil the bushel 462 | - one born every minute 463 | - one brick short 464 | - one foot in the grave 465 | - one in a million 466 | - one red cent 467 | - only game in town 468 | - open a can of worms 469 | - open and shut case 470 | - open the flood gates 471 | - opportunity doesn't knock twice 472 | - out of pocket 473 | - out of sight, out of mind 474 | - out of the frying pan into the fire 475 | - out of the woods 476 | - out on a limb 477 | - over a barrel 478 | - over the hump 479 | - pain and suffering 480 | - pain in the 481 | - panic button 482 | - par for the course 483 | - part and parcel 484 | - party pooper 485 | - pass the buck 486 | - patience is a virtue 487 | - pay through the nose 488 | - penny pincher 489 | - perfect storm 490 | - pig in a poke 491 | - pile it on 492 | - pillar of the community 493 | - pin your hopes on 494 | - pitter patter of little feet 495 | - plain as day 496 | - plain as the nose on your face 497 | - play by the rules 498 | - play your cards right 499 | - playing the field 500 | - playing with fire 501 | - pleased as punch 502 | - plenty of fish in the sea 503 | - point with pride 504 | - poor as a church mouse 505 | - pot calling the kettle black 506 | - pretty as a picture 507 | - pull a fast one 508 | - pull your punches 509 | - pulling your leg 510 | - pure as the driven snow 511 | - put it in a nutshell 512 | - put one over on you 513 | - put the cart before the horse 514 | - put the pedal to the metal 515 | - put your best foot forward 516 | - put your foot down 517 | - quick as a bunny 518 | - quick as a lick 519 | - quick as a wink 520 | - quick as lightning 521 | - quiet as a dormouse 522 | - rags to riches 523 | - raining buckets 524 | - raining cats and dogs 525 | - rank and file 526 | - rat race 527 | - reap what you sow 528 | - red as a beet 529 | - red herring 530 | - reinvent the wheel 531 | - rich and famous 532 | - rings a bell 533 | - ripe old age 534 | - ripped me off 535 | - rise and shine 536 | - road to hell is paved with good intentions 537 | - rob Peter to pay Paul 538 | - roll over in the grave 539 | - rub the wrong way 540 | - ruled the roost 541 | - running in circles 542 | - sad but true 543 | - sadder but wiser 544 | - salt of the earth 545 | - scared stiff 546 | - scared to death 547 | - sealed with a kiss 548 | - second to none 549 | - see eye to eye 550 | - seen the light 551 | - seize the day 552 | - set the record straight 553 | - set the world on fire 554 | - set your teeth on edge 555 | - sharp as a tack 556 | - shoot for the moon 557 | - shoot the breeze 558 | - shot in the dark 559 | - shoulder to the wheel 560 | - sick as a dog 561 | - sigh of relief 562 | - signed, sealed, and delivered 563 | - sink or swim 564 | - six of one, half a dozen of another 565 | - skating on thin ice 566 | - slept like a log 567 | - slinging mud 568 | - slippery as an eel 569 | - slow as molasses 570 | - smart as a whip 571 | - smooth as a baby's bottom 572 | - sneaking suspicion 573 | - snug as a bug in a rug 574 | - sow wild oats 575 | - spare the rod, spoil the child 576 | - speak of the devil 577 | - spilled the beans 578 | - spinning your wheels 579 | - spitting image of 580 | - spoke with relish 581 | - spread like wildfire 582 | - spring to life 583 | - squeaky wheel gets the grease 584 | - stands out like a sore thumb 585 | - start from scratch 586 | - stick in the mud 587 | - still waters run deep 588 | - stitch in time 589 | - stop and smell the roses 590 | - straight as an arrow 591 | - straw that broke the camel's back 592 | - strong as an ox 593 | - stubborn as a mule 594 | - stuff that dreams are made of 595 | - stuffed shirt 596 | - sweating blood 597 | - sweating bullets 598 | - take a load off 599 | - take one for the team 600 | - take the bait 601 | - take the bull by the horns 602 | - take the plunge 603 | - takes one to know one 604 | - takes two to tango 605 | - the more the merrier 606 | - the real deal 607 | - the real McCoy 608 | - the red carpet treatment 609 | - the same old story 610 | - there is no accounting for taste 611 | - thick as a brick 612 | - thick as thieves 613 | - thin as a rail 614 | - think outside of the box 615 | - third time's the charm 616 | - this day and age 617 | - this hurts me worse than it hurts you 618 | - this point in time 619 | - three sheets to the wind 620 | - through thick and thin 621 | - throw in the towel 622 | - tie one on 623 | - tighter than a drum 624 | - time and time again 625 | - time is of the essence 626 | - tip of the iceberg 627 | - tired but happy 628 | - to coin a phrase 629 | - to each his own 630 | - to make a long story short 631 | - to the best of my knowledge 632 | - toe the line 633 | - tongue in cheek 634 | - too good to be true 635 | - too hot to handle 636 | - too numerous to mention 637 | - touch with a ten foot pole 638 | - tough as nails 639 | - trial and error 640 | - trials and tribulations 641 | - tried and true 642 | - trip down memory lane 643 | - twist of fate 644 | - two cents worth 645 | - two peas in a pod 646 | - ugly as sin 647 | - under the counter 648 | - under the gun 649 | - under the same roof 650 | - under the weather 651 | - until the cows come home 652 | - unvarnished truth 653 | - up the creek 654 | - uphill battle 655 | - upper crust 656 | - upset the applecart 657 | - vain attempt 658 | - vain effort 659 | - vanquish the enemy 660 | - vested interest 661 | - waiting for the other shoe to drop 662 | - wakeup call 663 | - warm welcome 664 | - watch your p's and q's 665 | - watch your tongue 666 | - watching the clock 667 | - water under the bridge 668 | - weather the storm 669 | - weed them out 670 | - week of Sundays 671 | - went belly up 672 | - wet behind the ears 673 | - what goes around comes around 674 | - what you see is what you get 675 | - when it rains, it pours 676 | - when push comes to shove 677 | - when the cat's away 678 | - when the going gets tough, the tough get going 679 | - white as a sheet 680 | - whole ball of wax 681 | - whole hog 682 | - whole nine yards 683 | - wild goose chase 684 | - will wonders never cease? 685 | - wisdom of the ages 686 | - wise as an owl 687 | - wolf at the door 688 | - words fail me 689 | - work like a dog 690 | - world weary 691 | - worst nightmare 692 | - worth its weight in gold 693 | - wrong side of the bed 694 | - yanking your chain 695 | - yappy as a dog 696 | - years young 697 | - you are what you eat 698 | - you can run but you can't hide 699 | - you only live once 700 | - you're the boss 701 | - young and foolish 702 | - young and vibrant 703 | --------------------------------------------------------------------------------