├── tests ├── examples │ ├── output │ │ ├── conf.py │ │ ├── padded-comment.txt │ │ ├── comment-with-complex-key.txt │ │ ├── commented-scalar-in-sequence.txt │ │ ├── simple-key.yml │ │ ├── capped1.txt │ │ ├── flow-style.txt │ │ ├── simple-key.txt │ │ ├── multiline-text.txt │ │ ├── flow-style.yml │ │ ├── padded-comment.yml │ │ ├── capped2.txt │ │ ├── comment-with-complex-key.yml │ │ ├── comment-without-trailing.yml │ │ ├── comment-without-trailing.txt │ │ ├── multiline-comment.yml │ │ ├── multiline-comment.txt │ │ ├── multiline-text.yml │ │ ├── commented-scalar-in-sequence.yml │ │ ├── multiple-documents.txt │ │ ├── embedded-rst-in-comment.txt │ │ ├── embedded-rst-in-comment.yml │ │ ├── comment-in-sequence.txt │ │ ├── multiple-documents.yml │ │ ├── comment-in-uncommented-mapping.txt │ │ ├── comment-in-uncommented-mapping.yml │ │ ├── capped1.yml │ │ ├── multilevel-commented-mapping.txt │ │ ├── comment-in-sequence.yml │ │ ├── capped2.yml │ │ ├── multilevel-commented-mapping.yml │ │ ├── comment-in-nested-sequence.txt │ │ └── comment-in-nested-sequence.yml │ ├── wrong_location1 │ │ └── conf.py │ └── wrong_location2 │ │ ├── conf.py │ │ └── index.yml │ │ └── .keepme ├── requirements-1.txt ├── requirements-2.txt ├── requirements-3.txt ├── requirements-4.txt ├── requirements-5.txt └── __main__.py ├── .editorconfig ├── MANIFEST.in ├── pyproject.toml ├── LICENSE ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── ci.sh ├── CHANGELOG ├── README.md └── sphinxcontrib └── autoyaml └── __init__.py /tests/examples/output/conf.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/examples/wrong_location1/conf.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/examples/wrong_location2/conf.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/examples/wrong_location2/index.yml/.keepme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/examples/output/padded-comment.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | -------------------------------------------------------------------------------- /tests/examples/output/comment-with-complex-key.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | -------------------------------------------------------------------------------- /tests/examples/output/commented-scalar-in-sequence.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | -------------------------------------------------------------------------------- /tests/examples/output/simple-key.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key: value 4 | -------------------------------------------------------------------------------- /tests/examples/output/capped1.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | -------------------------------------------------------------------------------- /tests/examples/output/flow-style.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | -------------------------------------------------------------------------------- /tests/examples/output/simple-key.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.sh] 2 | indent_style = tab 3 | indent_size = 4 4 | max_line_length = 120 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG 2 | include README.rst 3 | include LICENSE 4 | prune test 5 | -------------------------------------------------------------------------------- /tests/examples/output/multiline-text.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key1 5 | Comment for key1 6 | -------------------------------------------------------------------------------- /tests/examples/output/flow-style.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key: {key1: value, key2: value, key3: value} 4 | -------------------------------------------------------------------------------- /tests/examples/output/padded-comment.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # This comment shouldn't be compiled. 3 | 4 | key: null 5 | -------------------------------------------------------------------------------- /tests/examples/output/capped2.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key1 5 | comment 6 | 7 | key1 8 | comment 9 | -------------------------------------------------------------------------------- /tests/examples/output/comment-with-complex-key.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # This comment shouldn't be compiled. 3 | [1,2]: null 4 | -------------------------------------------------------------------------------- /tests/examples/output/comment-without-trailing.yml: -------------------------------------------------------------------------------- 1 | ### 2 | #No trailing space 3 | # 4 | # in this comment. 5 | key: null 6 | -------------------------------------------------------------------------------- /tests/examples/output/comment-without-trailing.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | No trailing space 6 | 7 | in this comment. 8 | -------------------------------------------------------------------------------- /tests/examples/output/multiline-comment.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | # continues to next line 4 | # 5 | # Forced newline. 6 | key: value 7 | -------------------------------------------------------------------------------- /tests/examples/output/multiline-comment.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment continues to next line 6 | 7 | Forced newline. 8 | -------------------------------------------------------------------------------- /tests/examples/output/multiline-text.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # Comment for key1 3 | key1: | 4 | ### 5 | # Invalid comment for key2 6 | key2: test 7 | -------------------------------------------------------------------------------- /tests/examples/output/commented-scalar-in-sequence.yml: -------------------------------------------------------------------------------- 1 | key: 2 | - value 3 | ### 4 | # This comment shouldn't be compiled. 5 | - value 6 | - value 7 | -------------------------------------------------------------------------------- /tests/examples/output/multiple-documents.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | 7 | key1 8 | comment 9 | 10 | key 11 | comment 12 | -------------------------------------------------------------------------------- /tests/examples/output/embedded-rst-in-comment.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | 7 | - property1: "value1" 8 | property2: "value2" 9 | -------------------------------------------------------------------------------- /tests/examples/output/embedded-rst-in-comment.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | # 4 | # .. code-block:: yaml 5 | # 6 | # - property1: "value1" 7 | # property2: "value2" 8 | key: value 9 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-sequence.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key1 5 | key1 6 | comment 7 | 8 | key3 9 | comment 10 | 11 | key3 12 | comment 13 | 14 | key2 15 | key1 16 | comment 17 | -------------------------------------------------------------------------------- /tests/examples/output/multiple-documents.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key: value 4 | 5 | --- 6 | 7 | ### 8 | # comment 9 | key1: value 10 | key2: value 11 | 12 | --- 13 | --- 14 | 15 | ### 16 | # comment 17 | key: value 18 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-uncommented-mapping.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | key1 6 | comment - documented key in undocumented mapping 7 | 8 | key2 9 | key2 10 | comment - documented key in nested undocumented mapping 11 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-uncommented-mapping.yml: -------------------------------------------------------------------------------- 1 | key: 2 | ### 3 | # comment - documented key in undocumented mapping 4 | key1: value 5 | key2: 6 | key1: value 7 | ### 8 | # comment - documented key in nested undocumented mapping 9 | key2: value 10 | -------------------------------------------------------------------------------- /tests/examples/output/capped1.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key: 4 | ### 5 | # Don't compile this comment 6 | key1: value 7 | key2: 8 | ### 9 | # Don't compile this comment 10 | key: value 11 | key3: 12 | ### 13 | # Don't compile this comment 14 | - key: value 15 | -------------------------------------------------------------------------------- /tests/examples/output/multilevel-commented-mapping.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key 5 | comment 6 | 7 | key 8 | comment - documented key in documented mapping 9 | 10 | key1 11 | comment - documented key in documented nested mapping 12 | 13 | key3 14 | comment - documented key in documented nested mapping 15 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-sequence.yml: -------------------------------------------------------------------------------- 1 | key1: 2 | ### 3 | # comment 4 | - key1: value 5 | key2: value 6 | ### 7 | # comment 8 | key3: value 9 | key4: value 10 | - value 11 | - key1: value 12 | ### 13 | # comment 14 | key3: value 15 | key2: 16 | ### 17 | # comment 18 | key1: value 19 | key2: value 20 | -------------------------------------------------------------------------------- /tests/examples/output/capped2.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key1: 4 | ### 5 | # comment 6 | key1: value 7 | key2: 8 | ### 9 | # Don't compile this comment 10 | key: value 11 | key3: 12 | ### 13 | # Don't compile this comment 14 | - key: value 15 | 16 | key2: 17 | ### 18 | # Don't compile this comment 19 | - key: value 20 | -------------------------------------------------------------------------------- /tests/examples/output/multilevel-commented-mapping.yml: -------------------------------------------------------------------------------- 1 | ### 2 | # comment 3 | key: 4 | ### 5 | # comment - documented key in documented mapping 6 | key: 7 | ### 8 | # comment - documented key in documented nested mapping 9 | key1: value 10 | key2: value 11 | ### 12 | # comment - documented key in documented nested mapping 13 | key3: value 14 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-nested-sequence.txt: -------------------------------------------------------------------------------- 1 | Test 2 | **** 3 | 4 | key1 5 | key 6 | comment 7 | 8 | key1 9 | comment 10 | 11 | key3 12 | comment 13 | 14 | key1 15 | comment 16 | 17 | key2 18 | key2 19 | comment 20 | 21 | key3 22 | comment 23 | 24 | key 25 | comment 26 | 27 | key2 28 | key 29 | comment 30 | -------------------------------------------------------------------------------- /tests/examples/output/comment-in-nested-sequence.yml: -------------------------------------------------------------------------------- 1 | key1: 2 | ### 3 | # comment 4 | - key: value 5 | ### 6 | # comment 7 | - - key1: value 8 | key2: value 9 | ### 10 | # comment 11 | key3: value 12 | key4: value 13 | ### 14 | # comment 15 | - key1: value 16 | key2: 17 | key1: value 18 | ### 19 | # comment 20 | key2: value 21 | key3: value 22 | ### 23 | # comment 24 | key3: value 25 | key4: value 26 | - key: value 27 | ### 28 | # comment 29 | - key: value 30 | key2: 31 | ### 32 | # comment 33 | key: value 34 | -------------------------------------------------------------------------------- /tests/requirements-1.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.16 2 | babel==2.16.0 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | docutils==0.17.1 6 | flake8==7.1.1 7 | idna==3.10 8 | imagesize==1.4.1 9 | Jinja2==3.1.4 10 | MarkupSafe==3.0.2 11 | mccabe==0.7.0 12 | packaging==24.2 13 | pycodestyle==2.12.1 14 | pyflakes==3.2.0 15 | Pygments==2.18.0 16 | requests==2.32.3 17 | ruamel.yaml==0.18.6 18 | ruamel.yaml.clib==0.2.12 19 | six==1.16.0 20 | snowballstemmer==2.2.0 21 | Sphinx==4.5.0 22 | sphinx-testing==1.0.1 23 | sphinxcontrib-applehelp==1.0.4 24 | sphinxcontrib-devhelp==1.0.2 25 | sphinxcontrib-htmlhelp==2.0.1 26 | sphinxcontrib-jsmath==1.0.1 27 | sphinxcontrib-qthelp==1.0.3 28 | sphinxcontrib-serializinghtml==1.1.5 29 | urllib3==2.2.3 30 | -------------------------------------------------------------------------------- /tests/requirements-2.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.16 2 | babel==2.16.0 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | docutils==0.19 6 | flake8==7.1.1 7 | idna==3.10 8 | imagesize==1.4.1 9 | Jinja2==3.1.4 10 | MarkupSafe==3.0.2 11 | mccabe==0.7.0 12 | packaging==24.2 13 | pycodestyle==2.12.1 14 | pyflakes==3.2.0 15 | Pygments==2.18.0 16 | requests==2.32.3 17 | ruamel.yaml==0.18.6 18 | ruamel.yaml.clib==0.2.12 19 | six==1.16.0 20 | snowballstemmer==2.2.0 21 | Sphinx==5.3.0 22 | sphinx-testing==1.0.1 23 | sphinxcontrib-applehelp==2.0.0 24 | sphinxcontrib-devhelp==2.0.0 25 | sphinxcontrib-htmlhelp==2.1.0 26 | sphinxcontrib-jsmath==1.0.1 27 | sphinxcontrib-qthelp==2.0.0 28 | sphinxcontrib-serializinghtml==2.0.0 29 | urllib3==2.2.3 30 | -------------------------------------------------------------------------------- /tests/requirements-3.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.16 2 | babel==2.16.0 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | docutils==0.19 6 | flake8==7.1.1 7 | idna==3.10 8 | imagesize==1.4.1 9 | Jinja2==3.1.4 10 | MarkupSafe==3.0.2 11 | mccabe==0.7.0 12 | packaging==24.2 13 | pycodestyle==2.12.1 14 | pyflakes==3.2.0 15 | Pygments==2.18.0 16 | requests==2.32.3 17 | ruamel.yaml==0.17.40 18 | ruamel.yaml.clib==0.2.12 19 | six==1.16.0 20 | snowballstemmer==2.2.0 21 | Sphinx==5.3.0 22 | sphinx-testing==1.0.1 23 | sphinxcontrib-applehelp==2.0.0 24 | sphinxcontrib-devhelp==2.0.0 25 | sphinxcontrib-htmlhelp==2.1.0 26 | sphinxcontrib-jsmath==1.0.1 27 | sphinxcontrib-qthelp==2.0.0 28 | sphinxcontrib-serializinghtml==2.0.0 29 | urllib3==2.2.3 30 | -------------------------------------------------------------------------------- /tests/requirements-4.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.16 2 | babel==2.16.0 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | docutils==0.19 6 | flake8==7.1.1 7 | idna==3.10 8 | imagesize==1.4.1 9 | Jinja2==3.1.4 10 | MarkupSafe==3.0.2 11 | mccabe==0.7.0 12 | packaging==24.2 13 | pycodestyle==2.12.1 14 | pyflakes==3.2.0 15 | Pygments==2.18.0 16 | requests==2.32.3 17 | ruamel.yaml==0.18.6 18 | ruamel.yaml.clib==0.2.12 19 | six==1.16.0 20 | snowballstemmer==2.2.0 21 | Sphinx==6.2.1 22 | sphinx-testing==1.0.1 23 | sphinxcontrib-applehelp==2.0.0 24 | sphinxcontrib-devhelp==2.0.0 25 | sphinxcontrib-htmlhelp==2.1.0 26 | sphinxcontrib-jsmath==1.0.1 27 | sphinxcontrib-qthelp==2.0.0 28 | sphinxcontrib-serializinghtml==2.0.0 29 | urllib3==2.2.3 30 | -------------------------------------------------------------------------------- /tests/requirements-5.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.16 2 | babel==2.16.0 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | docutils==0.21.2 6 | flake8==7.1.1 7 | idna==3.10 8 | imagesize==1.4.1 9 | Jinja2==3.1.4 10 | MarkupSafe==3.0.2 11 | mccabe==0.7.0 12 | packaging==24.2 13 | pycodestyle==2.12.1 14 | pyflakes==3.2.0 15 | Pygments==2.18.0 16 | requests==2.32.3 17 | ruamel.yaml==0.18.6 18 | ruamel.yaml.clib==0.2.12 19 | six==1.16.0 20 | snowballstemmer==2.2.0 21 | Sphinx==7.4.7 22 | sphinx-testing==1.0.1 23 | sphinxcontrib-applehelp==2.0.0 24 | sphinxcontrib-devhelp==2.0.0 25 | sphinxcontrib-htmlhelp==2.1.0 26 | sphinxcontrib-jsmath==1.0.1 27 | sphinxcontrib-qthelp==2.0.0 28 | sphinxcontrib-serializinghtml==2.0.0 29 | urllib3==2.2.3 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 66.1.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "sphinxcontrib-autoyaml" 7 | version = "1.1.3" 8 | description = "Sphinx autodoc extension for documenting YAML files from comments" 9 | authors = [ 10 | {name = "Jakub Pieńkowski", email = "jakub+sphinxcontrib-autoyaml@jakski.name"}, 11 | ] 12 | license = {text = "MIT"} 13 | readme = "README.md" 14 | classifiers = [ 15 | "Development Status :: 3 - Alpha", 16 | "Framework :: Sphinx :: Extension", 17 | "Intended Audience :: System Administrators", 18 | "License :: OSI Approved :: MIT License", 19 | "Topic :: Documentation", 20 | ] 21 | requires-python = ">=3.6" 22 | dependencies = [ 23 | "Sphinx>=3.5.1", 24 | "ruamel.yaml>=0.16.12", 25 | ] 26 | 27 | [project.optional-dependencies] 28 | test = [ 29 | "sphinx-testing>=1.0.1", 30 | "flake8>=3.8.4", 31 | ] 32 | 33 | [tool.setuptools] 34 | packages = ["sphinxcontrib"] 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Jakub Pieńkowski 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 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - test 8 | paths-ignore: 9 | - README.md 10 | - LICENSE 11 | - CHANGELOG 12 | - .gitignore 13 | 14 | jobs: 15 | linux: 16 | name: Linux, Python ${{ matrix.python }}, environment ${{ matrix.env }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | python: ["3.10", "3.11"] 21 | env: ["1", "2", "3", "4", "5"] 22 | exclude: 23 | # ModuleNotFoundError: No module named 'tomli' 24 | - python: "3.10" 25 | env: "5" 26 | steps: 27 | - name: Cache dependencies 28 | uses: actions/cache@v2 29 | with: 30 | key: ${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} 31 | path: | 32 | ~/.cache 33 | 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | 37 | - name: Setup Python 38 | uses: actions/setup-python@v3 39 | with: 40 | python-version: ${{ matrix.python }} 41 | 42 | - name: Run tests 43 | run: ./ci.sh test "$ENV_NUMBER" 44 | env: 45 | ENV_NUMBER: ${{ matrix.env }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | 45 | test 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail -o errtrace 4 | shopt -s inherit_errexit nullglob 5 | 6 | on_error() { 7 | declare exit_code=$? cmd=$BASH_COMMAND 8 | echo "Failing with code ${exit_code} at ${*} in command: ${cmd}" >&2 9 | exit "$exit_code" 10 | } 11 | 12 | print_help() { 13 | cat << EOF 14 | Handle continuous integration tasks. 15 | 16 | Options: 17 | -h|--help Show this message 18 | 19 | Subcommands: 20 | update-requirements Update test requirements files 21 | test VARIANT Run specified test variant 22 | EOF 23 | } 24 | 25 | # shellcheck disable=SC2034 26 | update_requirements_cmd() { 27 | declare selected=${1:-} i 28 | declare -a env1 env2 env3 env4 29 | # Some contrib extensions don't specify required Sphinx version in dependencies, yet fail on setup 30 | env1=( 31 | "Sphinx>=4,<5" 32 | "sphinxcontrib-applehelp<=1.0.5" 33 | "sphinxcontrib-devhelp<=1.0.2" 34 | "sphinxcontrib-htmlhelp<=2.0.1" 35 | "sphinxcontrib-serializinghtml<=1.1.5" 36 | "sphinxcontrib-qthelp<=1.0.3" 37 | ) 38 | env2=("Sphinx>=5,<6") 39 | # ruamel.yaml deprecated compose_all in 0.18 40 | env3=("Sphinx>=5,<6" "ruamel.yaml>=0.17,<0.18") 41 | env4=("Sphinx>=6,<7") 42 | env5=("Sphinx>=7,<8") 43 | for i in {1..5}; do 44 | if [ -n "$selected" ] && [ "$i" != "$selected" ]; then 45 | continue 46 | fi 47 | declare -n constraints="env${i}" 48 | python3 -m venv --clear venv 49 | ./venv/bin/pip install ".[test]" "${constraints[@]}" 50 | ./venv/bin/pip freeze --exclude "sphinxcontrib-autoyaml" >"tests/requirements-${i}.txt" 51 | if [ "$selected" = "$i" ]; then 52 | break 53 | fi 54 | done 55 | } 56 | 57 | test_cmd() { 58 | declare env=$1 59 | python3 -m venv --clear venv 60 | ./venv/bin/pip install --no-deps -r "tests/requirements-${env}.txt" . 61 | ./venv/bin/python -m tests -v 62 | } 63 | 64 | main() { 65 | trap 'on_error ${BASH_SOURCE[0]}:${LINENO}' ERR 66 | 67 | declare subcommand 68 | subcommand=$1 69 | shift 70 | case "$subcommand" in 71 | update-requirements) 72 | update_requirements_cmd "$@" 73 | ;; 74 | test) 75 | test_cmd "$@" 76 | ;; 77 | -h|--help) 78 | print_help 79 | ;; 80 | *) 81 | print_help 82 | echo "Unknown command: ${subcommand}" >&2 83 | return 1 84 | esac 85 | } 86 | 87 | main "$@" 88 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # [1.1.3] - 2024-11-21 2 | ### Added 3 | - Tests for Sphinx 7 and 8 4 | 5 | ### Fixed 6 | - Missing compose_all in ruamel.yaml 0.18 7 | 8 | ### Removed 9 | - Tests for Sphinx 3. `ImportError: cannot import name 'Union' from 'types'` 10 | for Python above 3.9. Version constraint is left for compatibility, but support 11 | for Sphinx 3 will be dropped in future versions. 12 | 13 | # [1.1.2] - 2024-11-13 14 | ### Changed 15 | - Support for ruamel.yaml 0.18.x 16 | 17 | # [1.1.1] - 2023-04-12 18 | ### Fixed 19 | - Comment strings from multiline scalar values aren't mistakenly used anymore 20 | 21 | ### Added 22 | - Support for YAML SafeLoader 23 | 24 | ### Changed 25 | - Updated dependency lockfiles 26 | 27 | # [1.1.0] - 2023-02-12 28 | ### Removed 29 | - Upper limits on Sphinx version 30 | 31 | # [1.0.0] - 2022-07-11 32 | ### Changed 33 | - Sequences are now traversed as well 34 | 35 | # [0.6.2] - 2022-07-04 36 | ### Added 37 | - Support for Sphinx 4.x and 5.x 38 | 39 | # [0.6.1] - 2021-03-30 40 | ### Changed 41 | - Support all Python versions from 3.6 up 42 | - Update dependencies 43 | 44 | # [0.6.0] - 2021-03-05 45 | ### Changed 46 | - Use one definition list per YAML document. 47 | - Comments from nested keys are no longer root level definitions. 48 | Parser constructs a tree from found commented keys and decides visibility 49 | based on `autoyaml_level`. 50 | 51 | ### Added 52 | - Parse nested keys where applicable. 53 | 54 | ## [0.5.0] - 2019-03-17 55 | ### Changed 56 | - Lines with comment delimiter are no longer compiled to output. 57 | - Only comments directly above mapping keys are compild to output. 58 | - docutils definitions are now returned instead of paragraphs. 59 | 60 | ## [0.4.2] - 2018-12-27 61 | ### Removed 62 | - Deprecated AutodocReporter. 63 | 64 | ## [0.4.1] - 2018-02-22 65 | ### Added 66 | - Debug level log messages with paths to parsed files. 67 | 68 | ### Fixed 69 | - Error reporting compatibility with Sphinx. 70 | 71 | ## [0.4.0] - 2018-02-06 72 | ### Changed 73 | - Search for YAML files relatively to environment's source directory. 74 | - Use absolute paths in error messages. 75 | 76 | ## [0.3.3] - 2017-12-20 77 | ### Fixed 78 | - Removing whitespace from beginning. 79 | 80 | ### Added 81 | - Tests. 82 | 83 | ## [0.3.2] - 2017-12-18 84 | ### Fixed 85 | - Proper whitespace handling for comment openings. 86 | 87 | ## [0.3.1] - 2017-12-18 88 | ### Fixed 89 | - Proper whitespace handling for comment openings. 90 | 91 | ## [0.3.0] - 2017-12-15 92 | ### Changed 93 | - Moved module to sphinxcontrib namespace 94 | 95 | ## [0.2.0] - 2017-12-03 96 | ### Removed 97 | - Handling directories. 98 | 99 | ### Fixed 100 | - Faulty file and line location reporting. 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sphinxcontrib-autoyaml 2 | 3 | This Sphinx autodoc extension documents YAML files from comments. Documentation 4 | is returned as reST definitions, e.g.: 5 | 6 | This document: 7 | 8 | ```yaml 9 | ### 10 | # Enable Nginx web server. 11 | enable_nginx: true 12 | 13 | ### 14 | # Enable Varnish caching proxy. 15 | enable_varnish: true 16 | ``` 17 | 18 | would be turned into text: 19 | 20 | ```rst 21 | enable_nginx 22 | 23 | Enable Nginx web server. 24 | 25 | enable_varnish 26 | 27 | Enable Varnish caching proxy. 28 | ``` 29 | 30 | See `tests/examples/output/*.yml` and `tests/examples/output/*.txt` for 31 | more examples. 32 | 33 | `autoyaml` will take into account only comments which first line starts with 34 | `autoyaml_doc_delimiter`. 35 | 36 | ## Usage 37 | 38 | You can use `autoyaml` directive, where you want to extract comments 39 | from YAML file, e.g.: 40 | 41 | ```rst 42 | Some title 43 | ========== 44 | 45 | Documenting single YAML file. 46 | 47 | .. autoyaml:: some_yml_file.yml 48 | ``` 49 | 50 | ## Options 51 | 52 | ```python 53 | # Look for YAML files relatively to this directory. 54 | autoyaml_root = ".." 55 | # Character(s) which start a documentation comment. 56 | autoyaml_doc_delimiter = "###" 57 | # Comment start character(s). 58 | autoyaml_comment = "#" 59 | # Parse comments from nested structures n-levels deep. 60 | autoyaml_level = 1 61 | # Whether to use YAML SafeLoader 62 | autoyaml_safe_loader = False 63 | ``` 64 | 65 | ## Installing 66 | 67 | Issue command: 68 | 69 | ```sh 70 | pip install sphinxcontrib-autoyaml 71 | ``` 72 | 73 | And add extension in your project's ``conf.py``: 74 | 75 | ```python 76 | extensions = ["sphinxcontrib.autoyaml"] 77 | ``` 78 | 79 | ## Caveats 80 | 81 | ### Mapping keys nested in sequences 82 | 83 | Sequences are traversed as well, but they are not represented in output 84 | documentation. This extension focuses only on documenting mapping keys. It means 85 | that structure like this: 86 | 87 | ```yaml 88 | key: 89 | ### 90 | # comment1 91 | - - inner_key1: value 92 | ### 93 | # comment2 94 | inner_key2: value 95 | ### 96 | # comment3 97 | - inner_key3: value 98 | ``` 99 | 100 | will be flattened, so it will appear as though inner keys exist directly under 101 | `key`. Duplicated key documentation will be duplicated in output as well. See 102 | `tests/examples/output/comment-in-nested-sequence.txt` and 103 | `tests/examples/output/comment-in-nested-sequence.yml` to get a better 104 | understanding how sequences are processed. 105 | 106 | ### Complex mapping keys 107 | 108 | YAML allows for complex mapping keys like so: 109 | 110 | ```yaml 111 | [1, 2]: value 112 | ``` 113 | 114 | These kind of keys won't be documented in output, because it's unclear how they 115 | should be represented as a string. 116 | 117 | ### Flow-style entries 118 | 119 | YAML allows writing complex data structures in single line like JSON. 120 | Documentation is generated only for the first key in such entry, so this: 121 | 122 | ```yaml 123 | ### 124 | # comment 125 | key: {key1: value, key2: value, key3: value} 126 | ``` 127 | 128 | would yield documentation only for `key`. 129 | -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import warnings 3 | import os 4 | from collections import defaultdict 5 | from pathlib import Path 6 | from functools import wraps 7 | 8 | from sphinx_testing import with_app 9 | from sphinxcontrib.autoyaml import AutoYAMLException 10 | 11 | 12 | CONFIG = { 13 | "master_doc": "index", 14 | "extensions": ["sphinxcontrib.autoyaml"], 15 | "autoyaml_root": ".", 16 | "autoyaml_level": 0, 17 | "autoyaml_safe_loader": True, 18 | } 19 | 20 | 21 | def patch_config(delta): 22 | r = CONFIG.copy() 23 | r.update(delta) 24 | return r 25 | 26 | 27 | def build(app, filename, filetype): 28 | """Build and return documents without known warnings""" 29 | with open(os.path.join(app.srcdir, "index.rst"), "w") as index: 30 | index.write(f"Test\n========\n.. autoyaml:: {filename}.yml\n") 31 | with warnings.catch_warnings(): 32 | # Ignore warnings emitted by docutils internals. 33 | warnings.filterwarnings("ignore", "'U' mode is deprecated", DeprecationWarning) 34 | app.build() 35 | with open( 36 | os.path.join(app.outdir, f"index.{filetype}"), encoding="utf-8" 37 | ) as rendered: 38 | return rendered.read() 39 | 40 | 41 | def make_text_test(filename, directory="output", confoverrides=None): 42 | if confoverrides is None: 43 | confoverrides = {} 44 | @with_app( 45 | confoverrides=patch_config(confoverrides), 46 | buildername="text", 47 | srcdir=os.path.join("tests/examples", directory), 48 | copy_srcdir_to_tmpdir=True, 49 | ) 50 | def test(self, app, status, warning): 51 | output = build(app, filename, "txt") 52 | correct_file = os.path.join("tests/examples", directory, f"{filename}.txt") 53 | if os.environ.get("TEST_GENERATE_EXAMPLES", "0") == "1": 54 | with open(correct_file, "w") as f: 55 | f.write(output) 56 | else: 57 | with open(correct_file, "r") as f: 58 | correct = f.read() 59 | self.assertEqual(correct, output) 60 | 61 | return test 62 | 63 | 64 | class TestAutoYAML(unittest.TestCase): 65 | @with_app( 66 | confoverrides=CONFIG, 67 | buildername="html", 68 | srcdir="tests/examples/wrong_location1", 69 | copy_srcdir_to_tmpdir=True, 70 | ) 71 | def test_missing_file(self, app, status, warning): 72 | with self.assertRaises(AutoYAMLException): 73 | build(app, "index", "html") 74 | 75 | @with_app( 76 | confoverrides=CONFIG, 77 | buildername="html", 78 | srcdir="tests/examples/wrong_location2", 79 | copy_srcdir_to_tmpdir=True, 80 | ) 81 | def test_directory_argument(self, app, status, warning): 82 | with self.assertRaises(AutoYAMLException): 83 | build(app, "index", "html") 84 | 85 | 86 | if __name__ == "__main__": 87 | text_examples = [ 88 | f for f in os.listdir("tests/examples/output") if f.endswith(".yml") 89 | ] 90 | confoverrides = defaultdict(dict) 91 | confoverrides["capped1"] = {"autoyaml_level": 1} 92 | confoverrides["capped2"] = {"autoyaml_level": 2} 93 | for text_example in text_examples: 94 | filename, _ = os.path.splitext(text_example) 95 | test_name = filename.replace("-", "_") 96 | setattr(TestAutoYAML, f"test_{test_name}", make_text_test(filename, confoverrides=confoverrides[filename])) 97 | unittest.main() 98 | -------------------------------------------------------------------------------- /sphinxcontrib/autoyaml/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ruamel.yaml.nodes import ( 4 | MappingNode, 5 | SequenceNode, 6 | ScalarNode, 7 | ) 8 | from ruamel.yaml import SafeLoader, Loader 9 | from docutils.statemachine import ViewList 10 | from docutils.parsers.rst import Directive 11 | from docutils import nodes 12 | from sphinx.util import logging 13 | from sphinx.util.docutils import switch_source_input 14 | from sphinx.errors import ExtensionError 15 | 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class TreeNode: 21 | def __init__(self, value, comments, parent=None): 22 | self.value = value 23 | self.parent = parent 24 | self.children = [] 25 | self.comments = comments 26 | if value is None: 27 | self.comment = None 28 | else: 29 | # Flow-style entries may attempt to incorrectly reuse comments 30 | self.comment = self.comments.pop(self.value.start_mark.line + 1, None) 31 | 32 | def add_child(self, value): 33 | node = TreeNode(value, self.comments, self) 34 | self.children.append(node) 35 | return node 36 | 37 | def remove_child(self): 38 | return self.children.pop(0) 39 | 40 | 41 | class AutoYAMLException(ExtensionError): 42 | 43 | category = "AutoYAML error" 44 | 45 | 46 | class AutoYAMLDirective(Directive): 47 | 48 | required_arguments = 1 49 | 50 | def run(self): 51 | self.config = self.state.document.settings.env.config 52 | self.env = self.state.document.settings.env 53 | self.record_dependencies = self.state.document.settings.record_dependencies 54 | output_nodes = [] 55 | location = os.path.normpath( 56 | os.path.join( 57 | self.env.srcdir, self.config.autoyaml_root + "/" + self.arguments[0] 58 | ) 59 | ) 60 | if os.path.isfile(location): 61 | logger.debug("[autoyaml] parsing file: %s", location) 62 | try: 63 | output_nodes.extend(self._parse_file(location)) 64 | except Exception as e: 65 | raise AutoYAMLException( 66 | "Failed to parse YAML file: %s" % (location) 67 | ) from e 68 | else: 69 | raise AutoYAMLException( 70 | '%s:%s: location "%s" is not a file.' 71 | % ( 72 | self.env.doc2path(self.env.docname, None), 73 | self.content_offset - 1, 74 | location, 75 | ) 76 | ) 77 | self.record_dependencies.add(location) 78 | return output_nodes 79 | 80 | def _get_comments(self, source, source_file): 81 | comments = {} 82 | in_docstring = False 83 | for linenum, line in enumerate(source.splitlines(), start=1): 84 | line = line.lstrip() 85 | if line.startswith(self.config.autoyaml_doc_delimiter): 86 | in_docstring = True 87 | comment = ViewList() 88 | elif line.startswith(self.config.autoyaml_comment) and in_docstring: 89 | line = line[len(self.config.autoyaml_comment) :] 90 | # strip preceding whitespace 91 | if line and line[0] == " ": 92 | line = line[1:] 93 | comment.append(line, source_file, linenum) 94 | elif in_docstring: 95 | comments[linenum] = comment 96 | in_docstring = False 97 | return comments 98 | 99 | def _parse_document(self, doc, comments): 100 | tree = TreeNode(None, comments) 101 | if not isinstance(doc, MappingNode): 102 | return tree 103 | unvisited = [(doc, 0)] 104 | while len(unvisited) > 0: 105 | node, index = unvisited[-1] 106 | if index == len(node.value): 107 | # Only mapping nodes increase depth. Directly nested 108 | # sequences are flattened. 109 | if tree.parent is not None and (len(unvisited) == 1 or isinstance(unvisited[-2][0], MappingNode)): 110 | tree = tree.parent 111 | unvisited.pop() 112 | continue 113 | for node_item in node.value[index:]: 114 | index += 1 115 | unvisited[-1] = (node, index) 116 | subtree = None 117 | node_key = None 118 | node_value = None 119 | if isinstance(node, SequenceNode): 120 | node_value = node_item 121 | elif isinstance(node, MappingNode): 122 | node_key, node_value = node_item 123 | # Using complex structures for keys in YAML is possible as 124 | # well, but it's currently not handled. 125 | if not isinstance(node_key, ScalarNode): 126 | continue 127 | subtree = tree.add_child(node_key) 128 | for i in (node_key, node_value): 129 | if isinstance(i, ScalarNode): 130 | for i in range(i.start_mark.line, i.end_mark.line + 1): 131 | comments.pop(i + 1, None) 132 | if isinstance(node_value, (MappingNode, SequenceNode)) and ( 133 | len(unvisited) + 1 <= self.config.autoyaml_level 134 | or self.config.autoyaml_level == 0 135 | ): 136 | unvisited.append((node_value, 0)) 137 | if subtree is not None: 138 | tree = subtree 139 | break 140 | return tree 141 | 142 | def _generate_documentation(self, tree): 143 | unvisited = [tree] 144 | while len(unvisited) > 0: 145 | node = unvisited[-1] 146 | if len(node.children) > 0: 147 | unvisited.append(node.remove_child()) 148 | continue 149 | if node.parent is None or node.comment is None: 150 | unvisited.pop() 151 | continue 152 | with switch_source_input(self.state, node.comment): 153 | definition = nodes.definition() 154 | if isinstance(node.comment, ViewList): 155 | self.state.nested_parse(node.comment, 0, definition) 156 | else: 157 | definition += node.comment 158 | node.comment = nodes.definition_list_item( 159 | "", 160 | nodes.term("", node.value.value), 161 | definition, 162 | ) 163 | if node.parent.comment is None: 164 | node.parent.comment = nodes.definition_list() 165 | elif not isinstance(node.parent.comment, nodes.definition_list): 166 | with switch_source_input(self.state, node.parent.comment): 167 | dlist = nodes.definition_list() 168 | self.state.nested_parse(node.parent.comment, 0, dlist) 169 | node.parent.comment = dlist 170 | node.parent.comment += node.comment 171 | unvisited.pop() 172 | return tree.comment 173 | 174 | def _compose_all(self, loader): 175 | try: 176 | while loader.check_node(): 177 | yield loader._composer.get_node() 178 | finally: 179 | loader._parser.dispose() 180 | 181 | def _parse_file(self, source_file): 182 | with open(source_file, "r") as f: 183 | source = f.read() 184 | comments = self._get_comments(source, source_file) 185 | if self.config.autoyaml_safe_loader: 186 | loader = SafeLoader 187 | else: 188 | loader = Loader 189 | for doc in self._compose_all(Loader(source)): 190 | docs = self._generate_documentation(self._parse_document(doc, comments)) 191 | if docs is not None: 192 | yield docs 193 | 194 | 195 | def setup(app): 196 | app.add_directive("autoyaml", AutoYAMLDirective) 197 | app.add_config_value("autoyaml_root", "..", "env") 198 | app.add_config_value("autoyaml_doc_delimiter", "###", "env") 199 | app.add_config_value("autoyaml_comment", "#", "env") 200 | app.add_config_value("autoyaml_level", 1, "env") 201 | # Set to false to preserve backward compatibility. 202 | app.add_config_value("autoyaml_safe_loader", False, "env") 203 | --------------------------------------------------------------------------------