├── .git_archival.txt ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ ├── history-update.yaml │ ├── integration.yaml │ ├── python-publish.yaml │ └── test.yml ├── .gitignore ├── .mergify.yml ├── .readthedocs.yml ├── .testr.conf ├── .virtualenvwrapper ├── postactivate └── predeactivate ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── requirements.txt └── source │ ├── conf.py │ ├── customize.rst │ ├── developers.rst │ ├── history.rst │ ├── index.rst │ ├── install.rst │ ├── run.rst │ └── spelling_wordlist.txt ├── integration_tests └── build_django.py ├── pyproject.toml ├── requirements-dev.txt ├── show-changelog.sh ├── sphinxcontrib └── spelling │ ├── __init__.py │ ├── asset.py │ ├── builder.py │ ├── checker.py │ ├── directive.py │ ├── domain.py │ ├── filters.py │ └── role.py ├── tests ├── helpers.py ├── test_builder.py ├── test_checker.py ├── test_filter.py └── test_wordlist.txt └── tools └── history-update.sh /.git_archival.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/pypa/setuptools_scm 2 | node: 322ad6e91130791785e82daaa0138dda9d7d47db 3 | node-date: 2025-05-25T11:53:34-04:00 4 | describe-name: 8.0.1-9-g322ad6e9 5 | ref-names: HEAD -> main 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .git_archival.txt export-subst 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Via https://github.com/nedbat/scriv/blob/main/.github/dependabot.yml 2 | # 3 | # From: 4 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot 5 | # Set update schedule for GitHub Actions 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | # Check for updates to GitHub Actions once a week 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | if: ${{ !startsWith(github.ref, 'refs/tags') }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | hatch-environment: 16 | - docs:build 17 | - docs:check 18 | - test:lint 19 | - test:pkglint 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | 31 | - name: Install dependencies 32 | run: python -m pip install hatch 33 | 34 | - name: Run 35 | run: hatch run ${{ matrix.hatch-environment }} 36 | -------------------------------------------------------------------------------- /.github/workflows/history-update.yaml: -------------------------------------------------------------------------------- 1 | name: History Update 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | history-update: 9 | runs-on: ubuntu-latest 10 | if: ${{ !startsWith(github.ref, 'refs/tags') }} 11 | 12 | strategy: 13 | fail-fast: false 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | # Get all of the git history. https://github.com/actions/checkout 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Check for history.rst update 22 | if: startsWith(github.ref, 'refs/tags') != true 23 | run: ./tools/history-update.sh 24 | -------------------------------------------------------------------------------- /.github/workflows/integration.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | django: 9 | runs-on: ubuntu-latest 10 | if: ${{ !startsWith(github.ref, 'refs/tags') }} 11 | 12 | strategy: 13 | fail-fast: false 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Python 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: '3.x' 24 | 25 | - name: Install dependencies 26 | run: python3 -m pip install hatch 27 | 28 | - name: Test 29 | run: hatch run integration:django 30 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yaml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | - push 8 | 9 | jobs: 10 | build-n-publish: 11 | name: Build and publish Python distributions to PyPI 12 | if: ${{ github.repository_owner == 'sphinx-contrib' }} 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | - name: Set up Python 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: '3.x' 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install setuptools wheel twine build setuptools_scm 28 | - name: Build sdist and wheel 29 | run: | 30 | python -m build 31 | # - name: Publish distribution to Test PyPI 32 | # uses: pypa/gh-action-pypi-publish@master 33 | # with: 34 | # password: ${{ secrets.test_pypi_password }} 35 | # repository_url: https://test.pypi.org/legacy/ 36 | - name: Publish distribution to PyPI 37 | if: startsWith(github.ref, 'refs/tags') 38 | uses: pypa/gh-action-pypi-publish@master 39 | with: 40 | password: ${{ secrets.pypi_password }} 41 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | if: ${{ !startsWith(github.ref, 'refs/tags') }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | python-version: 16 | - '3.10' 17 | - '3.11' 18 | - '3.12' 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install dependencies 31 | run: python -m pip install hatch 32 | 33 | - name: Run tests 34 | run: hatch run test:test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg 3 | *.so 4 | build/ 5 | dist/ 6 | env/ 7 | *.egg-info/ 8 | TAGS 9 | *~ 10 | .DS_Store 11 | .idea 12 | .vscode 13 | spelling/spelling_wordlist.txt 14 | .tox/ 15 | .coverage/ 16 | *.swp 17 | 18 | # Created by pbr 19 | AUTHORS 20 | ChangeLog 21 | /.coverage 22 | /.testrepository/ 23 | /cover/ 24 | .eggs/ 25 | # Generated by setuptools_scm 26 | sphinxcontrib/spelling/version.py -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Add CI label 3 | conditions: 4 | - or: 5 | - "title~=^tox:" 6 | - "title~=^ci:" 7 | - "files~=tox.ini" 8 | - "files~=.github/" 9 | actions: 10 | label: 11 | add: 12 | - ci 13 | 14 | - name: Add Mergify label 15 | conditions: 16 | - or: 17 | - "title~=^mergify:" 18 | - "files~=.mergify.yml$" 19 | actions: 20 | label: 21 | add: 22 | - mergify 23 | 24 | - name: Automatic merge on approval 25 | conditions: 26 | - and: 27 | - "check-success=build (docs:build)" 28 | - "check-success=build (test:linter)" 29 | - "check-success=build (docs:spelling)" 30 | - "check-success=django" 31 | - "check-success=build (3.10)" 32 | - "check-success=build (3.11)" 33 | - "check-success=build (3.12)" 34 | - "-draft" 35 | - or: 36 | - "check-success=history-update" 37 | - "label=ci" 38 | - or: 39 | - "approved-reviews-by=dhellmann" 40 | - "author=dhellmann" 41 | actions: 42 | merge: 43 | method: merge 44 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # https://docs.readthedocs.io/en/stable/config-file/v2.html 2 | 3 | # Required 4 | version: 2 5 | 6 | sphinx: 7 | configuration: docs/source/conf.py 8 | 9 | python: 10 | install: 11 | - requirements: docs/requirements.txt 12 | -------------------------------------------------------------------------------- /.testr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_command=${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION 3 | test_id_option=--load-list $IDFILE 4 | test_list_option=--list 5 | -------------------------------------------------------------------------------- /.virtualenvwrapper/postactivate: -------------------------------------------------------------------------------- 1 | # -*- shell-script -*- 2 | 3 | #export PYENCHANT_LIBRARY_PATH=/opt/homebrew/Cellar/enchant/2.6.4/lib/libenchant-2.2.dylib 4 | if [ $(uname) = Darwin ]; then 5 | export PYENCHANT_LIBRARY_PATH=$(brew list enchant | grep 'libenchant-.*\.dylib' | head -n 1) 6 | fi 7 | -------------------------------------------------------------------------------- /.virtualenvwrapper/predeactivate: -------------------------------------------------------------------------------- 1 | # -*- shell-script -*- 2 | 3 | unset PYENCHANT_LIBRARY_PATH 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Doug Hellmann. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Default target is to show help 2 | help: 3 | @echo "sdist - Source distribution" 4 | @echo "html - HTML documentation" 5 | @echo "spelling - Check spelling of documentation" 6 | @echo "upload - upload a new release to PyPI" 7 | @echo "installwebsite - deploy web version of docs" 8 | @echo "develop - install development version" 9 | @echo "test - run the test suite" 10 | 11 | 12 | .PHONY: sdist 13 | sdist: html 14 | python setup.py sdist 15 | 16 | .PHONY: upload 17 | upload: html 18 | python setup.py sdist upload 19 | 20 | # Documentation 21 | .PHONY: html 22 | html: 23 | (cd docs && $(MAKE) html) 24 | 25 | .PHONY: spelling 26 | spelling: 27 | (cd docs && $(MAKE) spelling) 28 | 29 | installwebsite: html 30 | (cd docs/build/html && rsync --rsh=ssh --archive --delete --verbose . www.doughellmann.com:/var/www/doughellmann/DocumentRoot/docs/sphinxcontrib.spelling/) 31 | 32 | # Register the new version on pypi 33 | .PHONY: register 34 | register: 35 | python setup.py register 36 | 37 | # Testing 38 | .PHONY: test 39 | test: 40 | tox 41 | 42 | develop: 43 | python setup.py develop 44 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. -*- mode: rst -*- 2 | 3 | ========================= 4 | sphinxcontrib-spelling 5 | ========================= 6 | 7 | This package contains sphinxcontrib.spelling, a spelling checker for 8 | Sphinx-based documentation. It uses PyEnchant_ to produce a report 9 | showing misspelled words. 10 | 11 | Refer to the `main documentation page 12 | `__ for 13 | installation and setup details. 14 | 15 | License 16 | ======= 17 | 18 | Copyright Doug Hellmann, All Rights Reserved 19 | 20 | Permission to use, copy, modify, and distribute this software and its 21 | documentation for any purpose and without fee is hereby granted, 22 | provided that the above copyright notice appear in all copies and that 23 | both that copyright notice and this permission notice appear in 24 | supporting documentation, and that the name of Doug Hellmann not be used 25 | in advertising or publicity pertaining to distribution of the software 26 | without specific, written prior permission. 27 | 28 | DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 29 | INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO 30 | EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR 31 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF 32 | USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 33 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 34 | PERFORMANCE OF THIS SOFTWARE. 35 | 36 | .. _PyEnchant: https://github.com/pyenchant/pyenchant 37 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sphinxcontribspelling.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sphinxcontribspelling.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/sphinxcontribspelling" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sphinxcontribspelling" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | spelling: 128 | $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling 129 | 130 | doctest: 131 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 132 | @echo "Testing of doctests in the sources finished, look at the " \ 133 | "results in $(BUILDDIR)/doctest/output.txt." 134 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is only needed for readthedocs.org 2 | .[docs] 3 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # sphinxcontrib.spelling documentation build configuration file, created by 2 | # sphinx-quickstart on Sun Apr 17 15:33:23 2011. 3 | # 4 | # This file is execfile()d with the current directory set to its containing dir. 5 | # 6 | # Note that not all possible configuration values are present in this 7 | # autogenerated file. 8 | # 9 | # All configuration values have a default; values that are commented out 10 | # serve to show the default. 11 | import os 12 | import sys 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | # -- General configuration ----------------------------------------------------- 20 | 21 | # If your documentation needs a minimal Sphinx version, state it here. 22 | # needs_sphinx = '1.0' 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = [ 27 | "sphinxcontrib.spelling", 28 | ] 29 | 30 | spelling_word_list_filename = [ 31 | "spelling_wordlist.txt", 32 | ] 33 | 34 | spelling_show_suggestions = True 35 | spelling_ignore_pypi_package_names = True 36 | spelling_ignore_contributor_names = True 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ["_templates"] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = ".rst" 43 | 44 | # The encoding of source files. 45 | # source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = "index" 49 | 50 | # General information about the project. 51 | project = "sphinxcontrib.spelling" 52 | copyright = "2011, Doug Hellmann" 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = "1.4" 60 | # The full version, including alpha/beta/rc tags. 61 | release = version 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | language = "en" 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | # today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | # today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = [] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all documents. 78 | # default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | # add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | # add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | # show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = "sphinx" 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | # modindex_common_prefix = [] 96 | 97 | 98 | # -- Options for HTML output --------------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = "default" 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | # html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | # html_theme_path = [] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | # html_title = None 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | # html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | # html_logo = None 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | # html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | # html_static_path = ['_static'] 132 | 133 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 134 | # using the given strftime format. 135 | # html_last_updated_fmt = '%b %d, %Y' 136 | 137 | # If true, SmartyPants will be used to convert quotes and dashes to 138 | # typographically correct entities. 139 | # html_use_smartypants = True 140 | 141 | # Custom sidebar templates, maps document names to template names. 142 | # html_sidebars = {} 143 | 144 | # Additional templates that should be rendered to pages, maps page names to 145 | # template names. 146 | # html_additional_pages = {} 147 | 148 | # If false, no module index is generated. 149 | # html_domain_indices = True 150 | 151 | # If false, no index is generated. 152 | # html_use_index = True 153 | 154 | # If true, the index is split into individual pages for each letter. 155 | # html_split_index = False 156 | 157 | # If true, links to the reST sources are added to the pages. 158 | # html_show_sourcelink = True 159 | 160 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 161 | # html_show_sphinx = True 162 | 163 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 164 | # html_show_copyright = True 165 | 166 | # If true, an OpenSearch description file will be output, and all pages will 167 | # contain a tag referring to it. The value of this option must be the 168 | # base URL from which the finished HTML is served. 169 | # html_use_opensearch = '' 170 | 171 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 172 | # html_file_suffix = None 173 | 174 | # Output file base name for HTML help builder. 175 | htmlhelp_basename = "sphinxcontribspellingdoc" 176 | 177 | 178 | # -- Options for LaTeX output -------------------------------------------------- 179 | 180 | # The paper size ('letter' or 'a4'). 181 | # latex_paper_size = 'letter' 182 | 183 | # The font size ('10pt', '11pt' or '12pt'). 184 | # latex_font_size = '10pt' 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ( 190 | "index", 191 | "sphinxcontribspelling.tex", 192 | "sphinxcontrib.spelling Documentation", 193 | "Doug Hellmann", 194 | "manual", 195 | ), 196 | ] 197 | 198 | # The name of an image file (relative to this directory) to place at the top of 199 | # the title page. 200 | # latex_logo = None 201 | 202 | # For "manual" documents, if this is true, then toplevel headings are parts, 203 | # not chapters. 204 | # latex_use_parts = False 205 | 206 | # If true, show page references after internal links. 207 | # latex_show_pagerefs = False 208 | 209 | # If true, show URL addresses after external links. 210 | # latex_show_urls = False 211 | 212 | # Additional stuff for the LaTeX preamble. 213 | # latex_preamble = '' 214 | 215 | # Documents to append as an appendix to all manuals. 216 | # latex_appendices = [] 217 | 218 | # If false, no module index is generated. 219 | # latex_domain_indices = True 220 | 221 | 222 | # -- Options for manual page output -------------------------------------------- 223 | 224 | # One entry per manual page. List of tuples 225 | # (source start file, name, description, authors, manual section). 226 | man_pages = [ 227 | ( 228 | "index", 229 | "sphinxcontribspelling", 230 | "sphinxcontrib.spelling Documentation", 231 | ["Doug Hellmann"], 232 | 1, 233 | ) 234 | ] 235 | -------------------------------------------------------------------------------- /docs/source/customize.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Configuration Options 3 | ======================= 4 | 5 | These options can be set in ``conf.py`` along with the other Sphinx 6 | configuration settings. 7 | 8 | Input Options 9 | ============= 10 | 11 | ``spelling_lang='en_US'`` 12 | 13 | String specifying the language, as understood by PyEnchant and 14 | enchant. Defaults to ``en_US`` for US English. 15 | 16 | ``tokenizer_lang='en_US'`` 17 | 18 | String specifying the tokenizer language as understood by PyEnchant 19 | and enchant. Defaults to ``en_US`` for US English. 20 | 21 | ``spelling_word_list_filename='spelling_wordlist.txt'`` 22 | 23 | String specifying a file containing a list of words known to be 24 | spelled correctly but that do not appear in the language dictionary 25 | selected by ``spelling_lang``. The file should contain one word per 26 | line. Refer to the `PyEnchant tutorial`_ for details. 27 | 28 | To add multiple files use a list, or a comma separated string. This 29 | is useful when calling sphinx with ``-D 30 | spelling_word_list_filename=...`` which will not accept a list and 31 | will only accept a string parameter. 32 | 33 | ``spelling_word_list_filename=['spelling_wordlist.txt','another_list.txt']`` 34 | 35 | Same as above, but with several files of correctly spelled words. 36 | 37 | ``spelling_word_list_filename='spelling_wordlist.txt,another_list.txt'`` 38 | 39 | Same as above, but with several files of correctly spelled words, and 40 | passing the setting as a single string. 41 | 42 | ``spelling_exclude_patterns=['ignored_*']`` 43 | 44 | A list of glob-style patterns that should be ignored when checking spelling. 45 | They are matched against the source file names relative to the source 46 | directory, using slashes as directory separators on all platforms. See Sphinx's 47 | `exclude_patterns`_ option for more details on glob-style patterns. 48 | 49 | .. _PyEnchant tutorial: https://github.com/rfk/pyenchant/blob/master/website/content/tutorial.rst 50 | .. _exclude_patterns : https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-exclude_patterns 51 | 52 | .. _output-options: 53 | 54 | Output Options 55 | ============== 56 | 57 | ``spelling_show_suggestions=False`` 58 | 59 | Boolean controlling whether suggestions for misspelled words are 60 | printed. Defaults to ``False``. 61 | 62 | ``spelling_suggestion_limit=0`` 63 | 64 | Integer number of suggestions to emit when 65 | ``spelling_show_suggestions`` is ``True``. Defaults to ``0``, 66 | meaning no limit. Any positive value truncates the suggestion limit. 67 | 68 | ``spelling_show_whole_line=True`` 69 | 70 | Boolean controlling whether the contents of the line containing each 71 | misspelled word is printed, for more context about the location of each 72 | word. Defaults to True. 73 | 74 | ``spelling_warning=False`` 75 | 76 | Boolean controlling whether a misspelling is emitted as a sphinx 77 | warning or as an info message. Defaults to False. 78 | 79 | ``spelling_verbose=True`` 80 | 81 | Choose whether or not the misspelled words should be displayed in 82 | the terminal. Defaults to True. 83 | 84 | Word Filters 85 | ============ 86 | 87 | Enable or disable the built-in filters to control which words are 88 | returned by the tokenizer to be checked. 89 | 90 | ``spelling_ignore_pypi_package_names=False`` 91 | 92 | Boolean controlling whether words that look like package names from 93 | PyPI are treated as spelled properly. When ``True``, the current 94 | list of package names is downloaded at the start of the build and 95 | used to extend the list of known words in the dictionary. Defaults 96 | to ``False``. 97 | 98 | ``spelling_ignore_wiki_words=True`` 99 | 100 | Boolean controlling whether words that follow the CamelCase 101 | conventions used for page names in :spelling:word:`wikis` should be treated as 102 | spelled properly. Defaults to ``True``. 103 | 104 | ``spelling_ignore_acronyms=True`` 105 | 106 | Boolean controlling treatment of words that appear in all capital 107 | letters, or all capital letters followed by a lower case ``s``. When 108 | ``True``, acronyms are assumed to be spelled properly. Defaults to 109 | ``True``. 110 | 111 | ``spelling_ignore_python_builtins=True`` 112 | 113 | Boolean controlling whether names built in to Python should be 114 | treated as spelled properly. Defaults to ``True``. 115 | 116 | ``spelling_ignore_importable_modules=True`` 117 | 118 | Boolean controlling whether words that are names of modules found on 119 | ``sys.path`` are treated as spelled properly. Defaults to ``True``. 120 | 121 | ``spelling_ignore_contributor_names=True`` 122 | 123 | Boolean controlling whether contributor names taken from the git 124 | history for the repository are considered as spelled correctly. 125 | 126 | ``spelling_filters=[]`` 127 | 128 | List of importable filter classes to be added to the tokenizer that 129 | produces words to be checked. For example, 130 | ``["enchant.tokenize.MentionFilter"]``. The classes should be 131 | derived from ``enchant.tokenize.Filter``. Refer to the `PyEnchant 132 | tutorial`_ for examples. 133 | 134 | Managing Lists of Correctly Spelled Words and Ignoring Words 135 | ============================================================ 136 | 137 | There are three ways to provide a list of known good words. The 138 | ``spelling_word_list_filename`` option (described above) specifies the 139 | name of a plain text file containing one word per line. All of the 140 | words in the file are assumed to be spelled correctly and may appear 141 | in any part of the document being processed. 142 | 143 | You can use multiple text files with words to be added to the dictionary, 144 | to do this all you need to do is use a list and include the name of your 145 | text files. 146 | 147 | For example:: 148 | 149 | spelling_word_list_filename = ['spelling_wordlist.txt', 'my_wordlist.txt'] 150 | 151 | The ``spelling:word-list`` directive can be used to create a list of 152 | words known to be spelled correctly within a single file. For 153 | example, if a document refers to a person or project by name, the name 154 | can be added to the list of known words for just that document. 155 | 156 | :: 157 | 158 | .. spelling:word-list:: 159 | 160 | Docutils 161 | Goodger 162 | 163 | The ``spelling:word`` role can be used to annotate individual words as 164 | being spelled correctly throughout a single document. 165 | 166 | 167 | :: 168 | 169 | This text refers to :spelling:word:`Goodger`. 170 | 171 | The ``spelling:ignore`` role can be used to ignore a single instance 172 | of a word. 173 | 174 | :: 175 | 176 | This text refers to :spelling:ignore:`docutils`. 177 | 178 | .. _PyEnchant: https://github.com/rfk/pyenchant 179 | 180 | Custom Word Filters 181 | =================== 182 | 183 | The PyEnchant tokenizer supports a "filtering" API for processing 184 | words from the input. Filters can alter the stream of words by adding, 185 | replacing, or dropping values. 186 | 187 | New filters should be derived from ``enchant.tokenize.Filter`` and 188 | implement either the ``_split()`` method (to add or replace words) or 189 | ``_skip()`` (to treat words as being spelled correctly). For example, 190 | this :class:`AcronymFilter` skips words that are all uppercase letters 191 | or all uppercase with a trailing lowercase "s". 192 | 193 | :: 194 | 195 | class AcronymFilter(Filter): 196 | """If a word looks like an acronym (all upper case letters), 197 | ignore it. 198 | """ 199 | 200 | def _skip(self, word): 201 | return (word.isupper() # all caps 202 | or 203 | # pluralized acronym ("URLs") 204 | (word[-1].lower() == 's' 205 | and 206 | word[:-1].isupper() 207 | ) 208 | ) 209 | 210 | To be used in a document, the custom filter needs to be installed 211 | somewhere that Sphinx can import it while processing the input 212 | files. The Sphinx project's ``conf.py`` then needs two changes. 213 | 214 | 1. Import the filter class. 215 | 2. Add the import string for the filter class to the 216 | ``spelling_filters`` configuration variable. 217 | 218 | :: 219 | 220 | spelling_filters = ['mymodule.MyFilter'] 221 | 222 | .. seealso:: 223 | 224 | * `Creating a Spelling Checker for reStructuredText Documents 225 | `_ 226 | * `PyEnchant tutorial`_ 227 | -------------------------------------------------------------------------------- /docs/source/developers.rst: -------------------------------------------------------------------------------- 1 | .. spelling:word-list:: 2 | 3 | sphinxcontrib 4 | reStructuredText 5 | 6 | ============ 7 | Developers 8 | ============ 9 | 10 | If you would like to contribute to sphinxcontrib.spelling directly, 11 | these instructions should help you get started. Patches, bug reports, 12 | and feature requests are all welcome through the `GitHub site 13 | `__. 14 | Contributions in the form of patches or pull requests are easier to 15 | integrate and will receive priority attention. 16 | 17 | Running tests 18 | ============= 19 | 20 | To run the tests, you need ``tox`` installed, then just run 21 | ``tox``. This should run the unit tests, the source code linter, and 22 | try to build the current documentation. 23 | 24 | Enchant C Library 25 | ----------------- 26 | 27 | You also need the C library from Enchant installed. On macOS, use 28 | `brew` to install the `enchant` package, then set 29 | `PYENCHANT_LIBRARY_PATH` to point to the `dylib` file included in the 30 | output of `brew list enchant`. 31 | 32 | .. code-block:: console 33 | 34 | $ brew list enchant | grep dylib 35 | /opt/homebrew/Cellar/enchant/2.6.4/lib/libenchant-2.dylib 36 | /opt/homebrew/Cellar/enchant/2.6.4/lib/libenchant-2.2.dylib 37 | 38 | $ export PYENCHANT_LIBRARY_PATH=/opt/homebrew/Cellar/enchant/2.6.4/lib/libenchant-2.2.dylib 39 | 40 | Coding style 41 | ============ 42 | 43 | Python imports are formatted and sorted using `isort 44 | `__. To format all files, run: 45 | 46 | .. code-block:: console 47 | 48 | $ tox -e style 49 | 50 | Building Documentation 51 | ====================== 52 | 53 | The documentation for sphinxcontrib.spelling is written in 54 | reStructuredText and converted to HTML using Sphinx. The build is 55 | driven by ``tox``. To build only the documentation, run ``tox -e 56 | docs``. 57 | 58 | Contributing 59 | ============ 60 | 61 | Please submit changes as pull requests using the `GitHub repository 62 | `__. 63 | 64 | In the pull request description, link to any issues closed by the 65 | changes using ``Fixes #NUM``, replacing ``NUM`` with the issue number. 66 | 67 | Release Notes 68 | ============= 69 | 70 | Please add a release note for each pull request to ``docs/history.rst``. 71 | -------------------------------------------------------------------------------- /docs/source/history.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Release History 3 | ================= 4 | 5 | .. spelling:word-list:: 6 | 7 | arg 8 | commandline 9 | config 10 | docstring 11 | emacs 12 | env 13 | Homebrew 14 | libenchant 15 | macOS 16 | namespace 17 | repo 18 | scm 19 | setuptools 20 | testrepository 21 | unicode 22 | unmaintained 23 | 24 | Next 25 | ==== 26 | 27 | - Modernize packaging using hatch and hatchling. 28 | 29 | Bug Fixes 30 | --------- 31 | 32 | - `#229 `__ Gracefully 33 | handle if git is not installed 34 | - `#227 `__ Use pypi.org's 35 | JSON API instead of XML-RPC. 36 | 37 | 7.7.0 38 | ===== 39 | 40 | New Features 41 | ------------ 42 | 43 | - `#199 `__ Add 44 | ``spelling:ignore`` role for marking inline text to not be 45 | checked. See :doc:`/customize` for more details. 46 | 47 | 7.6.2 48 | ===== 49 | 50 | Bug Fixes 51 | --------- 52 | 53 | - `#193 `__ 54 | Remove excessive debug printing in `:spelling:word:` and `spelling` builder 55 | implementations. 56 | 57 | 7.6.1 58 | ===== 59 | 60 | Bug Fixes 61 | --------- 62 | 63 | - `#188 `__ 64 | Fix `:spelling:word:` directives from being printed verbatim in 65 | output files. 66 | 67 | 7.6.0 68 | ===== 69 | 70 | Features 71 | -------- 72 | 73 | - Convert to use Sphinx domains. Add ``spelling:word-list`` 74 | directive. Have ``spelling`` directive report that it is deprecated. 75 | - Add ``spelling:word`` role for marking inline text as spelled 76 | correctly. 77 | 78 | 7.5.1 79 | ===== 80 | 81 | Bug Fixes 82 | --------- 83 | 84 | - `#180 `__ 85 | Suppress `SystemExit` errors in `ImportableModuleFilter` caused by 86 | importing modules that run code on import and exit when that code 87 | sees an error. Bug report and reproducer provided by Trevor Gross. 88 | 89 | 7.5.0 90 | ===== 91 | 92 | Features 93 | -------- 94 | 95 | - `#151 `__ 96 | Added configuration option to limit the number of suggestions 97 | output. See :doc:`/customize` for more details. Idea contributed by 98 | Trevor Gross. 99 | - `#169 `__ 100 | Adds the ability to pass in multiple wordlists via the sphinx 101 | command line as ``-D spelling_word_list_filename=file1,file2``. 102 | 103 | Bug Fixes 104 | --------- 105 | 106 | - `#36 `__ 107 | Include captions of figures in the set of nodes for which the text 108 | is checked. 109 | 110 | 7.4.1 111 | ===== 112 | 113 | - `#160 `__ 114 | Fixed issue with the builder crashing when reporting a misspelled word 115 | in a python docstring. 116 | 117 | 7.4.0 118 | ===== 119 | 120 | - Fix a problem that occurred when the extra word list is empty and an 121 | IndexError is thrown. Prevent the error by checking the contents of 122 | the file before using the list. 123 | - `#153 `__ 124 | Ensure the correct relative filename is reported as the location of 125 | a misspelled word when the word is in an included file. Log the 126 | location ourselves instead of letting the logging system compute it 127 | for consistency until `the fix 128 | `__ is merged into 129 | Sphinx. 130 | - Change default env list for local tox runs to only include the 131 | current python version, as defined by the installation of tox. 132 | - Tell tox to pass `PYENCHANT_LIBRARY_PATH` through to commands. On 133 | macOS it can be a little tricky to set up libenchant if your default 134 | python does not match the one used by Homebrew for the 135 | library. Setting the variable to point to the library fixes that, 136 | but we don't want to set it in this file for everyone so use 137 | `passenv` to tell tox to pass the setting through when running the 138 | commands for each env. 139 | - `#159 `__ 140 | Report using the line number of the misspelled word instead of using 141 | the first line of the node, in both the log and `.spelling` output 142 | file. 143 | 144 | 7.3.3 145 | ===== 146 | 147 | Bug Fixes 148 | --------- 149 | 150 | - `#149 `__ Fixes 151 | to support testing when building RPMs. Switch to PEP 420 native 152 | namespace and skip contributors test when not in a git repo. 153 | - `#150 `__ Minor 154 | code cleanup primarily around string interpolation. 155 | 156 | 7.3.2 157 | ===== 158 | 159 | Bug Fixes 160 | --------- 161 | 162 | - `#143 `__ Treat 163 | ``__main__`` as a special module name that cannot be imported. If 164 | the test suite is invoked by running ``python -m pytest`` instead of 165 | ``pytest`` then there will be no ``__main__`` and find_spec() will 166 | fail, so this change makes the tests work in both modes. 167 | - `#144 `__ Fix 168 | python filename handling in ``ImportableModuleFilter``. If the word 169 | looks like a python module filename, strip the extension to avoid 170 | the side-effect of actually importing the module. This prevents, for 171 | example, ``'setup.py'`` triggering an import of the ``setup`` module 172 | during a doc build, which makes it look like Sphinx is complaining 173 | about a commandline argument. 174 | 175 | 7.3.1 176 | ===== 177 | 178 | Bug Fixes 179 | --------- 180 | 181 | - `#137 `__ 182 | replace the use of deprecated ``imp`` in ``ImportableModuleFilter`` 183 | with ``importlib`` 184 | 185 | 7.3.0 186 | ===== 187 | 188 | New Features 189 | ------------ 190 | 191 | - `#131 `__ 192 | included a documentation update to fix a broken link. 193 | 194 | - `#130 `__ tested support 195 | for Python 3.10, and added the trove classifier. 196 | 197 | - `#129 `__ improved the 198 | speed of the ``ImportableModuleFilter``. 199 | 200 | - `#128 `__ fixed 201 | some issues with the packaging configuration. 202 | 203 | 7.2.0 204 | ===== 205 | 206 | New Features 207 | ------------ 208 | 209 | - `#123 `__ adds 210 | the ``spelling_verbose`` configuration option for controlling 211 | whether misspelled words are printed to the console as well as the 212 | output log files. See :ref:`output-options` for details. 213 | 214 | 7.1.0 215 | ===== 216 | 217 | New Features 218 | ------------ 219 | 220 | - `#116 `__ adds 221 | a config option `spelling_warning` that makes individual messages 222 | about misspellings warnings. The same change also updates the 223 | formatting of the message to make it easier for IDEs to parse, 224 | allowing the editor to navigate to the location of the misspelled 225 | word. See :ref:`output-options` for details. Contributed by Robert 226 | Cohn. 227 | 228 | 7.0.1 229 | ===== 230 | 231 | Bug Fixes 232 | --------- 233 | 234 | - `#105 `__ 235 | reverts a change that switched from `imp` to `importlib`. Using 236 | `importlib.find_spec()` 237 | is not safe at runtime as it can import modules which will cause 238 | side effects within environments. 239 | 240 | 7.0.0 241 | ===== 242 | 243 | This major release drops support for Python 3.5. This version is not 244 | maintained anymore. 245 | 246 | Bug Fixes 247 | --------- 248 | 249 | - Fixes an issue with ellipsis incorrectly being interpreted as 250 | relative imports and triggering a `ValueError` in the 251 | `ImportableModuleFilter`. See `#96 252 | `__ for 253 | details. 254 | 255 | 6.0.0 256 | ===== 257 | 258 | With this release, sphinxcontrib-spelling moves from beta to 259 | stable. It also updates the use of Python 3, including packaging 260 | metadata, code style, and test configuration. 261 | 262 | New Features 263 | ------------ 264 | 265 | - Add packaging metadata declaring the project stable. 266 | - Add packaging metadata declaring support for Python 3 only. 267 | - Add packaging metadata indicating that this is a sphinx extension. 268 | 269 | Bug Fixes 270 | --------- 271 | 272 | - Replace use of deprecated `imp` module with `importlib`. 273 | - Update use of `pyenchant.get_tokenizer()` to pass filters argument 274 | as a keyword and avoid a runtime warning message. 275 | - Remove unused test dependency on `fixtures`. 276 | - Use `pyupgrade` to modernize the source code. 277 | 278 | 5.4.0 279 | ===== 280 | 281 | New Features 282 | ------------ 283 | 284 | - Added a new filter 285 | (``sphinxcontrib.spelling.filters.ContributorFilter``) that treats 286 | contributor names extracted from the git history as spelled 287 | correctly, making it easier to refer to the names in 288 | acknowledgments . Includes a new configuration option, 289 | ``spelling_ignore_contributor_names`` to enable it. 290 | 291 | 5.3.0 292 | ===== 293 | 294 | New Features 295 | ------------ 296 | 297 | - Add a configuration option ``spelling_exclude_patterns`` to manage 298 | skipping spell checking for some input files. The option uses a 299 | list of glob-style patterns that are matched against the source 300 | file names relative to the source directory. See :doc:`/customize` 301 | for more details. Contributed by sdelliot. 302 | 303 | 5.2.2 304 | ===== 305 | 306 | Bug Fixes 307 | --------- 308 | 309 | - Updated to only create ``.spelling`` output files for inputs that 310 | generate spelling warnings. Fixes #63. 311 | 312 | 5.2.0 313 | ===== 314 | 315 | New Features 316 | ------------ 317 | 318 | - The builder is now registered using an entry point, so that if the 319 | ``spelling`` directive is not used in a project 320 | ``sphinxcontrib.spelling`` does not need to be included explicitly 321 | in the ``extensions`` list in ``conf.py`` in order to use it with 322 | the project on the command line. 323 | 324 | - PyEnchant is an optional dependency. If it is not installed, the 325 | spell checker will not work, but the extension can still be 326 | initialized. This allows projects that use spell checking to 327 | publish their documentation to ``readthedocs.org``, where it is 328 | not possible to install PyEnchant. 329 | 330 | - Restore support for parallel builds. Words that do not appear in 331 | any configured dictionary are written to a file named based on the 332 | input file, with the ``.rst`` extension replaced with 333 | ``.spelling``. 334 | 335 | 5.1.2 336 | ===== 337 | 338 | - Mark as unsafe for parallel builds (contributed by Jared Dillard) 339 | - Add -W arg to sphinx-build in docs so warnings cause error 340 | (contributed by Elsa Gonsiorowski, PhD) 341 | 342 | 5.1.0 343 | ===== 344 | 345 | - Add an option to show the line containing a misspelling for context 346 | (contributed by Huon Wilson) 347 | 348 | 5.0.0 349 | ===== 350 | 351 | - Drop Python 2.7 support. (contributed by Johannes Raggam) 352 | - `allow customizing with classes using import strings 353 | `__ 354 | - pyenchant is now maintained (contributed by Adam Johnson 355 | 356 | 4.3.0 357 | ===== 358 | 359 | - Logging: use warning() instead of its deprecated alias (contributed 360 | by Sergey Kolosov) 361 | - Support additional contractions (contributed by David Baumgold) 362 | - require Sphinx >= 2.0.0 363 | - declare support for Python 3.6 364 | 365 | 4.2.1 366 | ===== 367 | 368 | - fix remaining logging issue (contributed by Timotheus Kampik) 369 | - Remove usage of deprecated logging API (contributed by Tim Graham) 370 | 371 | 4.2.0 372 | ===== 373 | 374 | - Fix a bug with empty word lists (contributed by FabioRosado) 375 | - Update dependency management to use setuptools extras 376 | - Document how to create multiple wordfiles (contributed by 377 | FabioRosado) 378 | - Note that PyEnchant is unmaintained and fix links (contributed by 379 | Marti Raudsepp) 380 | - Don’t use mutable default argument (contributed by Daniele Tricoli) 381 | 382 | 4.1.0 383 | ===== 384 | 385 | - Make it possible to provide several wordlists (contributed by Tobias 386 | Olausson) 387 | - Update developer documentation (contributed by Tobias Olausson) 388 | - Update home page link (contributed by Devin Sevilla) 389 | 390 | 4.0.1 391 | ===== 392 | 393 | - use the right method to emit warnings 394 | - disable smart quotes so that we can recognize 395 | contractions/possessives correctly (contributed by Alex Gaynor) 396 | 397 | 4.0.0 398 | ===== 399 | 400 | - Don’t fail by default (contributed by Stephen Finucane) 401 | - Mark the extension as safe for parallel reading (contributed by Alex 402 | Gaynor) 403 | - be more verbose about configuration options 404 | - switch to testrepository for running tests 405 | - update Python 3.3 to 3.5 406 | 407 | 2.3.0 408 | ===== 409 | 410 | - make it possible to specify tokenizer #7 (contributed by Timotheus 411 | Kampik) 412 | 413 | 2.2.0 414 | ===== 415 | 416 | - Use ``https`` with ``pypi.python.org`` package name checker 417 | (contributed by John-Scott Atlakson) 418 | - Removed unnecessary shebang lines from non-script files (contributed 419 | by Avram Lubkin) 420 | - Re-enable the PyEnchant dependency (contributed by Julian Berman) 421 | 422 | 2.1.2 423 | ===== 424 | 425 | - Fixed issue with six under Python 3.4 426 | 427 | 2.1.1 428 | ===== 429 | 430 | - Use ``str.isupper()`` instead of ad-hoc method 431 | - fix syntax for tags directive 432 | - Removed no more used CHANGES file 433 | 434 | 2.1 435 | === 436 | 437 | - Fix unicode error in ``PythonBuiltinsFilter``. 438 | - Make error output useful in emacs compiler mode 439 | - Only show the words being added to a local dictionary if debugging 440 | is enabled. 441 | 442 | 443 | 2.0 444 | === 445 | 446 | - Add Python 3.3 support. 447 | - Add PyPy support. 448 | - Use pbr for packaging. 449 | - Update tox config to work with forked version of PyEnchant until 450 | changes are accepted upstream. 451 | 452 | 1.4 453 | === 454 | 455 | - Fixed detection of builtins under PyPy, contributed by Hong Minhee 456 | (https://bitbucket.org/dahlia). 457 | 458 | 1.3 459 | === 460 | 461 | - Handle text nodes without parents. (#19) 462 | - Include the input document name in the console output. 463 | - Use the Sphinx wrapper for registering a directive. 464 | 465 | 1.2 466 | === 467 | 468 | - Add the document name to the messages showing the contents of a 469 | local dictionary created by the ``spelling`` directive. 470 | - Add title nodes to the list of node types checked for 471 | spelling. Resolves issue #17. 472 | - Add test/test_wordlist.txt to the manifest so it is included in 473 | the source distribution and the tests will pass. Resolves issue 474 | #17. 475 | - Documentation patch from Hank Gay. 476 | 477 | 1.1.1 478 | ===== 479 | 480 | - Fix initialization so the per-document filters work even if no 481 | ``spelling`` directive is used. 482 | 483 | 1.1 484 | === 485 | 486 | - Add an option treat the names of packages on PyPI as spelled 487 | properly. 488 | - Add an option to treat CamelCase names as spelled properly. 489 | - Add an option to treat acronyms as spelled properly. 490 | - Add an option to treat Python built-ins as spelled properly. 491 | - Add an option to treat names that can be found as modules as 492 | spelled properly. 493 | - Add an option to let the user provide a list of other filter 494 | classes for the tokenizer. 495 | - Add ``spelling`` directive for passing local configuration 496 | settings to the spelling checker. This version allows setting a 497 | list of words known to be spelled correctly. 498 | 499 | 1.0 500 | === 501 | 502 | - Re-implement using just a Builder, without a separate visitor 503 | class. 504 | - Show the file and line number of any words not appearing in the 505 | dictionary, instead of the section title. 506 | - Log the file, line, and unknown words as the documents are 507 | processed. 508 | 509 | 0.2 510 | === 511 | 512 | - Warn but otherwise ignore unknown node types. 513 | 514 | 0.1 515 | === 516 | 517 | - First public release. 518 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. spelling:word-list:: 2 | 3 | sphinxcontrib 4 | 5 | .. sphinxcontrib.spelling documentation master file, created by 6 | sphinx-quickstart on Sun Apr 17 15:33:23 2011. 7 | You can adapt this file completely to your liking, but it should at least 8 | contain the root `toctree` directive. 9 | 10 | ======================== 11 | sphinxcontrib.spelling 12 | ======================== 13 | 14 | ``sphinxcontrib.spelling`` is a spelling checker for Sphinx_. It uses 15 | PyEnchant_ to produce a report showing misspelled words. 16 | 17 | Features 18 | ======== 19 | 20 | 1. Supports multiple source languages using the standard enchant 21 | dictionaries. 22 | 2. Supports project-specific dictionaries for localized jargon and 23 | other terminology that may not appear in the global dictionaries. 24 | 3. Suggests alternatives to words not found in the dictionary, when 25 | possible. 26 | 27 | Details 28 | ======= 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | install 34 | customize 35 | run 36 | developers 37 | history 38 | 39 | 40 | .. _PyEnchant: https://github.com/rfk/pyenchant 41 | 42 | .. _Sphinx: https://www.sphinx-doc.org/ 43 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | .. spelling:word-list:: 2 | 3 | sphinxcontrib 4 | 5 | ============== 6 | Installation 7 | ============== 8 | 9 | 1. Install the extension with pip: ``pip install sphinxcontrib-spelling`` 10 | 11 | 2. Add ``'sphinxcontrib.spelling'`` to the ``extensions`` list in 12 | ``conf.py``. 13 | 14 | .. code-block:: python 15 | 16 | extensions = [ 'sphinxcontrib.spelling' ] 17 | 18 | 3. Then pass ``"spelling"`` as the builder argument to ``sphinx-build``. 19 | 20 | .. code-block:: shell-session 21 | 22 | $ sphinx-build -b spelling docs/source docs/build 23 | -------------------------------------------------------------------------------- /docs/source/run.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Running 3 | ========= 4 | 5 | To process a document with the spell checker, use ``sphinx-build`` and 6 | specify ``spelling`` as the builder name using the ``-b`` option. The 7 | output includes the headings from the document and any misspelled 8 | words. If suggestions are enabled, they are shown on the same line as 9 | the misspelling. A log of the words in each input file not found in 10 | the dictionary is saved to the file ``.spelling`` under the 11 | build directory. 12 | 13 | .. code-block:: console 14 | 15 | $ tox -e spelling -r 16 | spelling create: .../sphinxcontrib-spelling/.tox/spelling 17 | spelling installdeps: .[docs] 18 | spelling develop-inst: .../sphinxcontrib-spelling 19 | spelling installed: -f /Users/dhellmann/.pip/wheelhouse,alabaster==0.7.12,Babel==2.8.0,certifi==2020.6.20,chardet==3.0.4,docutils==0.16,dulwich==0.20.5,idna==2.10,imagesize==1.2.0,importlib-metadata==1.7.0,Jinja2==2.11.2,MarkupSafe==1.1.1,packaging==20.4,pbr==5.4.5,pyenchant==3.1.1,Pygments==2.6.1,pyparsing==2.4.7,pytz==2020.1,PyYAML==5.3.1,reno==3.1.0,requests==2.24.0,six==1.15.0,snowballstemmer==2.0.0,Sphinx==3.2.0,sphinxcontrib-applehelp==1.0.2,sphinxcontrib-devhelp==1.0.2,sphinxcontrib-htmlhelp==1.0.3,sphinxcontrib-jsmath==1.0.1,sphinxcontrib-qthelp==1.0.3,sphinxcontrib-serializinghtml==1.1.4,-e git+git@github.com:sphinx-contrib/spelling.git@b0b3e2a8c935701cfcbbc76ea1aa501a03ae4e22#egg=sphinxcontrib_spelling,urllib3==1.25.10,zipp==3.1.0 20 | spelling run-test-pre: PYTHONHASHSEED='1632297322' 21 | spelling run-test: commands[0] | sphinx-build -W -j auto -b spelling -d docs/build/doctrees docs/source docs/build/spelling 22 | Running Sphinx v3.2.0 23 | Initializing Spelling Checker 5.2.1.dev2 24 | Ignoring wiki words 25 | Ignoring acronyms 26 | Adding package names from PyPI to local dictionary… 27 | Ignoring Python builtins 28 | Ignoring importable module names 29 | Adding contents of .../sphinxcontrib-spelling/docs/source/spelling_wordlist.txt to custom word list 30 | Adding contents of .../sphinxcontrib-spelling/docs/source/spelling_people.txt to custom word list 31 | Looking for custom word list in /var/folders/5q/8gk0wq888xlggz008k8dr7180000hg/T/tmphsetrn0s/spelling_wordlist.txt 32 | building [mo]: targets for 0 po files that are out of date 33 | building [spelling]: all documents 34 | updating environment: [new config] 6 added, 0 changed, 0 removed 35 | reading sources... [ 16%] customize 36 | reading sources... [ 33%] developers 37 | reading sources... [ 50%] history 38 | reading sources... [ 66%] index 39 | reading sources... [ 83%] install 40 | reading sources... [100%] run 41 | 42 | waiting for workers... 43 | scanning .../sphinxcontrib-spelling/releasenotes/notes for current branch release notes 44 | got versions ['5.2.0'] 45 | looking for now-outdated files... none found 46 | pickling environment... done 47 | checking consistency... done 48 | preparing documents... done 49 | writing output... [ 16%] customize 50 | Extending local dictionary for customize 51 | writing output... [ 33%] developers 52 | Extending local dictionary for developers 53 | writing output... [ 50%] history 54 | Extending local dictionary for history 55 | writing output... [ 66%] index 56 | Extending local dictionary for index 57 | index.rst:17:speel:["Peel", "peel", "spell", "spiel", "Speer", "speed", "steel", "sepal", "spill", "spoil", "spool", "speller", "Pele", "supple", "Perl", "spew", "spree", "suppl", "repel", "spells", "spiels", "spleen", "peal", "seal", "seep", "sell", "Aspell", "Ispell", "sleep", "spell's", "spiel's"]:I can't speel. 58 | Writing .../sphinxcontrib-spelling/docs/build/spelling/index.spelling 59 | writing output... [ 83%] install 60 | Extending local dictionary for install 61 | writing output... [100%] run 62 | 63 | 64 | Warning, treated as error: 65 | Found 1 misspelled words 66 | ERROR: InvocationError for command .../sphinxcontrib-spelling/.tox/spelling/bin/sphinx-build -W -j auto -b spelling -d docs/build/doctrees docs/source docs/build/spelling (exited with code 2) 67 | __________________________________________________ summary ___________________________________________________ 68 | ERROR: spelling: commands failed 69 | -------------------------------------------------------------------------------- /docs/source/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | builtins 2 | hoc 3 | linter 4 | linters 5 | macOS 6 | pypi 7 | reStructuredText 8 | sphinxcontrib 9 | tokenizer 10 | txt 11 | wikis 12 | wordfiles 13 | wordlist 14 | wordlists 15 | -------------------------------------------------------------------------------- /integration_tests/build_django.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | """Try to build the Django documentation.""" 4 | 5 | import argparse 6 | import os 7 | import subprocess 8 | import sys 9 | import tempfile 10 | 11 | 12 | def doit(*cmd, description="", cwd=None): 13 | print(f"\n[{description}]\nrunning: {' '.join(cmd)}") 14 | completed = subprocess.run(cmd, cwd=cwd) 15 | try: 16 | completed.check_returncode() 17 | except subprocess.CalledProcessError as err: 18 | raise RuntimeError(f"command failed {description}") from err 19 | 20 | 21 | def try_build(workdir, srcdir, django_repo): 22 | print(f"working in {workdir}") 23 | doit( 24 | "git", 25 | "clone", 26 | "--depth", 27 | "1", 28 | django_repo, 29 | "django", 30 | description="clone django", 31 | cwd=workdir, 32 | ) 33 | djangodir = workdir + "/django" 34 | doit( 35 | "tox", 36 | "-e", 37 | "docs", 38 | "--notest", 39 | description="build django docs virtualenv", 40 | cwd=djangodir, 41 | ) 42 | doit( 43 | ".tox/docs/bin/pip", 44 | "uninstall", 45 | "-y", 46 | "sphinxcontrib-spelling", 47 | description="uninstall packaged sphinxcontrib-spelling", 48 | cwd=djangodir, 49 | ) 50 | doit( 51 | ".tox/docs/bin/pip", 52 | "install", 53 | srcdir, 54 | description="install sphinxcontrib-spelling from source", 55 | cwd=djangodir, 56 | ) 57 | doit( 58 | "tox", 59 | "-e", 60 | "docs", 61 | description="build django docs", 62 | cwd=djangodir, 63 | ) 64 | 65 | 66 | def main(args=sys.argv[1:]): 67 | parser = argparse.ArgumentParser() 68 | parser.add_argument( 69 | "--debug", action="store_true", default=False, help="show full tracebacks" 70 | ) 71 | parser.add_argument("--src", help="source directory") 72 | parser.add_argument("--django-repo", default="https://github.com/django/django.git") 73 | parsed = parser.parse_args(args) 74 | 75 | srcdir = parsed.src 76 | if not srcdir: 77 | srcdir = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0]))) 78 | 79 | try: 80 | with tempfile.TemporaryDirectory() as dirname: 81 | try_build(dirname, srcdir, parsed.django_repo) 82 | except Exception as err: 83 | if parsed.debug: 84 | raise 85 | print(f"ERROR: {err}") 86 | return 1 87 | return 0 88 | 89 | 90 | if __name__ == "__main__": 91 | sys.exit(main()) 92 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-vcs"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "sphinxcontrib-spelling" 7 | readme = "README.rst" 8 | authors = [{ name = "Doug Hellmann", email = "doug@doughellmann.com" }] 9 | description = "Sphinx spelling extension" 10 | dynamic = ["version"] 11 | 12 | classifiers = [ 13 | "Development Status :: 5 - Production/Stable", 14 | "Environment :: Console", 15 | "Environment :: Web Environment", 16 | "Framework :: Sphinx :: Extension", 17 | "Intended Audience :: Developers", 18 | "License :: OSI Approved :: BSD License", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3 :: Only", 23 | "Programming Language :: Python :: 3.10", 24 | "Programming Language :: Python :: 3.11", 25 | "Programming Language :: Python :: 3.12", 26 | "Programming Language :: Python :: Implementation :: CPython", 27 | "Topic :: Documentation", 28 | "Topic :: Utilities", 29 | ] 30 | 31 | requires-python = ">=3.10" 32 | 33 | dependencies = ["PyEnchant>=3.1.1", "Sphinx>=3.0.0", "requests>=2.32.3"] 34 | 35 | [project.optional-dependencies] 36 | test = ["pytest", "pytest-cov", "coverage!=4.4,>=4.0"] 37 | 38 | [project.entry-points."sphinx.builders"] 39 | spelling = "sphinxcontrib.spelling" 40 | 41 | [project.urls] 42 | homepage = "https://sphinxcontrib-spelling.readthedocs.io/en/latest/" 43 | repository = "https://github.com/sphinx-contrib/spelling" 44 | 45 | [tool.hatch.version] 46 | source = "vcs" 47 | 48 | [tool.hatch.build.hooks.vcs] 49 | version-file = "sphinxcontrib/spelling/version.py" 50 | 51 | [tool.hatch.build.targets.sdist] 52 | exclude = [".github", "cover", ".mergify.yml", ".gitignore"] 53 | [tool.hatch.build.targets.wheel] 54 | only-include = ["sphinxcontrib"] 55 | 56 | [tool.hatch.envs.docs] 57 | dependencies = ["sphinx"] 58 | [tool.hatch.envs.docs.env] 59 | ENABLE_SPELLING = "1" 60 | [tool.hatch.envs.docs.scripts] 61 | build = [ 62 | "sphinx-build -W -j auto -b html -d docs/build/doctrees docs/source docs/build/html", 63 | "sphinx-build -W -j auto -b linkcheck -d docs/build/doctrees docs/source docs/build/linkcheck", 64 | "sphinx-build -W -j auto -b spelling -d docs/build/doctrees docs/source docs/build/spelling", 65 | ] 66 | check = "sphinx-build -W -j auto -b spelling -d docs/build/doctrees docs/source docs/build/spelling" 67 | 68 | [tool.hatch.envs.test] 69 | dependencies = [ 70 | "pytest", 71 | "pytest-cov", 72 | "coverage!=4.4,>=4.0", 73 | "ruff", 74 | "twine", 75 | "check-python-versions", 76 | ] 77 | [tool.hatch.envs.test.scripts] 78 | test = "python -m pytest --cov=sphinxcontrib.spelling --cov-report term-missing --log-level DEBUG tests" 79 | lint = [ 80 | "ruff check sphinxcontrib integration_tests tests", 81 | "ruff format --check sphinxcontrib integration_tests tests", 82 | ] 83 | lint-fix = ["ruff format sphinxcontrib integration_tests tests"] 84 | pkglint = [ 85 | "hatch build", 86 | "twine check dist/*.tar.gz dist/*.whl", 87 | "check-python-versions --only pyproject.toml,.github/workflows/test.yml", 88 | ] 89 | 90 | [tool.hatch.envs.integration] 91 | dependencies = ["tox"] 92 | [tool.hatch.envs.integration.scripts] 93 | django = "./integration_tests/build_django.py" 94 | 95 | [tool.ruff] 96 | exclude = ["sphinxcontrib/spelling/version.py"] 97 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | ruff 2 | -------------------------------------------------------------------------------- /show-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git_project=$(git remote get-url origin | cut -f2 -d: | sed 's/.git//') 4 | pr_url_base="https://github.com/${git_project}/pull/" 5 | 6 | git log --merges --pretty="format:- %s %b" $(git describe --abbrev=0).. \ 7 | | sed -E \ 8 | -e 's#Merge pull request ##g' \ 9 | -e 's# from [^[:space:]]+##' \ 10 | -e 's|#([[:digit:]]+)|`#\1 <'${pr_url_base}'\1>`__|g' 11 | echo 12 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | # For Python 3.8 and later 3 | import importlib.metadata as importlib_metadata 4 | except ImportError: 5 | # For everyone else 6 | import importlib_metadata 7 | 8 | from sphinx.util import logging 9 | 10 | from . import asset, builder, directive, domain 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def setup(app): 16 | version = importlib_metadata.version("sphinxcontrib-spelling") 17 | logger.info("Initializing Spelling Checker %s", version) 18 | app.add_builder(builder.SpellingBuilder) 19 | # Register the 'spelling' directive for setting parameters within 20 | # a document 21 | app.add_directive("spelling", directive.LegacySpellingDirective) 22 | app.add_domain(domain.SpellingDomain) 23 | # Register an environment collector to merge data gathered by the 24 | # directive in parallel builds 25 | app.add_env_collector(asset.SpellingCollector) 26 | # Report guesses about correct spelling 27 | app.add_config_value("spelling_show_suggestions", False, "env") 28 | # Limit the number of suggestions output 29 | app.add_config_value("spelling_suggestion_limit", 0, "env") 30 | # Report the whole line that has the error 31 | app.add_config_value("spelling_show_whole_line", True, "env") 32 | # Emit misspelling as a sphinx warning instead of info message 33 | app.add_config_value("spelling_warning", False, "env") 34 | # Set the language for the text 35 | app.add_config_value("spelling_lang", "en_US", "env") 36 | # Set the language for the tokenizer 37 | app.add_config_value("tokenizer_lang", "en_US", "env") 38 | # Set a user-provided list of words known to be spelled properly 39 | app.add_config_value("spelling_word_list_filename", None, "env") 40 | # Assume anything that looks like a PyPI package name is spelled properly 41 | app.add_config_value("spelling_ignore_pypi_package_names", False, "env") 42 | # Assume words that look like wiki page names are spelled properly 43 | app.add_config_value("spelling_ignore_wiki_words", True, "env") 44 | # Assume words that are all caps, or all caps with trailing s, are 45 | # spelled properly 46 | app.add_config_value("spelling_ignore_acronyms", True, "env") 47 | # Assume words that are part of __builtins__ are spelled properly 48 | app.add_config_value("spelling_ignore_python_builtins", True, "env") 49 | # Assume words that look like the names of importable modules are 50 | # spelled properly 51 | app.add_config_value("spelling_ignore_importable_modules", True, "env") 52 | # Treat contributor names from git history as spelled correctly 53 | app.add_config_value("spelling_ignore_contributor_names", True, "env") 54 | # Add any user-defined filter classes 55 | app.add_config_value("spelling_filters", [], "env") 56 | # Set a user-provided list of files to ignore 57 | app.add_config_value("spelling_exclude_patterns", [], "env") 58 | # Choose whether or not the misspelled output should be displayed 59 | # in the terminal 60 | app.add_config_value("spelling_verbose", True, "env") 61 | return { 62 | "parallel_read_safe": True, 63 | "parallel_write_safe": True, 64 | "version": version, 65 | } 66 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/asset.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2020 Doug Hellmann. All rights reserved. 3 | # 4 | """Asset collector for additional spelling terms.""" 5 | 6 | import collections 7 | import contextlib 8 | 9 | from sphinx.environment.collectors import EnvironmentCollector 10 | from sphinx.util import logging 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class SpellingCollector(EnvironmentCollector): 16 | def clear_doc(self, app, env, docname) -> None: 17 | with contextlib.suppress(AttributeError, KeyError): 18 | del env.spelling_document_words[docname] 19 | 20 | def merge_other(self, app, env, docnames, other): 21 | try: 22 | other_words = other.spelling_document_words 23 | except AttributeError: 24 | other_words = {} 25 | 26 | if not hasattr(env, "spelling_document_words"): 27 | env.spelling_document_words = collections.defaultdict(list) 28 | env.spelling_document_words.update(other_words) 29 | 30 | def process_doc(self, app, doctree): 31 | pass 32 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/builder.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Spelling checker extension for Sphinx.""" 5 | 6 | import collections 7 | import importlib 8 | import os 9 | import tempfile 10 | 11 | import docutils.nodes 12 | import docutils.utils 13 | from sphinx.builders import Builder 14 | from sphinx.util import logging, osutil 15 | from sphinx.util.console import red 16 | from sphinx.util.matching import Matcher 17 | from sphinx.util.osutil import ensuredir 18 | 19 | try: 20 | from enchant.tokenize import EmailFilter, WikiWordFilter 21 | except ImportError as imp_exc: 22 | enchant_import_error = imp_exc 23 | else: 24 | enchant_import_error = None 25 | 26 | from . import checker, filters 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | # TODO - Words with multiple uppercase letters treated as classes and ignored 31 | 32 | 33 | class SpellingBuilder(Builder): 34 | """ 35 | Spell checks a document 36 | """ 37 | 38 | name = "spelling" 39 | 40 | def init(self): 41 | if enchant_import_error is not None: 42 | raise RuntimeError( 43 | "Cannot initialize spelling builder without PyEnchant installed" 44 | ) from enchant_import_error 45 | self.misspelling_count = 0 46 | 47 | self.env.settings["smart_quotes"] = False 48 | # Initialize the per-document filters 49 | if not hasattr(self.env, "spelling_document_words"): 50 | self.env.spelling_document_words = collections.defaultdict(list) 51 | 52 | # Initialize the global filters 53 | f = [ 54 | filters.ContractionFilter, 55 | EmailFilter, 56 | ] 57 | if self.config.spelling_ignore_wiki_words: 58 | logger.info("Ignoring wiki words") 59 | f.append(WikiWordFilter) 60 | if self.config.spelling_ignore_acronyms: 61 | logger.info("Ignoring acronyms") 62 | f.append(filters.AcronymFilter) 63 | if self.config.spelling_ignore_pypi_package_names: 64 | logger.info("Adding package names from PyPI to local dictionary…") 65 | f.append(filters.PyPIFilterFactory()) 66 | if self.config.spelling_ignore_python_builtins: 67 | logger.info("Ignoring Python builtins") 68 | f.append(filters.PythonBuiltinsFilter) 69 | if self.config.spelling_ignore_importable_modules: 70 | logger.info("Ignoring importable module names") 71 | f.append(filters.ImportableModuleFilter) 72 | if self.config.spelling_ignore_contributor_names: 73 | logger.info("Ignoring contributor names") 74 | f.append(filters.ContributorFilter) 75 | f.extend(self._load_filter_classes(self.config.spelling_filters)) 76 | 77 | if not os.path.isdir(self.outdir): 78 | os.mkdir(self.outdir) 79 | 80 | word_list = self.get_wordlist_filename() 81 | logger.info("Looking for custom word list in %s", word_list) 82 | 83 | self.checker = checker.SpellingChecker( 84 | lang=self.config.spelling_lang, 85 | tokenizer_lang=self.config.tokenizer_lang, 86 | suggest=self.config.spelling_show_suggestions, 87 | word_list_filename=word_list, 88 | filters=f, 89 | context_line=self.config.spelling_show_whole_line, 90 | ) 91 | 92 | def _load_filter_classes(self, filters): 93 | # Filters may be expressed in the configuration file using 94 | # names, so look through them and import the referenced class 95 | # and use that in the checker. 96 | for filter_ in filters: 97 | if not isinstance(filter_, str): 98 | yield filter_ 99 | continue 100 | module_name, _, class_name = filter_.rpartition(".") 101 | mod = importlib.import_module(module_name) 102 | yield getattr(mod, class_name) 103 | 104 | def get_configured_wordlist_filenames(self): 105 | "Returns the configured wordlist filenames." 106 | word_list = self.config.spelling_word_list_filename 107 | if word_list is None: 108 | word_list = ["spelling_wordlist.txt"] 109 | 110 | if isinstance(word_list, str): 111 | # Wordlist is a string. Split on comma in case it came 112 | # from the command line, via -D, and has multiple values. 113 | word_list = word_list.split(",") 114 | 115 | return [os.path.join(self.srcdir, p) for p in word_list] 116 | 117 | def get_wordlist_filename(self): 118 | "Returns the filename of the wordlist to use when checking content." 119 | filenames = self.get_configured_wordlist_filenames() 120 | if len(filenames) == 1: 121 | return filenames[0] 122 | # In case the user has multiple word lists, we combine them 123 | # into one large list that we pass on to the checker. 124 | return self._build_combined_wordlist() 125 | 126 | def _build_combined_wordlist(self): 127 | # If we have a list, the combined list is the first list plus all words 128 | # from the other lists. Otherwise, word_list is assumed to just be a 129 | # string. 130 | temp_dir = tempfile.mkdtemp() 131 | combined_word_list = os.path.join(temp_dir, "spelling_wordlist.txt") 132 | 133 | with open(combined_word_list, "w", encoding="UTF-8") as outfile: 134 | for word_file in self.get_configured_wordlist_filenames(): 135 | # Paths are relative 136 | long_word_file = os.path.join(self.srcdir, word_file) 137 | logger.info("Adding contents of %s to custom word list", long_word_file) 138 | with open(long_word_file, encoding="UTF-8") as infile: 139 | infile_contents = infile.readlines() 140 | outfile.writelines(infile_contents) 141 | 142 | # Check for newline, and add one if not present 143 | if infile_contents and not infile_contents[-1].endswith("\n"): 144 | outfile.write("\n") 145 | 146 | return combined_word_list 147 | 148 | def get_outdated_docs(self): 149 | return "all documents" 150 | 151 | def prepare_writing(self, docnames): 152 | return 153 | 154 | def get_target_uri(self, docname, typ=None): 155 | return "" 156 | 157 | def get_suggestions_to_show(self, suggestions): 158 | if not self.config.spelling_show_suggestions or not suggestions: 159 | return [] 160 | to_show = suggestions 161 | try: 162 | n_to_show = int(self.config.spelling_suggestion_limit) 163 | except ValueError: 164 | n_to_show = 0 165 | if n_to_show > 0: 166 | to_show = suggestions[:n_to_show] 167 | return to_show 168 | 169 | def format_suggestions(self, suggestions): 170 | to_show = self.get_suggestions_to_show(suggestions) 171 | if not to_show: 172 | return "" 173 | return "[" + ", ".join('"%s"' % s for s in to_show) + "]" 174 | 175 | TEXT_NODES = { 176 | "block_quote", 177 | "caption", 178 | "paragraph", 179 | "list_item", 180 | "term", 181 | "definition_list_item", 182 | "title", 183 | } 184 | 185 | def write_doc(self, docname, doctree): 186 | lines = list(self._find_misspellings(docname, doctree)) 187 | self.misspelling_count += len(lines) 188 | if lines: 189 | output_filename = os.path.join(self.outdir, f"{docname}.spelling") 190 | logger.info("Writing %s", output_filename) 191 | ensuredir(os.path.dirname(output_filename)) 192 | with open(output_filename, "w", encoding="UTF-8") as output: 193 | output.writelines(lines) 194 | 195 | def _find_misspellings(self, docname, doctree): 196 | excluded = Matcher(self.config.spelling_exclude_patterns) 197 | if excluded(self.env.doc2path(docname, None)): 198 | return 199 | # Build the document-specific word filter based on any good 200 | # words listed in spelling directives. If we have no such 201 | # words, we want to push an empty list of filters so that we 202 | # can always safely pop the filter stack when we are done with 203 | # this document. 204 | doc_filters = [] 205 | good_words = self.env.spelling_document_words.get(docname) 206 | if good_words: 207 | logger.debug("Extending local dictionary for %s", docname) 208 | doc_filters.append(filters.IgnoreWordsFilterFactory(good_words)) 209 | self.checker.push_filters(doc_filters) 210 | 211 | # Set up a filter for the types of nodes to ignore during 212 | # traversal. 213 | def filter(n): 214 | if n.tagname != "#text": 215 | return False 216 | if n.parent and n.parent.tagname not in self.TEXT_NODES: 217 | return False 218 | # Nodes marked by the spelling:ignore role 219 | if hasattr(n, "spellingIgnore"): 220 | return False 221 | return True 222 | 223 | for node in doctree.findall(filter): 224 | # Get the location of the text being checked so we can 225 | # report it in the output file. Nodes from text that 226 | # comes in via an 'include' directive does not include 227 | # the full path, so convert all to relative path 228 | # for consistency. 229 | source, node_lineno = docutils.utils.get_source_line(node) 230 | source = osutil.relpath(source) 231 | 232 | # Check the text of the node. 233 | misspellings = self.checker.check(node.astext()) 234 | for word, suggestions, context_line, line_offset in misspellings: 235 | # Avoid TypeError on nodes lacking a line number 236 | # This happens for some node originating from docstrings 237 | lineno = node_lineno 238 | if lineno is not None: 239 | lineno += line_offset 240 | 241 | msg_parts = [ 242 | f"{source}:{lineno}: ", 243 | "Spell check", 244 | red(word), 245 | ] 246 | if self.format_suggestions(suggestions) != "": 247 | msg_parts.append(self.format_suggestions(suggestions)) 248 | msg_parts.append(context_line) 249 | msg = ": ".join(msg_parts) + "." 250 | if self.config.spelling_warning: 251 | logger.warning(msg) 252 | elif self.config.spelling_verbose: 253 | logger.info(msg) 254 | yield "%s:%s: (%s) %s %s\n" % ( 255 | source, 256 | lineno, 257 | word, 258 | self.format_suggestions(suggestions), 259 | context_line, 260 | ) 261 | 262 | self.checker.pop_filters() 263 | return 264 | 265 | def finish(self): 266 | if self.misspelling_count: 267 | logger.warning("Found %d misspelled words", self.misspelling_count) 268 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/checker.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Spelling checker extension for Sphinx.""" 5 | 6 | try: 7 | import enchant 8 | from enchant.tokenize import get_tokenizer 9 | except ImportError as imp_exc: 10 | enchant_import_error = imp_exc 11 | else: 12 | enchant_import_error = None 13 | 14 | 15 | class SpellingChecker: 16 | """Checks the spelling of blocks of text. 17 | 18 | Uses options defined in the sphinx configuration file to control 19 | the checking and filtering behavior. 20 | """ 21 | 22 | def __init__( 23 | self, 24 | lang, 25 | suggest, 26 | word_list_filename, 27 | tokenizer_lang="en_US", 28 | filters=None, 29 | context_line=False, 30 | ): 31 | if enchant_import_error is not None: 32 | raise RuntimeError( 33 | "Cannot instantiate SpellingChecker without PyEnchant installed", 34 | ) from enchant_import_error 35 | if filters is None: 36 | filters = [] 37 | self.dictionary = enchant.DictWithPWL(lang, word_list_filename) 38 | self.tokenizer = get_tokenizer(tokenizer_lang, filters=filters) 39 | self.original_tokenizer = self.tokenizer 40 | self.suggest = suggest 41 | self.context_line = context_line 42 | 43 | def push_filters(self, new_filters): 44 | """Add a filter to the tokenizer chain.""" 45 | t = self.tokenizer 46 | for f in new_filters: 47 | t = f(t) 48 | self.tokenizer = t 49 | 50 | def pop_filters(self): 51 | """Remove the filters pushed during the last call to push_filters().""" 52 | self.tokenizer = self.original_tokenizer 53 | 54 | def check(self, text): 55 | """Yields bad words and suggested alternate spellings.""" 56 | for word, pos in self.tokenizer(text): 57 | correct = self.dictionary.check(word) 58 | if correct: 59 | continue 60 | 61 | suggestions = self.dictionary.suggest(word) if self.suggest else [] 62 | line = line_of_index(text, pos) if self.context_line else "" 63 | line_offset = text.count("\n", 0, pos) 64 | 65 | yield word, suggestions, line, line_offset 66 | 67 | 68 | def line_of_index(text, index): 69 | try: 70 | line_start = text.rindex("\n", 0, index) + 1 71 | except ValueError: 72 | line_start = 0 73 | try: 74 | line_end = text.index("\n", index) 75 | except ValueError: 76 | line_end = len(text) 77 | 78 | return text[line_start:line_end] 79 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/directive.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Spelling checker extension for Sphinx.""" 5 | 6 | import collections 7 | 8 | from docutils.parsers import rst 9 | from sphinx.util import logging 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def add_good_words_to_document(env, docname, good_words): 15 | # Initialize the per-document good words list 16 | if not hasattr(env, "spelling_document_words"): 17 | env.spelling_document_words = collections.defaultdict(list) 18 | logger.debug("Extending local dictionary for %s with %s", env.docname, good_words) 19 | env.spelling_document_words[env.docname].extend(good_words) 20 | 21 | 22 | class SpellingDirective(rst.Directive): 23 | """Custom directive for passing instructions to the spelling checker. 24 | 25 | .. spelling:: 26 | 27 | word1 28 | word2 29 | 30 | """ 31 | 32 | has_content = True 33 | 34 | def run(self): 35 | env = self.state.document.settings.env 36 | 37 | good_words = [] 38 | for entry in self.content: 39 | if not entry: 40 | continue 41 | good_words.extend(entry.split()) 42 | if good_words: 43 | add_good_words_to_document(env, env.docname, good_words) 44 | 45 | return [] 46 | 47 | 48 | class LegacySpellingDirective(SpellingDirective): 49 | def run(self): 50 | logger.info( 51 | "direct use of the spelling directive is deprecated, " 52 | 'replace ".. spelling::" with ".. spelling:word-list::"' 53 | ) 54 | return super().run() 55 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/domain.py: -------------------------------------------------------------------------------- 1 | from sphinx.domains import Domain 2 | 3 | from . import directive, role 4 | 5 | 6 | class SpellingDomain(Domain): 7 | name = "spelling" 8 | label = "Spelling Checker" 9 | directives = { 10 | "word-list": directive.SpellingDirective, 11 | } 12 | roles = {"word": role.spelling_word, "ignore": role.spelling_ignore} 13 | 14 | def get_objects(self): 15 | return [] 16 | 17 | def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): 18 | return None 19 | 20 | def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): 21 | return [] 22 | 23 | def merge_domaindata(self, docnames, otherdata): 24 | pass 25 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/filters.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Spelling checker extension for Sphinx.""" 5 | 6 | # TODO - Words with multiple uppercase letters treated as classes and ignored 7 | 8 | import builtins 9 | import importlib 10 | import subprocess 11 | import sys 12 | 13 | import requests 14 | from enchant.tokenize import Filter, get_tokenizer, tokenize, unit_tokenize 15 | from sphinx.util import logging 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class AcronymFilter(Filter): 21 | """If a word looks like an acronym (all upper case letters), 22 | ignore it. 23 | """ 24 | 25 | def _skip(self, word): 26 | return ( 27 | word.isupper() # all caps 28 | or 29 | # pluralized acronym ("URLs") 30 | (word[-1].lower() == "s" and word[:-1].isupper()) 31 | ) 32 | 33 | 34 | class list_tokenize(tokenize): 35 | def __init__(self, words): 36 | super().__init__("") 37 | self._words = words 38 | 39 | def next(self): 40 | if not self._words: 41 | raise StopIteration() 42 | word = self._words.pop(0) 43 | return (word, 0) 44 | 45 | 46 | class ContractionFilter(Filter): 47 | """Strip common contractions from words.""" 48 | 49 | splits = { 50 | "aren't": ["are", "not"], 51 | "can't": ["can", "not"], 52 | "could've": ["could", "have"], 53 | "couldn't": ["could", "not"], 54 | "didn't": ["did", "not"], 55 | "doesn't": ["does", "not"], 56 | "don't": ["do", "not"], 57 | "hadn't": ["had", "not"], 58 | "hasn't": ["has", "not"], 59 | "haven't": ["have", "not"], 60 | "he'd": ["he", "would"], 61 | "he'll": ["he", "will"], 62 | "he's": ["he", "is"], 63 | "how'd": ["how", "would"], 64 | "how'll": ["how", "will"], 65 | "how's": ["how", "is"], 66 | "i'd": ["I", "would"], 67 | "i'll": ["I", "will"], 68 | "i'm": ["I", "am"], 69 | "i've": ["I", "have"], 70 | "isn't": ["is", "not"], 71 | "it'd": ["it", "would"], 72 | "it'll": ["it", "will"], 73 | "it's": ["it", "is"], 74 | "ma'am": ["madam"], 75 | "might've": ["might", "have"], 76 | "mightn't": ["might", "not"], 77 | "must've": ["must", "have"], 78 | "mustn't": ["must", "not"], 79 | "o'": ["of"], 80 | "o'clock": ["of", "the", "clock"], 81 | "she'd": ["she", "would"], 82 | "she'll": ["she", "will"], 83 | "she's": ["she", "is"], 84 | "should've": ["should", "have"], 85 | "shouldn't": ["should", "not"], 86 | "that'd": ["that", "would"], 87 | "that'll": ["that", "will"], 88 | "that's": ["that", "is"], 89 | "they'd": ["they", "would"], 90 | "they'll": ["they", "will"], 91 | "they're": ["they", "are"], 92 | "they've": ["they", "have"], 93 | "wasn't": ["was", "not"], 94 | "we'd": ["we", "would"], 95 | "we'll": ["we", "will"], 96 | "we're": ["we", "are"], 97 | "we've": ["we", "have"], 98 | "weren't": ["were", "not"], 99 | "what'd": ["what", "would"], 100 | "what'll": ["what", "will"], 101 | "what're": ["what", "are"], 102 | "what's": ["what", "is"], 103 | "when'd": ["when", "would"], 104 | "when'll": ["when", "will"], 105 | "when's": ["when", "is"], 106 | "where'd": ["where", "would"], 107 | "where'll": ["where", "will"], 108 | "where's": ["where", "is"], 109 | "who'd": ["who", "would"], 110 | "who'll": ["who", "will"], 111 | "who's": ["who", "is"], 112 | "why'd": ["why", "would"], 113 | "why'll": ["why", "will"], 114 | "why's": ["why", "is"], 115 | "won't": ["will", "not"], 116 | "would've": ["would", "have"], 117 | "wouldn't": ["would", "not"], 118 | "you'd": ["you", "would"], 119 | "you'll": ["you", "will"], 120 | "you're": ["you", "are"], 121 | "you've": ["you", "have"], 122 | } 123 | 124 | def _split(self, word): 125 | # Fixed responses 126 | if word.lower() in self.splits: 127 | return list_tokenize(self.splits[word.lower()]) 128 | 129 | # Possessive 130 | if word.lower().endswith("'s"): 131 | return unit_tokenize(word[:-2]) 132 | 133 | # * not 134 | if word.lower().endswith("n't"): 135 | return unit_tokenize(word[:-3]) 136 | 137 | return unit_tokenize(word) 138 | 139 | 140 | class IgnoreWordsFilter(Filter): 141 | """Given a set of words, ignore them all.""" 142 | 143 | def __init__(self, tokenizer, word_set): 144 | self.word_set = set(word_set) 145 | super().__init__(tokenizer) 146 | 147 | def _skip(self, word): 148 | return word in self.word_set 149 | 150 | 151 | class IgnoreWordsFilterFactory: 152 | def __init__(self, words): 153 | self.words = words 154 | 155 | def __call__(self, tokenizer): 156 | return IgnoreWordsFilter(tokenizer, self.words) 157 | 158 | 159 | class PyPIFilterFactory(IgnoreWordsFilterFactory): 160 | """Build an IgnoreWordsFilter for all of the names of packages on PyPI.""" 161 | 162 | def __init__(self): 163 | r = requests.get( 164 | "https://pypi.org/simple/", 165 | headers={ 166 | "user-agent": "sphinxcontrib.spelling", 167 | "accept": "application/vnd.pypi.simple.v1+json", 168 | }, 169 | ) 170 | names = [i["name"] for i in r.json()["projects"]] 171 | logger.debug("retrieved %d project names from pypi.org", len(names)) 172 | super().__init__(names) 173 | 174 | 175 | class PythonBuiltinsFilter(Filter): 176 | """Ignore names of built-in Python symbols.""" 177 | 178 | def _skip(self, word): 179 | return hasattr(builtins, word) 180 | 181 | 182 | class ImportableModuleFilter(Filter): 183 | """Ignore names of modules that we could import.""" 184 | 185 | def __init__(self, tokenizer): 186 | super().__init__(tokenizer) 187 | self.found_modules = set(sys.builtin_module_names) 188 | self.sought_modules = self.found_modules.copy() 189 | # By adding __main__ to the list of sought modules but not 190 | # found modules we ensure that it is never recognized as a 191 | # valid module, which is consistent with the behavior before 192 | # version 7.3.1. See 193 | # https://github.com/sphinx-contrib/spelling/issues/141 194 | self.sought_modules.add("__main__") 195 | 196 | def _skip(self, word): 197 | # If the word looks like a python module filename, strip the 198 | # extension to avoid the side-effect of actually importing the 199 | # module. This prevents, for example, 'setup.py' triggering an 200 | # import of the setup module during a doc build, which makes 201 | # it look like Sphinx is complaining about a commandline 202 | # argument. See 203 | # https://github.com/sphinx-contrib/spelling/issues/142 204 | if word.endswith(".py"): 205 | logger.debug( 206 | "removing .py extension from %r before searching for module", word 207 | ) 208 | word = word[:-3] 209 | 210 | valid_module_name = all(n.isidentifier() for n in word.split(".")) 211 | if not valid_module_name: 212 | return False 213 | 214 | if word not in self.sought_modules: 215 | self.sought_modules.add(word) 216 | try: 217 | mod = importlib.util.find_spec(word) 218 | except BaseException as err: 219 | # This could be an ImportError, SystemExit, some more detailed 220 | # error out of distutils, or something else triggered 221 | # by failing to be able to import a parent package to 222 | # use the metadata to search for a subpackage. 223 | logger.debug("find_spec(%r) failed, invalid module name: %s", word, err) 224 | else: 225 | if mod is not None: 226 | self.found_modules.add(word) 227 | 228 | return word in self.found_modules 229 | 230 | 231 | class ContributorFilter(IgnoreWordsFilter): 232 | """Accept information about contributors as spelled correctly. 233 | 234 | Look in the git history for authors and committers and accept 235 | tokens that are in the set. 236 | """ 237 | 238 | _pretty_format = "%(trailers:key=Co-Authored-By,separator=%x0A)%x0A%an%x0A%cn" 239 | 240 | def __init__(self, tokenizer): 241 | contributors = self._get_contributors() 242 | super().__init__(tokenizer, contributors) 243 | 244 | def _get_contributors(self): 245 | logger.info("Scanning contributors") 246 | cmd = [ 247 | "git", 248 | "log", 249 | "--quiet", 250 | "--no-color", 251 | f"--pretty=format:{self._pretty_format}", 252 | ] 253 | 254 | try: 255 | p = subprocess.run(cmd, check=True, stdout=subprocess.PIPE) 256 | except (subprocess.CalledProcessError, FileNotFoundError) as err: 257 | logger.warning("Called: %s", " ".join(cmd)) 258 | logger.warning("Failed to scan contributors: %s", err) 259 | return set() 260 | output = p.stdout.decode("utf-8") 261 | tokenizer = get_tokenizer("en_US", filters=[]) 262 | return {word for word, pos in tokenizer(output)} 263 | -------------------------------------------------------------------------------- /sphinxcontrib/spelling/role.py: -------------------------------------------------------------------------------- 1 | from docutils import nodes 2 | 3 | from . import directive 4 | 5 | 6 | def spelling_word(role, rawtext, text, lineno, inliner, options={}, content=[]): 7 | """Let the user indicate that inline text is spelled correctly.""" 8 | env = inliner.document.settings.env 9 | docname = env.docname 10 | good_words = text.split() 11 | directive.add_good_words_to_document(env, docname, good_words) 12 | node = nodes.Text(text) 13 | return [node], [] 14 | 15 | 16 | def spelling_ignore(role, rawtext, text, lineno, inliner, options={}, content=[]): 17 | """Let the user indicate that inline text is to not be spellchecked.""" 18 | node = nodes.Text(text) 19 | setattr(node, "spellingIgnore", True) 20 | return [node], [] 21 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import pathlib 5 | 6 | import pytest 7 | 8 | 9 | def require_git_repo(f): 10 | return pytest.mark.skipif( 11 | not (pathlib.Path(os.getcwd()) / ".git").is_dir(), reason="Not a git repo" 12 | )(f) 13 | -------------------------------------------------------------------------------- /tests/test_builder.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Tests for SpellingBuilder""" 5 | 6 | import contextlib 7 | import io 8 | import os 9 | import sys 10 | import textwrap 11 | 12 | import pytest 13 | from sphinx.application import Sphinx 14 | 15 | from tests import helpers # isort:skip 16 | 17 | 18 | def _make_sphinx_project(tmpdir): 19 | srcdir = tmpdir.mkdir("src") 20 | outdir = tmpdir.mkdir("out") 21 | add_file( 22 | srcdir, 23 | "conf.py", 24 | """ 25 | extensions = [ 'sphinxcontrib.spelling' ] 26 | """, 27 | ) 28 | return (srcdir, outdir) 29 | 30 | 31 | @pytest.fixture 32 | def sphinx_project(tmpdir): 33 | yield _make_sphinx_project(tmpdir) 34 | 35 | 36 | @contextlib.contextmanager 37 | def working_dir(targetdir): 38 | "Temporarily change the working directory of the process." 39 | before = os.getcwd() 40 | os.chdir(targetdir) 41 | try: 42 | yield os.getcwd() 43 | finally: 44 | os.chdir(before) 45 | 46 | 47 | @contextlib.contextmanager 48 | def import_path(new_path): 49 | "Temporarily change sys.path for imports." 50 | before = sys.path 51 | try: 52 | sys.path = new_path 53 | yield 54 | finally: 55 | sys.path = before 56 | 57 | 58 | def add_file(thedir, filename, content): 59 | with open(thedir.join(filename), "w") as f: 60 | f.write(textwrap.dedent(content)) 61 | 62 | 63 | def get_sphinx_app(srcdir, outdir, docname, builder="spelling"): 64 | stdout = io.StringIO() 65 | stderr = io.StringIO() 66 | app = Sphinx( 67 | srcdir, 68 | srcdir, 69 | outdir, 70 | outdir, 71 | builder, 72 | status=stdout, 73 | warning=stderr, 74 | freshenv=True, 75 | ) 76 | return (stdout, stderr, app) 77 | 78 | 79 | def get_sphinx_output(srcdir, outdir, docname, builder="spelling"): 80 | stdout, stderr, app = get_sphinx_app(srcdir, outdir, docname, builder) 81 | app.build() 82 | path = os.path.join(outdir, f"{docname}.spelling") 83 | try: 84 | with open(path, "r") as f: 85 | output_text = f.read() 86 | except FileNotFoundError: 87 | output_text = None 88 | return (stdout, stderr, output_text) 89 | 90 | 91 | def test_setup(sphinx_project): 92 | srcdir, outdir = sphinx_project 93 | stdout = io.StringIO() 94 | stderr = io.StringIO() 95 | # If the spelling builder is not properly initialized, 96 | # trying to use it with the Sphinx app class will 97 | # generate an exception. 98 | Sphinx( 99 | str(srcdir), 100 | str(srcdir), 101 | str(outdir), 102 | str(outdir), 103 | "spelling", 104 | status=stdout, 105 | warning=stderr, 106 | freshenv=True, 107 | ) 108 | 109 | 110 | def test_title(sphinx_project): 111 | srcdir, outdir = sphinx_project 112 | add_file( 113 | srcdir, 114 | "contents.rst", 115 | """ 116 | Welcome to Speeling Checker documentation! 117 | ========================================== 118 | """, 119 | ) 120 | stdout, stderr, output_text = get_sphinx_output(srcdir, outdir, "contents") 121 | assert "(Speeling)" in output_text 122 | 123 | 124 | def test_body(sphinx_project): 125 | srcdir, outdir = sphinx_project 126 | add_file( 127 | srcdir, 128 | "contents.rst", 129 | """ 130 | Welcome to Spelling Checker documentation! 131 | ========================================== 132 | 133 | There are several mispelled words in this txt. 134 | """, 135 | ) 136 | stdout, stderr, output_text = get_sphinx_output(srcdir, outdir, "contents") 137 | assert "(mispelled)" in output_text 138 | assert "(txt)" in output_text 139 | 140 | 141 | def test_ignore_literals(sphinx_project): 142 | srcdir, outdir = sphinx_project 143 | add_file( 144 | srcdir, 145 | "contents.rst", 146 | """ 147 | Welcome to Spelling Checker documentation! 148 | ========================================== 149 | 150 | There are several misspelled words in this text. 151 | 152 | :: 153 | 154 | Literal blocks are ignoreed. 155 | 156 | Inline ``litterals`` are ignored, too. 157 | 158 | """, 159 | ) 160 | stdout, stderr, output_text = get_sphinx_output(srcdir, outdir, "contents") 161 | # The 'contents.spelling' output file should not have been 162 | # created, because the errors are ignored. 163 | assert output_text is None 164 | 165 | 166 | def test_several_word_lists(sphinx_project): 167 | srcdir, outdir = sphinx_project 168 | add_file( 169 | srcdir, 170 | "conf.py", 171 | """ 172 | extensions = ['sphinxcontrib.spelling'] 173 | spelling_word_list_filename=['test_wordlist.txt','test_wordlist2.txt'] 174 | """, 175 | ) 176 | 177 | add_file( 178 | srcdir, 179 | "contents.rst", 180 | """ 181 | Welcome to Spelling Checker documentation! 182 | ========================================== 183 | 184 | There are several mispelled words in tihs txt. 185 | """, 186 | ) 187 | 188 | add_file( 189 | srcdir, 190 | "test_wordlist.txt", 191 | """ 192 | txt 193 | """, 194 | ) 195 | 196 | add_file( 197 | srcdir, 198 | "test_wordlist2.txt", 199 | """ 200 | mispelled 201 | """, 202 | ) 203 | stdout, stderr, output_text = get_sphinx_output(srcdir, outdir, "contents") 204 | # Both of these should be fine now 205 | assert "(mispelled)" not in output_text 206 | assert "(txt)" not in output_text 207 | # But not this one 208 | assert "(tihs)" in output_text 209 | 210 | 211 | def _wordlist_sphinx_project(tmpdir, conf_contents): 212 | srcdir, outdir = _make_sphinx_project(tmpdir) 213 | add_file(srcdir, "conf.py", conf_contents) 214 | add_file( 215 | srcdir, 216 | "test_wordlist.txt", 217 | """ 218 | txt 219 | """, 220 | ) 221 | add_file( 222 | srcdir, 223 | "test_wordlist2.txt", 224 | """ 225 | mispelled 226 | """, 227 | ) 228 | stdout, stderr, app = get_sphinx_app(srcdir, outdir, "contents") 229 | return (srcdir, outdir, stdout, stderr, app) 230 | 231 | 232 | def test_word_list_default(tmpdir): 233 | srcdir, outdir, stdout, stderr, app = _wordlist_sphinx_project( 234 | tmpdir, 235 | """ 236 | extensions = ['sphinxcontrib.spelling'] 237 | """, 238 | ) 239 | results = app.builder.get_configured_wordlist_filenames() 240 | assert len(results) == 1 241 | assert os.path.basename(results[0]) == "spelling_wordlist.txt" 242 | 243 | 244 | def test_one_word_list_str(tmpdir): 245 | srcdir, outdir, stdout, stderr, app = _wordlist_sphinx_project( 246 | tmpdir, 247 | """ 248 | extensions = ['sphinxcontrib.spelling'] 249 | spelling_word_list_filename='test_wordlist.txt' 250 | """, 251 | ) 252 | results = app.builder.get_configured_wordlist_filenames() 253 | assert len(results) == 1 254 | assert os.path.basename(results[0]) == "test_wordlist.txt" 255 | 256 | 257 | def test_multiple_word_list_str(tmpdir): 258 | # We don't expect anyone to set up their conf.py this way but it 259 | # simulates passing the configuration option from the command line 260 | # using -D. 261 | srcdir, outdir, stdout, stderr, app = _wordlist_sphinx_project( 262 | tmpdir, 263 | """ 264 | extensions = ['sphinxcontrib.spelling'] 265 | spelling_word_list_filename='test_wordlist.txt,test_wordlist2.txt' 266 | """, 267 | ) 268 | results = app.builder.get_configured_wordlist_filenames() 269 | assert len(results) == 2 270 | assert os.path.basename(results[0]) == "test_wordlist.txt" 271 | assert os.path.basename(results[1]) == "test_wordlist2.txt" 272 | 273 | 274 | def test_multiple_word_list_list(tmpdir): 275 | srcdir, outdir, stdout, stderr, app = _wordlist_sphinx_project( 276 | tmpdir, 277 | """ 278 | extensions = ['sphinxcontrib.spelling'] 279 | spelling_word_list_filename=['test_wordlist.txt', 'test_wordlist2.txt'] 280 | """, 281 | ) 282 | results = app.builder.get_configured_wordlist_filenames() 283 | assert len(results) == 2 284 | assert os.path.basename(results[0]) == "test_wordlist.txt" 285 | assert os.path.basename(results[1]) == "test_wordlist2.txt" 286 | 287 | 288 | def test_ignore_file(sphinx_project): 289 | srcdir, outdir = sphinx_project 290 | add_file( 291 | srcdir, 292 | "conf.py", 293 | """ 294 | extensions = ['sphinxcontrib.spelling'] 295 | spelling_exclude_patterns=['con*'] 296 | """, 297 | ) 298 | 299 | add_file( 300 | srcdir, 301 | "contents.rst", 302 | """ 303 | Welcome to Speeling Checker documentation! 304 | ========================================== 305 | """, 306 | ) 307 | 308 | stdout, stderr, output_text = get_sphinx_output(srcdir, outdir, "contents") 309 | # The 'contents.spelling' output file should not have been 310 | # created, because the file is ignored. 311 | assert output_text is None 312 | 313 | 314 | @helpers.require_git_repo 315 | def test_docstrings(sphinx_project): 316 | srcdir, outdir = sphinx_project 317 | 318 | add_file( 319 | srcdir, 320 | "conf.py", 321 | """ 322 | extensions = ['sphinxcontrib.spelling', 'sphinx.ext.autodoc'] 323 | """, 324 | ) 325 | 326 | add_file( 327 | srcdir / "..", 328 | "the_source.py", 329 | ''' 330 | #!/usr/bin/env python3 331 | 332 | def public_function(arg_name): 333 | """Does something useful. 334 | 335 | :param arg_name: Pass a vaule 336 | """ 337 | return 1 338 | ''', 339 | ) 340 | 341 | add_file( 342 | srcdir, 343 | "contents.rst", 344 | """ 345 | The Module 346 | ========== 347 | 348 | .. automodule:: the_source 349 | :members: 350 | 351 | """, 352 | ) 353 | 354 | with working_dir(srcdir / ".."): 355 | with import_path(["."] + sys.path): 356 | stdout, stderr, output_text = get_sphinx_output( 357 | srcdir, 358 | outdir, 359 | "contents", 360 | ) 361 | 362 | expected = "src/contents.rst:3: (vaule) Pass a vaule\n" 363 | assert expected in output_text 364 | 365 | 366 | def test_get_suggestions_to_show_all(sphinx_project): 367 | srcdir, outdir = sphinx_project 368 | add_file( 369 | srcdir, 370 | "conf.py", 371 | """ 372 | extensions = ['sphinxcontrib.spelling'] 373 | spelling_show_suggestions = True 374 | spelling_suggestion_limit = 0 375 | """, 376 | ) 377 | stdout, stderr, app = get_sphinx_app(srcdir, outdir, "contents") 378 | results = app.builder.get_suggestions_to_show(["a", "b", "c"]) 379 | assert len(results) == 3 380 | 381 | 382 | def test_get_suggestions_to_show_limit(sphinx_project): 383 | srcdir, outdir = sphinx_project 384 | add_file( 385 | srcdir, 386 | "conf.py", 387 | """ 388 | extensions = ['sphinxcontrib.spelling'] 389 | spelling_show_suggestions = True 390 | spelling_suggestion_limit = 1 391 | """, 392 | ) 393 | stdout, stderr, app = get_sphinx_app(srcdir, outdir, "contents") 394 | results = app.builder.get_suggestions_to_show(["a", "b", "c"]) 395 | assert len(results) == 1 396 | 397 | 398 | def test_get_suggestions_to_show_disabled(sphinx_project): 399 | srcdir, outdir = sphinx_project 400 | add_file( 401 | srcdir, 402 | "conf.py", 403 | """ 404 | extensions = ['sphinxcontrib.spelling'] 405 | spelling_show_suggestions = False 406 | spelling_suggestion_limit = 0 407 | """, 408 | ) 409 | stdout, stderr, app = get_sphinx_app(srcdir, outdir, "contents") 410 | results = app.builder.get_suggestions_to_show(["a", "b", "c"]) 411 | assert len(results) == 0 412 | 413 | 414 | def test_captions(sphinx_project): 415 | srcdir, outdir = sphinx_project 416 | 417 | add_file( 418 | srcdir, 419 | "contents.rst", 420 | """ 421 | The Module 422 | ========== 423 | 424 | .. figure:: blah.gif 425 | 426 | Teh caption 427 | 428 | """, 429 | ) 430 | 431 | stdout, stderr, output_text = get_sphinx_output( 432 | srcdir, 433 | outdir, 434 | "contents", 435 | ) 436 | assert "(Teh)" in output_text 437 | 438 | 439 | def test_legacy_directive(sphinx_project): 440 | srcdir, outdir = sphinx_project 441 | 442 | add_file( 443 | srcdir, 444 | "contents.rst", 445 | """ 446 | The Module 447 | ========== 448 | 449 | .. spelling:: 450 | 451 | teh 452 | 453 | teh is OK 454 | 455 | """, 456 | ) 457 | 458 | stdout, stderr, output_text = get_sphinx_output( 459 | srcdir, 460 | outdir, 461 | "contents", 462 | ) 463 | assert output_text is None 464 | 465 | 466 | def test_domain_directive(sphinx_project): 467 | srcdir, outdir = sphinx_project 468 | 469 | add_file( 470 | srcdir, 471 | "contents.rst", 472 | """ 473 | The Module 474 | ========== 475 | 476 | .. spelling:word-list:: 477 | 478 | teh 479 | 480 | teh is OK 481 | 482 | """, 483 | ) 484 | 485 | stdout, stderr, output_text = get_sphinx_output( 486 | srcdir, 487 | outdir, 488 | "contents", 489 | ) 490 | assert output_text is None 491 | 492 | 493 | def test_domain_role(sphinx_project): 494 | srcdir, outdir = sphinx_project 495 | 496 | add_file( 497 | srcdir, 498 | "contents.rst", 499 | """ 500 | The Module 501 | ========== 502 | 503 | :spelling:word:`teh` is OK 504 | 505 | """, 506 | ) 507 | 508 | stdout, stderr, output_text = get_sphinx_output( 509 | srcdir, 510 | outdir, 511 | "contents", 512 | ) 513 | assert output_text is None 514 | 515 | 516 | def test_domain_role_multiple_words(sphinx_project): 517 | srcdir, outdir = sphinx_project 518 | 519 | add_file( 520 | srcdir, 521 | "contents.rst", 522 | """ 523 | The Module 524 | ========== 525 | 526 | :spelling:word:`teh is KO` 527 | 528 | """, 529 | ) 530 | 531 | stdout, stderr, output_text = get_sphinx_output( 532 | srcdir, 533 | outdir, 534 | "contents", 535 | ) 536 | assert output_text is None 537 | 538 | 539 | def test_domain_role_output(sphinx_project): 540 | srcdir, outdir = sphinx_project 541 | 542 | add_file( 543 | srcdir, 544 | "contents.rst", 545 | """ 546 | The Module 547 | ========== 548 | 549 | :spelling:word:`teh` is OK 550 | 551 | """, 552 | ) 553 | 554 | stdout, stderr, output_text = get_sphinx_output( 555 | srcdir, 556 | outdir, 557 | "contents", 558 | "text", 559 | ) 560 | 561 | path = os.path.join(outdir, "contents.txt") 562 | try: 563 | with open(path, "r") as f: 564 | output_text = f.read() 565 | except FileNotFoundError: 566 | output_text = None 567 | 568 | assert output_text == "The Module\n**********\n\nteh is OK\n" 569 | 570 | 571 | def test_domain_ignore(sphinx_project): 572 | srcdir, outdir = sphinx_project 573 | 574 | add_file( 575 | srcdir, 576 | "contents.rst", 577 | """ 578 | The Module 579 | ========== 580 | 581 | :spelling:ignore:`baddddd` is OK 582 | 583 | """, 584 | ) 585 | 586 | stdout, stderr, output_text = get_sphinx_output( 587 | srcdir, 588 | outdir, 589 | "contents", 590 | ) 591 | assert output_text is None 592 | 593 | 594 | def test_domain_ignore_multiple_words(sphinx_project): 595 | srcdir, outdir = sphinx_project 596 | 597 | add_file( 598 | srcdir, 599 | "contents.rst", 600 | """ 601 | The Module 602 | ========== 603 | 604 | :spelling:ignore:`baddddd` is OK here. 605 | 606 | But, baddddd is not OK here. 607 | Nor, here baddddd. 608 | 609 | """, 610 | ) 611 | 612 | stdout, stderr, output_text = get_sphinx_output( 613 | srcdir, 614 | outdir, 615 | "contents", 616 | ) 617 | assert "(baddddd)" in output_text 618 | assert output_text.count("\n") == 2 # Only expect 2 errors, not 3. 619 | 620 | 621 | def test_domain_ignore_output(sphinx_project): 622 | srcdir, outdir = sphinx_project 623 | 624 | add_file( 625 | srcdir, 626 | "contents.rst", 627 | """ 628 | The Module 629 | ========== 630 | 631 | :spelling:ignore:`teh` is OK 632 | 633 | """, 634 | ) 635 | 636 | stdout, stderr, output_text = get_sphinx_output( 637 | srcdir, 638 | outdir, 639 | "contents", 640 | "text", 641 | ) 642 | 643 | path = os.path.join(outdir, "contents.txt") 644 | try: 645 | with open(path, "r") as f: 646 | output_text = f.read() 647 | except FileNotFoundError: 648 | output_text = None 649 | 650 | assert output_text == "The Module\n**********\n\nteh is OK\n" 651 | 652 | 653 | def test_only_directive(sphinx_project): 654 | # How to skip checking nested blocks of content 655 | # https://github.com/sphinx-contrib/spelling/issues/204 656 | srcdir, outdir = sphinx_project 657 | 658 | add_file( 659 | srcdir, 660 | "contents.rst", 661 | """ 662 | The Module 663 | ========== 664 | 665 | .. only:: html 666 | 667 | teh is ok 668 | 669 | whaat is not ok 670 | """, 671 | ) 672 | 673 | stdout, stderr, output_text = get_sphinx_output( 674 | srcdir, 675 | outdir, 676 | "contents", 677 | ) 678 | assert "(whaat)" in output_text 679 | assert "(teh)" not in output_text 680 | -------------------------------------------------------------------------------- /tests/test_checker.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Tests for SpellingChecker.""" 5 | 6 | import os 7 | 8 | from sphinxcontrib.spelling.checker import SpellingChecker, line_of_index 9 | 10 | 11 | def test_errors_only(): 12 | checker = SpellingChecker( 13 | lang="en_US", 14 | suggest=False, 15 | word_list_filename=None, 16 | ) 17 | for word, suggestions, line, offset in checker.check("This txt is wrong"): 18 | assert not suggestions, "Suggesting" 19 | assert word == "txt" 20 | assert line == "" 21 | assert offset == 0 22 | 23 | 24 | def test_with_suggestions(): 25 | checker = SpellingChecker( 26 | lang="en_US", 27 | suggest=True, 28 | word_list_filename=None, 29 | ) 30 | for word, suggestions, line, offset in checker.check("This txt is wrong"): 31 | assert suggestions, "Not suggesting" 32 | assert word == "txt" 33 | assert line == "" 34 | assert offset == 0 35 | 36 | 37 | def test_with_wordlist(): 38 | checker = SpellingChecker( 39 | lang="en_US", 40 | suggest=False, 41 | word_list_filename=os.path.join(os.path.dirname(__file__), "test_wordlist.txt"), 42 | ) 43 | words = [ 44 | word for word, suggestions, line, offset in checker.check("This txt is wrong") 45 | ] 46 | assert not words, "Did not use personal word list file" 47 | 48 | 49 | def test_with_context_line(): 50 | checker = SpellingChecker( 51 | lang="en_US", 52 | suggest=False, 53 | word_list_filename=None, 54 | context_line=True, 55 | ) 56 | 57 | text = "Line one\nThis txt is wrong\nLine two" 58 | for word, suggestions, line, offset in checker.check(text): 59 | assert not suggestions, "Suggesting" 60 | assert word == "txt" 61 | assert line == "This txt is wrong" 62 | assert offset == 1 63 | 64 | 65 | def test_line_of_index_one_line(): 66 | text = "foo bar baz" 67 | assert line_of_index(text, 0) == text 68 | assert line_of_index(text, 5) == text 69 | assert line_of_index(text, len(text)) == text 70 | 71 | 72 | def test_line_of_index_multi_line(): 73 | text = "\nfoo\n\nbar baz\n" 74 | 75 | assert line_of_index(text, 0) == "" 76 | 77 | assert line_of_index(text, 1) == "foo" 78 | assert line_of_index(text, 2) == "foo" 79 | assert line_of_index(text, 3) == "foo" 80 | assert line_of_index(text, 4) == "foo" 81 | 82 | assert line_of_index(text, 5) == "" 83 | 84 | assert line_of_index(text, 6) == "bar baz" 85 | assert line_of_index(text, 12) == "bar baz" 86 | assert line_of_index(text, 13) == "bar baz" 87 | 88 | assert line_of_index(text, 14) == "" 89 | -------------------------------------------------------------------------------- /tests/test_filter.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010 Doug Hellmann. All rights reserved. 3 | # 4 | """Tests for filters.""" 5 | 6 | import contextlib 7 | import logging 8 | import os 9 | import sys 10 | 11 | import pytest 12 | from enchant.tokenize import get_tokenizer 13 | 14 | from sphinxcontrib.spelling import filters # isort:skip 15 | from tests import helpers # isort:skip 16 | 17 | # Replace the sphinx logger with a normal one so pytest can collect 18 | # the output. 19 | filters.logger = logging.getLogger("test.filters") 20 | 21 | 22 | def test_builtin_unicode(): 23 | f = filters.PythonBuiltinsFilter(None) 24 | assert not f._skip("passé") 25 | 26 | 27 | def test_builtin_regular(): 28 | f = filters.PythonBuiltinsFilter(None) 29 | assert f._skip("print") 30 | 31 | 32 | def test_acronym(): 33 | text = "a front-end for DBM-style databases" 34 | t = get_tokenizer("en_US", []) 35 | f = filters.AcronymFilter(t) 36 | words = [w[0] for w in f(text)] 37 | assert "DBM" not in words, "Failed to filter out acronym" 38 | 39 | 40 | def test_acronym_unicode(): 41 | text = "a front-end for DBM-style databases" 42 | t = get_tokenizer("en_US", []) 43 | f = filters.AcronymFilter(t) 44 | words = [w[0] for w in f(text)] 45 | assert "DBM" not in words, "Failed to filter out acronym" 46 | 47 | 48 | @helpers.require_git_repo 49 | @pytest.mark.parametrize( 50 | "name", 51 | [ 52 | "Alex", 53 | "Atlakson", 54 | "Avram", 55 | "Baumgold", 56 | "Berman", 57 | "Daniele", 58 | "Doug", 59 | "Finucane", 60 | "Gaynor", 61 | "Gonsiorowski", 62 | "Hong", 63 | "Hong", 64 | "Huon", 65 | "Kampik", 66 | "Kolosov", 67 | "Lubkin", 68 | "Marti", 69 | "Minhee", 70 | "Olausson", 71 | "Raggam", 72 | "Raudsepp", 73 | "sdelliot", 74 | "Sergey", 75 | "Sevilla", 76 | "Timotheus", 77 | "Tobias", 78 | "Tricoli", 79 | ], 80 | ) 81 | def test_contributors(name): 82 | f = filters.ContributorFilter(None) 83 | assert f._skip(name) 84 | 85 | 86 | @pytest.mark.parametrize( 87 | "word,expected", 88 | [ 89 | ("os", True), 90 | ("os.name", False), 91 | ("__main__", False), 92 | ("don't", False), 93 | ], 94 | ) 95 | def test_importable_module_skip(word, expected): 96 | f = filters.ImportableModuleFilter(None) 97 | assert f._skip(word) is expected 98 | 99 | 100 | @contextlib.contextmanager 101 | def import_path(new_path): 102 | "Temporarily change sys.path for imports." 103 | before = sys.path 104 | try: 105 | sys.path = new_path 106 | yield 107 | finally: 108 | sys.path = before 109 | 110 | 111 | def test_importable_module_with_side_effets(tmpdir): 112 | logging.debug("tmpdir %r", tmpdir) 113 | logging.debug("cwd %r", os.getcwd()) 114 | 115 | parentdir = tmpdir.join("parent") 116 | parentdir.mkdir() 117 | 118 | parentdir.join("__init__.py").write('raise SystemExit("exit as side-effect")\n') 119 | parentdir.join("child.py").write("") 120 | 121 | with import_path([str(tmpdir)] + sys.path): 122 | f = filters.ImportableModuleFilter(None) 123 | skip_parent = f._skip("parent") 124 | skip_both = f._skip("parent.child") 125 | 126 | # The parent module name is valid because it is not imported, only 127 | # discovered. 128 | assert skip_parent is True 129 | assert "parent" in f.found_modules 130 | 131 | # The child module name is not valid because the parent is 132 | # imported to find the child and that triggers the side-effect. 133 | assert skip_both is False 134 | assert "parent.child" not in f.found_modules 135 | 136 | 137 | def test_importable_module_with_system_exit(tmpdir): 138 | path = tmpdir.join("mytestmodule.py") 139 | path.write('raise SystemExit("exit as side-effect")\n') 140 | 141 | with import_path([str(tmpdir)] + sys.path): 142 | f = filters.ImportableModuleFilter(None) 143 | skip = f._skip("mytestmodule") 144 | 145 | # The filter does not actually import the module in this case, so 146 | # it shows up as a valid word. 147 | assert skip is True 148 | assert "mytestmodule" in f.found_modules 149 | 150 | 151 | def test_pypi_filter_factory(): 152 | f = filters.PyPIFilterFactory() 153 | assert "sphinxcontrib-spelling" in f.words 154 | assert "setuptools" in f.words 155 | -------------------------------------------------------------------------------- /tests/test_wordlist.txt: -------------------------------------------------------------------------------- 1 | txt 2 | -------------------------------------------------------------------------------- /tools/history-update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | git remote -v 6 | 7 | git branch -a 8 | 9 | # We only look at the files that have changed in the current PR, to 10 | # avoid problems when the template is changed in a way that is 11 | # incompatible with existing documents. 12 | if git log --name-only --pretty= "origin/main.." -- \ 13 | | grep -q '^docs/source/history.rst$'; then 14 | echo "Found a change to history file." 15 | exit 0 16 | fi 17 | 18 | echo "PRs must include a change in docs/source/history.rst" 19 | exit 1 20 | --------------------------------------------------------------------------------