├── .flake8
├── .gitattributes
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── checks.yaml
│ ├── primer.yaml
│ ├── primer_comment.yaml
│ ├── publish.yaml
│ └── tests.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── .pre-commit-hooks.yaml
├── .readthedocs.yaml
├── LICENSE
├── README.md
├── codecov.yml
├── docs
├── Makefile
├── _ext
│ └── usage_page.py
├── conf.py
├── development.rst
├── index.rst
├── make.bat
├── requirements-doc.txt
└── usage.rst
├── pydocstringformatter
├── __init__.py
├── __main__.py
├── _configuration
│ ├── __init__.py
│ ├── arguments_manager.py
│ ├── boolean_option_action.py
│ ├── command_line_parsing.py
│ ├── formatter_options.py
│ ├── toml_parsing.py
│ └── validators.py
├── _formatting
│ ├── __init__.py
│ ├── _utils.py
│ ├── base.py
│ ├── formatters_default.py
│ ├── formatters_numpydoc.py
│ └── formatters_pep257.py
├── _testutils
│ ├── __init__.py
│ ├── example_formatters.py
│ └── primer
│ │ ├── __init__.py
│ │ ├── const.py
│ │ ├── packages.py
│ │ └── primer.py
├── _utils
│ ├── __init__.py
│ ├── exceptions.py
│ ├── file_diference.py
│ ├── find_docstrings.py
│ ├── find_python_file.py
│ ├── issue_template.py
│ └── output.py
└── run.py
├── pyproject.toml
├── requirements-coverage.txt
├── requirements.txt
└── tests
├── conftest.py
├── data
├── config
│ ├── exclude_match
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ ├── exclude_match_csv
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ ├── exclude_match_csv_list
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ ├── exclude_match_inner
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── inner_dir
│ │ │ └── incorrect_docstring.py
│ ├── exclude_non_match
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ ├── invalid_toml
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── correct_docstring.py
│ ├── no_toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ ├── non_existing_options
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── correct_docstring.py
│ ├── non_valid_toml_boolopt
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── non_valid_toml_boolopt_two
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── valid_toml
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── correct_docstring.py
│ ├── valid_toml_boolopt
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── valid_toml_numpydoc
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── valid_toml_numpydoc_pep257
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── valid_toml_pep257
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── numpydoc_style.py
│ ├── valid_toml_two
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ │ └── incorrect_docstring.py
│ └── valid_toml_without_section
│ │ ├── pyproject.toml
│ │ └── test_package
│ │ └── correct_docstring.py
├── format
│ ├── beginning_quote
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── multi_line_no_push
│ │ │ ├── class_docstring.py
│ │ │ ├── class_docstring.py.out
│ │ │ ├── function_docstrings.py
│ │ │ ├── function_docstrings.py.out
│ │ │ ├── module_docstring.py
│ │ │ ├── module_docstring.py.out
│ │ │ ├── variable_docstring.py
│ │ │ └── variable_docstring.py.out
│ │ ├── multi_line_push
│ │ │ ├── class_docstring.args
│ │ │ ├── class_docstring.py
│ │ │ ├── class_docstring.py.out
│ │ │ ├── function_docstrings.args
│ │ │ ├── function_docstrings.py
│ │ │ ├── function_docstrings.py.out
│ │ │ ├── module_docstring.args
│ │ │ ├── module_docstring.py
│ │ │ ├── module_docstring.py.out
│ │ │ ├── variable_docstring.args
│ │ │ ├── variable_docstring.py
│ │ │ └── variable_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── capitalization_first_letter
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── single_quote_docstring.args
│ │ ├── single_quote_docstring.py
│ │ ├── single_quote_docstring.py.out
│ │ ├── uncapitalized_starting_letter.py
│ │ ├── uncapitalized_starting_letter.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── ending_quote
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── final_period
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── exotic_line_ending.py
│ │ ├── exotic_line_ending.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── function_title_docstrings.py
│ │ ├── function_title_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── single_quote_docstring.args
│ │ ├── single_quote_docstring.py
│ │ ├── single_quote_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── linewrap_summary
│ │ ├── function_docstrings.args
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── max_line_length_50.args
│ │ ├── max_line_length_50.py
│ │ └── max_line_length_50.py.out
│ ├── multi_line
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── newlines
│ │ ├── dos.py
│ │ ├── dos.py.out
│ │ ├── mac.py
│ │ ├── mac.py.out
│ │ ├── mixed.err
│ │ ├── mixed.py
│ │ ├── mixed.py.out
│ │ ├── none.args
│ │ ├── none.py
│ │ ├── none.py-0d0a.out
│ │ ├── none.py.out
│ │ ├── unix.py
│ │ └── unix.py.out
│ ├── no_changes
│ │ ├── multi_line_variables.py
│ │ └── multi_line_variables.py.out
│ ├── no_whitespace_stripper
│ │ ├── module_docstring.args
│ │ ├── module_docstring.py
│ │ └── module_docstring.py.out
│ ├── numpydoc
│ │ ├── numpydoc_closing_quotes.args
│ │ ├── numpydoc_closing_quotes.py
│ │ ├── numpydoc_closing_quotes.py.out
│ │ ├── numpydoc_header_line.args
│ │ ├── numpydoc_header_line.py
│ │ ├── numpydoc_header_line.py.out
│ │ ├── numpydoc_parameter_spacing.args
│ │ ├── numpydoc_parameter_spacing.py
│ │ ├── numpydoc_parameter_spacing.py.out
│ │ ├── numpydoc_regression.args
│ │ ├── numpydoc_regression.py
│ │ ├── numpydoc_regression.py.out
│ │ ├── numpydoc_section_ordering.args
│ │ ├── numpydoc_section_ordering.py
│ │ ├── numpydoc_section_ordering.py.out
│ │ ├── numpydoc_section_spacing.args
│ │ ├── numpydoc_section_spacing.py
│ │ ├── numpydoc_section_spacing.py.out
│ │ ├── numpydoc_style.args
│ │ ├── numpydoc_style.py
│ │ ├── numpydoc_style.py.out
│ │ ├── numpydoc_style_without_type.args
│ │ ├── numpydoc_style_without_type.py
│ │ └── numpydoc_style_without_type.py.out
│ ├── quotes_type
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── single_quote_docstring.args
│ │ ├── single_quote_docstring.py
│ │ ├── single_quote_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ ├── summary_splitter
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── dots.args
│ │ ├── dots.py
│ │ ├── dots.py.out
│ │ ├── empty_summary.args
│ │ ├── empty_summary.py
│ │ ├── empty_summary.py.out
│ │ ├── ending_quotes.args
│ │ ├── ending_quotes.py
│ │ ├── ending_quotes.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── max_summary_lines
│ │ │ ├── max_lines_is_1.args
│ │ │ ├── max_lines_is_1.py
│ │ │ ├── max_lines_is_1.py.out
│ │ │ ├── max_lines_is_2.args
│ │ │ ├── max_lines_is_2.py
│ │ │ ├── max_lines_is_2.py.out
│ │ │ ├── max_lines_is_3.args
│ │ │ ├── max_lines_is_3.py
│ │ │ ├── max_lines_is_3.py.out
│ │ │ ├── max_lines_is_default.py
│ │ │ ├── max_lines_is_default.py.out
│ │ │ ├── max_lines_with_dot.py
│ │ │ └── max_lines_with_dot.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── regression_151.py
│ │ ├── regression_151.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
│ └── whitespace_stripper
│ │ ├── class_docstring.py
│ │ ├── class_docstring.py.out
│ │ ├── function_docstrings.py
│ │ ├── function_docstrings.py.out
│ │ ├── module_docstring.py
│ │ ├── module_docstring.py.out
│ │ ├── single_quote_docstring.args
│ │ ├── single_quote_docstring.py
│ │ ├── single_quote_docstring.py.out
│ │ ├── variable_docstring.py
│ │ └── variable_docstring.py.out
├── incorrect_python_file.py
└── utils
│ ├── find_docstrings
│ ├── dictionary.py
│ ├── function_docstrings.py
│ └── module_docstrings.py
│ ├── find_nothing
│ └── README.md
│ ├── find_recursive_files
│ ├── file_one.py
│ ├── file_two.txt
│ └── inner_directory
│ │ ├── inner_file_one.py
│ │ ├── inner_file_two.txt
│ │ └── inner_inner_directory
│ │ ├── inner_inner_file_one.py
│ │ └── inner_inner_file_one.txt
│ └── find_underscore_files
│ ├── ____file_five.py
│ ├── ___file_four.txt
│ ├── __file_three.py
│ ├── _file_two.py
│ └── file_one.py
├── test_config.py
├── test_conflicting_formatters.py
├── test_exceptions.py
├── test_formatter.py
├── test_formatting.py
├── test_run.py
└── test_utils.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore =
3 | E203,
4 | W503,
5 | W293,
6 | max-line-length=88
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /tests/data/format/newlines/* -text
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [DanielNoord, Pierre-Sassoulas]
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "pip"
4 | directory: "/" # Location of package manifests
5 | schedule:
6 | interval: "daily"
7 | labels:
8 | - "dependency"
9 | reviewers:
10 | - "danielnoord"
11 | - package-ecosystem: "github-actions"
12 | directory: "/"
13 | schedule:
14 | interval: "daily"
15 | labels:
16 | - "dependency"
17 | reviewers:
18 | - "danielnoord"
19 |
--------------------------------------------------------------------------------
/.github/workflows/checks.yaml:
--------------------------------------------------------------------------------
1 | name: Checks
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | docs-build:
11 | name: Docs / Build
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check out code from GitHub
15 | uses: actions/checkout@v4.2.2
16 | with:
17 | fetch-depth: 2
18 | - name: Set up Python 3.13
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: "3.13"
22 | - name: Install dependencies
23 | run: pip install -U -r docs/requirements-doc.txt
24 | - name: Make docs
25 | run: |
26 | cd docs
27 | make html
28 | - name: Compare the diff
29 | run: |
30 | git diff > DIFF
31 | if [ -s DIFF ]; then
32 | echo "Documentation is not up to date."
33 | echo "Please perform the following commands locally and then re-commit."
34 | echo "pip install -e ."
35 | echo "cd docs"
36 | echo "make html"
37 | exit 1
38 | else
39 | echo "Documentation is up to date."
40 | fi
41 |
--------------------------------------------------------------------------------
/.github/workflows/primer.yaml:
--------------------------------------------------------------------------------
1 | # Most of this is inspired by the mypy primer
2 |
3 | name: Primer
4 |
5 | on:
6 | pull_request:
7 |
8 | jobs:
9 | primer-run:
10 | name: Run
11 | runs-on: ubuntu-latest
12 | permissions: write-all
13 | steps:
14 | - name: Check out working version
15 | uses: actions/checkout@v4.2.2
16 | with:
17 | fetch-depth: 2
18 | - name: Check out changeable version
19 | uses: actions/checkout@v4.2.2
20 | with:
21 | path: program_to_test
22 | fetch-depth: 0
23 | - name: Set up Python 3.13
24 | uses: actions/setup-python@v5
25 | with:
26 | python-version: "3.13"
27 | - name: Install dependencies
28 | run: pip install -U -r requirements.txt
29 | - name: Run primer
30 | shell: bash
31 | run: |
32 | cd program_to_test
33 | echo "new commit"
34 | git rev-list --format=%s --max-count=1 $GITHUB_SHA
35 |
36 | MERGE_BASE=$(git merge-base $GITHUB_SHA origin/main)
37 | git checkout -b base_commit $MERGE_BASE
38 | echo "base commit"
39 | git rev-list --format=%s --max-count=1 base_commit
40 |
41 | echo "installing changeable version"
42 | pip install -e .
43 | cd ..
44 |
45 | python -m pydocstringformatter._testutils.primer.primer --prepare
46 | python -m pydocstringformatter._testutils.primer.primer --step-one
47 |
48 | cd program_to_test
49 | git checkout $GITHUB_SHA
50 | cd ..
51 | python -m pydocstringformatter._testutils.primer.primer --step-two
52 | - name: Save PR number and copy fulldiff.txt
53 | run: |
54 | echo ${{ github.event.pull_request.number }} | tee pr_number.txt
55 | cp .pydocstringformatter_primer_tests/fulldiff.txt fulldiff.txt
56 | - name: Upload primer diff and PR number
57 | uses: actions/upload-artifact@v4.6.2
58 | with:
59 | name: primer_diffs
60 | path: |
61 | pr_number.txt
62 | fulldiff.txt
63 |
--------------------------------------------------------------------------------
/.github/workflows/primer_comment.yaml:
--------------------------------------------------------------------------------
1 | # Most of this is inspired by the mypy primer
2 |
3 | name: Comment with primer diff
4 |
5 | on:
6 | workflow_run:
7 | workflows: [Primer]
8 | types:
9 | - completed
10 |
11 | permissions:
12 | contents: read
13 | pull-requests: write
14 |
15 | jobs:
16 | primer-comment:
17 | name: Run
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Download diffs
21 | uses: actions/github-script@v7
22 | with:
23 | script: |
24 | const fs = require('fs');
25 | const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
26 | owner: context.repo.owner,
27 | repo: context.repo.repo,
28 | run_id: ${{ github.event.workflow_run.id }},
29 | });
30 | const [matchArtifact] = artifacts.data.artifacts.filter((artifact) =>
31 | artifact.name == "primer_diffs");
32 | const download = await github.rest.actions.downloadArtifact({
33 | owner: context.repo.owner,
34 | repo: context.repo.repo,
35 | artifact_id: matchArtifact.id,
36 | archive_format: "zip",
37 | });
38 | fs.writeFileSync("diff.zip", Buffer.from(download.data));
39 | - run: unzip diff.zip
40 | - name: Post comment
41 | id: post-comment
42 | uses: actions/github-script@v7
43 | with:
44 | github-token: ${{ secrets.GITHUB_TOKEN }}
45 | script: |
46 | const fs = require('fs')
47 | const data = fs.readFileSync('fulldiff.txt', { encoding: 'utf8' })
48 | console.log("Diff from primer:")
49 | console.log(data)
50 | let body
51 | if (data.trim()) {
52 | body = 'Diff from the primer, showing the effect of this PR on open source code:\n' + data
53 | } else {
54 | body = 'According to the primer, this change has no effect on the checked open source code. 🤖🎉'
55 | }
56 | const prNumber = parseInt(fs.readFileSync("pr_number.txt", { encoding: "utf8" }))
57 | await github.rest.issues.createComment({
58 | issue_number: prNumber,
59 | owner: context.repo.owner,
60 | repo: context.repo.repo,
61 | body
62 | })
63 | return prNumber
64 | - name: Hide old comments
65 | # Taken from mypy primer
66 | # v0.3.0
67 | uses: kanga333/comment-hider@c12bb20b48aeb8fc098e35967de8d4f8018fffdf
68 | with:
69 | github_token: ${{ secrets.GITHUB_TOKEN }}
70 | leave_visible: 1
71 | issue_number: ${{ steps.post-comment.outputs.result }}
72 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish-to-pypi:
9 | name: Publish to pypi
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Check out code from GitHub
13 | uses: actions/checkout@v4.2.2
14 | - name: Set up Python 3.11
15 | uses: actions/setup-python@v5
16 | with:
17 | python-version: "3.11"
18 | - name: Install dependencies
19 | run: |
20 | pip install -U -r requirements.txt
21 | pip install -U build
22 | - name: Build package
23 | run: python -m build
24 | - name: Publish to PyPI
25 | uses: pypa/gh-action-pypi-publish@release/v1
26 | with:
27 | user: __token__
28 | password: ${{ secrets.PYPI_API_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | tests-linux:
11 | name: Run / ${{ matrix.python-version }} / Linux
12 | runs-on: ubuntu-latest
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
17 | steps:
18 | - name: Check out code from GitHub
19 | uses: actions/checkout@v4.2.2
20 | - name: Set up Python ${{ matrix.python-version }}
21 | id: python
22 | uses: actions/setup-python@v5
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install dependencies
26 | run: pip install -U -r requirements.txt
27 | - name: Run pytest
28 | run: pytest -vv --cov
29 | - name: Upload coverage artifact
30 | uses: actions/upload-artifact@v4.6.2
31 | with:
32 | include-hidden-files: true
33 | name: coverage-linux-${{ matrix.python-version }}
34 | path: .coverage
35 |
36 | tests-windows:
37 | name: Run / ${{ matrix.python-version }} / Windows
38 | runs-on: windows-latest
39 | strategy:
40 | fail-fast: false
41 | matrix:
42 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
43 | steps:
44 | - name: Check out code from GitHub
45 | uses: actions/checkout@v4.2.2
46 | - name: Set temp directory
47 | run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV
48 | # Workaround to set correct temp directory on Windows
49 | # https://github.com/actions/virtual-environments/issues/712
50 | - name: Set up Python ${{ matrix.python-version }}
51 | id: python
52 | uses: actions/setup-python@v5
53 | with:
54 | python-version: ${{ matrix.python-version }}
55 | - name: Install dependencies
56 | run: pip install -U -r requirements.txt
57 | - name: Run pytest
58 | run: pytest -vv --cov
59 | - name: Upload coverage artifact
60 | uses: actions/upload-artifact@v4.6.2
61 | with:
62 | include-hidden-files: true
63 | name: coverage-windows-${{ matrix.python-version }}
64 | path: .coverage
65 |
66 | coverage:
67 | name: Process / Coverage
68 | runs-on: ubuntu-latest
69 | needs: ["tests-linux", "tests-windows"]
70 | steps:
71 | - name: Check out code from GitHub
72 | uses: actions/checkout@v4.2.2
73 | - name: Set up Python 3.11
74 | id: python
75 | uses: actions/setup-python@v5
76 | with:
77 | python-version: "3.11"
78 | - name: Install dependencies
79 | run: pip install -U -r requirements-coverage.txt
80 | - name: Download all coverage artifacts
81 | uses: actions/download-artifact@v4.3.0
82 | - name: Combine Linux coverage results
83 | run: |
84 | coverage combine coverage-linux*/.coverage
85 | coverage xml -o coverage-linux.xml
86 | - uses: codecov/codecov-action@v5
87 | with:
88 | fail_ci_if_error: true
89 | verbose: true
90 | flags: linux
91 | files: coverage-linux.xml
92 | token: ${{ secrets.CODECOV_TOKEN }}
93 | - name: Combine Windows coverage results
94 | run: |
95 | coverage combine coverage-windows*/.coverage
96 | coverage xml -o coverage-windows.xml
97 | - uses: codecov/codecov-action@v5
98 | with:
99 | fail_ci_if_error: true
100 | verbose: true
101 | flags: windows
102 | files: coverage-windows.xml
103 | token: ${{ secrets.CODECOV_TOKEN }}
104 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .pydocstringformatter_primer_tests/
2 | pydocstringformatter.egg-info
3 | dist/
4 | docs/_build
5 |
6 | .coverage
7 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: trailing-whitespace
6 | exclude: "tests/data/format/no_whitespace_stripper|tests/data/format/whitespace_stripper|tests/data/format/quotes_type|tests/data/format/newlines|tests/test_config.py"
7 | - id: end-of-file-fixer
8 | exclude: "tests/data/format/newlines"
9 | - id: check-yaml
10 | - id: check-toml
11 | exclude: &test-data "tests/data"
12 | - repo: https://github.com/PyCQA/autoflake
13 | rev: v2.3.1
14 | hooks:
15 | - id: autoflake
16 | exclude: *test-data
17 | args:
18 | - --in-place
19 | - --remove-all-unused-imports
20 | - --expand-star-imports
21 | - --remove-duplicate-keys
22 | - --remove-unused-variables
23 | - repo: https://github.com/asottile/pyupgrade
24 | rev: v3.20.0
25 | hooks:
26 | - id: pyupgrade
27 | args: [--py38-plus]
28 | - repo: https://github.com/PyCQA/isort
29 | rev: 6.0.1
30 | hooks:
31 | - id: isort
32 | - repo: https://github.com/psf/black
33 | rev: 25.1.0
34 | hooks:
35 | - id: black
36 | - repo: https://github.com/PyCQA/flake8
37 | rev: 7.2.0
38 | hooks:
39 | - id: flake8
40 | exclude: *test-data
41 | - repo: https://github.com/pylint-dev/pylint
42 | rev: v3.3.7
43 | hooks:
44 | - id: pylint
45 | exclude: *test-data
46 | args: ["--disable=import-error, cyclic-import"]
47 | - repo: https://github.com/pre-commit/mirrors-mypy
48 | rev: v1.16.0
49 | hooks:
50 | - id: mypy
51 | args: [--config-file=pyproject.toml]
52 | exclude: *test-data
53 | additional_dependencies:
54 | [pytest-stub==1.1.0, types-docutils~=0.17.5, sphinx~=4.4]
55 | - repo: https://github.com/DanielNoord/pydocstringformatter
56 | rev: v0.7.3
57 | hooks:
58 | - id: pydocstringformatter
59 | exclude: *test-data
60 | args: []
61 | - repo: https://github.com/pre-commit/mirrors-prettier
62 | rev: v4.0.0-alpha.8
63 | hooks:
64 | - id: prettier
65 | exclude: *test-data
66 | args: [--prose-wrap=always, --print-width=88]
67 |
--------------------------------------------------------------------------------
/.pre-commit-hooks.yaml:
--------------------------------------------------------------------------------
1 | - id: pydocstringformatter
2 | name: pydocstringformatter
3 | entry: pydocstringformatter --write --quiet
4 | language: python
5 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
2 |
3 | version: 2
4 |
5 | python:
6 | install:
7 | - requirements: docs/requirements-doc.txt
8 |
9 | sphinx:
10 | builder: html
11 | fail_on_warning: true
12 | configuration: docs/conf.py
13 |
14 | build:
15 | os: "ubuntu-lts-latest"
16 | tools:
17 | python: "latest"
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Daniël van Noord
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pypi.python.org/pypi/pydocstringformatter/)
2 | [](https://codecov.io/gh/DanielNoord/pydocstringformatter)
3 | [](https://github.com/DanielNoord/pydocstringformatter/actions/workflows/tests.yaml)
4 | [](https://results.pre-commit.ci/latest/github/DanielNoord/pydocstringformatter/main)
5 | [](https://pydocstringformatter.readthedocs.io/en/latest/?badge=latest)
6 |
7 | # Pydocstringformatter
8 |
9 | A tool to automatically format Python docstrings to follow recommendations from
10 | [`PEP 8`](https://www.python.org/dev/peps/pep-0008/) and
11 | [`PEP 257`](https://www.python.org/dev/peps/pep-0257/) (or other supported style
12 | guides.)
13 |
14 | See [What it does](#what-it-does) for currently supported auto-formatting.
15 |
16 | **Rationale**
17 |
18 | This project is heavily inspired by
19 | [`docformatter`](https://github.com/PyCQA/docformatter).
20 |
21 | When this project was started `docformatter` did not meet all of the requirements the
22 | [`pylint`](https://github.com/PyCQA/pylint) project had for its docstring formatter and
23 | was no longer actively maintained (this has changed since then). Therefore, some
24 | contributors of `pylint` got together and started working on our own formatter to
25 | fulfill our needs.
26 |
27 | When asked we defined the objective of the tool as:
28 |
29 | _"A docstring formatter that follows PEP8 and PEP257 but makes some of the more
30 | 'controversial' elements of the PEPs optional"_
31 |
32 | See
33 | [the original answer](https://github.com/DanielNoord/pydocstringformatter/issues/38).
34 |
35 | As such, the biggest difference between the two is that `pydocstringformatter` fixes
36 | some of the open issues we found in `docformatter`. In general, the output of both
37 | formatters (and any other docstring formatter) should be relatively similar.
38 |
39 | ## How to install
40 |
41 | ```shell
42 | pip install pydocstringformatter
43 | ```
44 |
45 | ## Usage
46 |
47 | [`Click here`](https://pydocstringformatter.readthedocs.io/en/latest/usage.html) for a
48 | full Usage overview.
49 |
50 | ### Configuration
51 |
52 | Pydocstringformatter will also read any configuration added to the
53 | `[tool.pydocstringformatter]` section of a `pyproject.toml` file.
54 |
55 | For example:
56 |
57 | ```toml
58 | [tool.pydocstringformatter]
59 | write = true
60 | exclude = "**/my_dir/**,**/my_other_dir/**"
61 | # Or:
62 | exclude = ["**/my_dir/**", "**/my_other_dir/**"]
63 | strip-whitespaces = true
64 | split-summary-body = false
65 | numpydoc-section-hyphen-length = false
66 | ```
67 |
68 | #### Style
69 |
70 | Pydocstringformatter can be configured to use a specific style. The default is `pep257`
71 | but we support other styles as well. These can also be used at the same time. For
72 | example with:
73 |
74 | ```console
75 | pydocstringformatter --style=pep257 --style=numpydoc myfile.py
76 | ```
77 |
78 | ## Pre-commit
79 |
80 | Pydocstringformatter can also be used as a [pre-commit hook](https://pre-commit.com).
81 | Add the following to your `.pre-commit-config.yaml` file:
82 |
83 | ```yaml
84 | - repo: https://github.com/DanielNoord/pydocstringformatter
85 | rev: SPECIFY VERSION HERE
86 | hooks:
87 | - id: pydocstringformatter
88 | ```
89 |
90 | ## What it does
91 |
92 | The following examples show some of the changes pydocstringformatter will apply. For a
93 | full overview of all potential changes you can check out the
94 | [`Usage`](https://pydocstringformatter.readthedocs.io/en/latest/usage.html) page which
95 | shows an up to date list of all formatters and their description.
96 |
97 | ```python
98 | # Bad
99 | '''
100 | my docstring'''
101 |
102 | """ my
103 | multi-line docstring """
104 |
105 | """my title
106 | ===========
107 |
108 | my docstring
109 | """
110 |
111 |
112 | # Good
113 | """My docstring."""
114 |
115 | """My
116 | multi-line docstring.
117 | """
118 |
119 | """My title
120 | ===========
121 |
122 | My docstring
123 | """
124 |
125 | # With --summary-quotes-same-line
126 | # Bad
127 | """
128 | My
129 | multi-line docstring
130 | """
131 |
132 | # Good
133 | """My
134 | multi-line docstring
135 | """
136 | ```
137 |
138 | ## Development
139 |
140 | For development and contributing guidelines please see
141 | [`Development`](https://pydocstringformatter.readthedocs.io/en/latest/development.html).
142 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch:
4 | default:
5 | target: 100%
6 | project:
7 | default:
8 | target: 100%
9 | comment:
10 | layout: "reach, diff, flags, files"
11 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/_ext/usage_page.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | from sphinx import application
4 |
5 |
6 | def write_usage_page() -> None:
7 | """Write docs/usage.rst."""
8 |
9 | # Get the help message
10 | with subprocess.Popen(
11 | ["pydocstringformatter", "-h"], stdout=subprocess.PIPE
12 | ) as proc:
13 | assert proc.stdout
14 | out = proc.stdout.readlines()
15 |
16 | # Add correct indentation for the code block
17 | help_message = " ".join(i.decode("utf-8") for i in out)
18 | # Remove indentation from completely empty lines
19 | help_message = help_message.replace(" \n", "\n")
20 |
21 | with open("usage.rst", mode="w", encoding="utf-8") as file:
22 | file.write(
23 | f"""Usage
24 | =====
25 |
26 | Current usage of ``pydocstringformatter``:
27 |
28 | .. code-block:: shell
29 |
30 | {help_message}"""
31 | )
32 |
33 |
34 | def setup(_: application.Sphinx) -> None:
35 | """Required function to register the Sphinx extension."""
36 | write_usage_page()
37 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=invalid-name
2 | # flake8: noqa
3 |
4 | """Configuration file for the Sphinx documentation builder."""
5 |
6 | # -- Path setup --------------------------------------------------------------
7 |
8 | # Add parent directory so we can import pydocstringformatter
9 | import os
10 | import sys
11 |
12 | sys.path.insert(0, os.path.abspath(".."))
13 |
14 | # pylint: disable-next=import-error, wrong-import-position
15 | import pydocstringformatter # noqa: E402
16 |
17 | # -- Project information -----------------------------------------------------
18 |
19 | project = "pydocstringformatter"
20 | copyright = "2022, Github Contributors" # pylint: disable=redefined-builtin
21 | author = "Github Contributors"
22 | release = pydocstringformatter.__version__
23 |
24 | # -- General configuration ---------------------------------------------------
25 |
26 | extensions = ["myst_parser", "docs._ext.usage_page"]
27 | myst_heading_anchors = 2
28 | source_suffix = [".rst", ".md"]
29 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
30 |
31 | # -- Options for HTML output -------------------------------------------------
32 |
33 | html_theme = "furo"
34 | html_title = "pydocstringformatter"
35 |
36 | html_theme_options = {
37 | "light_css_variables": {"font-size-sidebar-brand-text": "10px"},
38 | "footer_icons": [
39 | {
40 | "name": "GitHub",
41 | "url": "https://github.com/danielnoord/pydocstringformatter/",
42 | "html": """
43 |
46 | """,
47 | "class": "",
48 | },
49 | ],
50 | }
51 |
--------------------------------------------------------------------------------
/docs/development.rst:
--------------------------------------------------------------------------------
1 | Development
2 | ===========
3 |
4 | Linting and checks
5 | ------------------
6 |
7 | Use ``pip`` to install ``pre-commit`` and then use ``pre-commit install`` to
8 | install the pre-commit hook for the repository.
9 |
10 | Creating a new formatter
11 | ------------------------
12 |
13 | - Implement a Formatter by inheriting from ``pydocstringformatter.formatting.Formatter``.
14 | - Add your new formatter to ``pydocstringformatter.formatting.FORMATTERS``.
15 | - Write a clear docstring because this will be user-facing: it's what will be seen in
16 | the help message for the formatter's command line option.
17 | - Choose a proper name because this will be user-facing: the name will be used to turn
18 | the formatter on and off via the command line or config files.
19 | - Rebuild the documentation by running:
20 |
21 | .. code-block:: shell
22 |
23 | cd docs
24 | make html
25 |
26 |
27 | Testing
28 | -------
29 |
30 | To run all the tests:
31 |
32 | .. code-block:: shell
33 |
34 | pytest
35 |
36 | To create test for a specific formatting option use the ``tests/data/format`` directory.
37 | For each ``.py`` file create a ``.py.out`` file with the expected output after formatting.
38 | The test suite will automatically pickup the new tests.
39 |
40 | To only run a specific test from that directory, for example
41 | ``tests/data/format/multi_line/class_docstring.py``, use:
42 |
43 | .. code-block:: shell
44 |
45 | pytest -k multi_line-class_docstring
46 |
47 |
48 | Primer
49 | -------
50 |
51 | To check if any changes create any unforeseen regressions all pull requests are tested
52 | with our primer. This process is heavily inspired on other open source projects and our
53 | own primer is based on the one used by `mypy `_.
54 |
55 | You can also run the primer locally with minimal set-up. First you will need to make
56 | a duplicate of your ``pydocstringformatter`` directory in a new ``./program_to_test``
57 | directory. This is required so that the primer can check out multiple versions of the
58 | program and compare the difference.
59 |
60 | The next step is to follow the bash script in the ``Run primer`` step in the ``primer``
61 | workflow file, found at ``.github/workflows/primer.yaml``. This should allow you to run
62 | all necessary steps locally.
63 |
64 | The final output of the primer run can be found in ``.pydocstringformatter_primer_tests/fulldiff.txt``
65 |
66 | New projects to run the primer over can be added to the ``pydocstringformatter/testutils/primer/packages.py``
67 | file.
68 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :hidden:
3 |
4 | Home
5 | usage
6 | development
7 |
8 | .. include:: ../README.md
9 | :parser: myst_parser.sphinx_
10 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.https://www.sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/requirements-doc.txt:
--------------------------------------------------------------------------------
1 | -e .
2 | myst-parser~=4.0
3 | sphinx~=8.2.3
4 | furo==2024.8.6
5 | types-docutils~=0.21
6 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | Current usage of ``pydocstringformatter``:
5 |
6 | .. code-block:: shell
7 |
8 | usage: pydocstringformatter [-h] [-w] [--quiet] [-v] [--exclude EXCLUDE]
9 | [--exit-code] [--max-summary-lines int]
10 | [--summary-quotes-same-line]
11 | [--max-line-length int]
12 | [--style {pep257,numpydoc} [{pep257,numpydoc} ...]]
13 | [--strip-whitespaces --no-strip-whitespaces]
14 | [--split-summary-body --no-split-summary-body]
15 | [--numpydoc-section-order --no-numpydoc-section-order]
16 | [--numpydoc-name-type-spacing --no-numpydoc-name-type-spacing]
17 | [--numpydoc-section-spacing --no-numpydoc-section-spacing]
18 | [--numpydoc-section-hyphen-length --no-numpydoc-section-hyphen-length]
19 | [--linewrap-full-docstring --no-linewrap-full-docstring]
20 | [--beginning-quotes --no-beginning-quotes]
21 | [--closing-quotes --no-closing-quotes]
22 | [--capitalize-first-letter --no-capitalize-first-letter]
23 | [--final-period --no-final-period]
24 | [--quotes-type --no-quotes-type]
25 | [files ...]
26 |
27 | positional arguments:
28 | files The directory or files to format.
29 |
30 | options:
31 | -h, --help show this help message and exit
32 | -w, --write Write the changes to file instead of printing the
33 | diffs to stdout.
34 | --quiet Do not print any logging or status messages to stdout.
35 | -v, --version Show version number and exit.
36 |
37 | configuration:
38 | --exclude EXCLUDE A comma separated list of glob patterns of file path
39 | names not to be formatted.
40 | --exit-code Turn on if the program should exit with bitwise exit
41 | codes. 0 = No changes, 32 = Changed files or printed
42 | diff.
43 | --max-summary-lines int
44 | The maximum numbers of lines a summary can span. The
45 | default value is 1.
46 | --summary-quotes-same-line
47 | Force the start of a multi-line docstring to be on the
48 | same line as the opening quotes. Similar to how this
49 | is enforced for single line docstrings.
50 | --max-line-length int
51 | Maximum line length of docstrings.
52 | --style {pep257,numpydoc} [{pep257,numpydoc} ...]
53 | Docstring styles that are used in the project. Can be
54 | more than one.
55 |
56 | default formatters:
57 | these formatters are turned on by default
58 |
59 | --strip-whitespaces, --no-strip-whitespaces
60 | Activate or deactivate strip-whitespaces: Strip 1)
61 | docstring start, 2) docstring end and 3) end of line.
62 | Styles: default. (default: True)
63 | --split-summary-body, --no-split-summary-body
64 | Activate or deactivate split-summary-body: Split the
65 | summary and body of a docstring based on a period and
66 | max length. The maximum length of a summary can be set
67 | with the --max-summary-lines option. Styles: pep257.
68 | (default: True)
69 | --numpydoc-section-order, --no-numpydoc-section-order
70 | Activate or deactivate numpydoc-section-order: Change
71 | section order to match numpydoc guidelines. Styles:
72 | numpydoc. (default: True)
73 | --numpydoc-name-type-spacing, --no-numpydoc-name-type-spacing
74 | Activate or deactivate numpydoc-name-type-spacing:
75 | Ensure proper spacing around the colon separating
76 | names from types. Styles: numpydoc. (default: True)
77 | --numpydoc-section-spacing, --no-numpydoc-section-spacing
78 | Activate or deactivate numpydoc-section-spacing:
79 | Ensure proper spacing between sections. Styles:
80 | numpydoc. (default: True)
81 | --numpydoc-section-hyphen-length, --no-numpydoc-section-hyphen-length
82 | Activate or deactivate numpydoc-section-hyphen-length:
83 | Ensure hyphens after section header lines are proper
84 | length. Styles: numpydoc. (default: True)
85 | --beginning-quotes, --no-beginning-quotes
86 | Activate or deactivate beginning-quotes: Fix the
87 | position of the opening quotes. Styles: default.
88 | (default: True)
89 | --closing-quotes, --no-closing-quotes
90 | Activate or deactivate closing-quotes: Fix the
91 | position of the closing quotes. Styles: default.
92 | (default: True)
93 | --capitalize-first-letter, --no-capitalize-first-letter
94 | Activate or deactivate capitalize-first-letter:
95 | Capitalize the first letter of the docstring if
96 | appropriate. Styles: default. (default: True)
97 | --final-period, --no-final-period
98 | Activate or deactivate final-period: Add a period to
99 | the end of single line docstrings and summaries.
100 | Styles: default. (default: True)
101 | --quotes-type, --no-quotes-type
102 | Activate or deactivate quotes-type: Change all opening
103 | and closing quotes to be triple quotes. Styles:
104 | default. (default: True)
105 |
106 | optional formatters:
107 | these formatters are turned off by default
108 |
109 | --linewrap-full-docstring, --no-linewrap-full-docstring
110 | Activate or deactivate linewrap-full-docstring:
111 | Linewrap the docstring by the pre-defined line length.
112 | Styles: default. (default: False)
113 |
--------------------------------------------------------------------------------
/pydocstringformatter/__init__.py:
--------------------------------------------------------------------------------
1 | # pylint: disable = import-outside-toplevel
2 | from __future__ import annotations
3 |
4 | import sys
5 |
6 | from pydocstringformatter._utils.exceptions import (
7 | ParsingError,
8 | PydocstringFormatterError,
9 | )
10 |
11 | __version__ = "0.8.0-dev"
12 |
13 |
14 | def run_docstring_formatter(argv: list[str] | None = None) -> None:
15 | """Run the formatter."""
16 | from pydocstringformatter.run import _Run
17 |
18 | _Run(argv or sys.argv[1:])
19 |
20 |
21 | __all__ = ("run_docstring_formatter", "PydocstringFormatterError", "ParsingError")
22 |
--------------------------------------------------------------------------------
/pydocstringformatter/__main__.py:
--------------------------------------------------------------------------------
1 | import pydocstringformatter
2 |
3 | pydocstringformatter.run_docstring_formatter()
4 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/pydocstringformatter/_configuration/__init__.py
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/arguments_manager.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 |
5 | from pydocstringformatter._configuration import (
6 | command_line_parsing,
7 | formatter_options,
8 | toml_parsing,
9 | )
10 | from pydocstringformatter._configuration.validators import VALIDATORS
11 | from pydocstringformatter._formatting.base import Formatter
12 |
13 |
14 | class ArgumentsManager:
15 | """Handler for arugments adding and parsing."""
16 |
17 | def __init__(self, version: str, formatters: list[Formatter]) -> None:
18 | # Initialize instance attributes for argument parsing
19 | self.parser = argparse.ArgumentParser(prog="pydocstringformatter")
20 | self.namespace = argparse.Namespace()
21 |
22 | self.formatters = formatters
23 | """List of loaded formatters."""
24 |
25 | # First register all argument groups, then add arguments
26 | self.configuration_group = self.parser.add_argument_group("configuration")
27 | self.default_formatters_group = self.parser.add_argument_group(
28 | "default formatters", "these formatters are turned on by default"
29 | )
30 | self.optional_formatters_group = self.parser.add_argument_group(
31 | "optional formatters", "these formatters are turned off by default"
32 | )
33 |
34 | # Register all arguments
35 | self.register_arguments(version)
36 | formatter_options.register_arguments_formatters(
37 | self.default_formatters_group,
38 | self.optional_formatters_group,
39 | self.formatters,
40 | )
41 |
42 | def register_arguments(self, version: str) -> None:
43 | """Register all standard arguments on the parser."""
44 | self.parser.add_argument(
45 | "files", nargs="*", type=str, help="The directory or files to format."
46 | )
47 |
48 | self.parser.add_argument(
49 | "-w",
50 | "--write",
51 | action="store_true",
52 | help="Write the changes to file instead of printing the diffs to stdout.",
53 | )
54 |
55 | self.parser.add_argument(
56 | "--quiet",
57 | action="store_true",
58 | help="Do not print any logging or status messages to stdout.",
59 | )
60 |
61 | self.parser.add_argument(
62 | "-v",
63 | "--version",
64 | action="version",
65 | version=version,
66 | help="Show version number and exit.",
67 | )
68 |
69 | self.configuration_group.add_argument(
70 | "--exclude",
71 | action="store",
72 | default=[""],
73 | type=VALIDATORS["csv"],
74 | help=(
75 | "A comma separated list of glob patterns of "
76 | "file path names not to be formatted."
77 | ),
78 | )
79 |
80 | self.configuration_group.add_argument(
81 | "--exit-code",
82 | action="store_true",
83 | default=False,
84 | help=(
85 | "Turn on if the program should exit with bitwise exit codes. "
86 | "0 = No changes, 32 = Changed files or printed diff."
87 | ),
88 | )
89 |
90 | self.configuration_group.add_argument(
91 | "--max-summary-lines",
92 | action="store",
93 | default=1,
94 | type=int,
95 | help=(
96 | "The maximum numbers of lines a summary can span. "
97 | "The default value is 1."
98 | ),
99 | metavar="int",
100 | )
101 |
102 | self.configuration_group.add_argument(
103 | "--summary-quotes-same-line",
104 | action="store_true",
105 | help=(
106 | "Force the start of a multi-line docstring to be on the "
107 | "same line as the opening quotes. Similar to how this is enforced "
108 | "for single line docstrings."
109 | ),
110 | )
111 |
112 | self.configuration_group.add_argument(
113 | "--max-line-length",
114 | action="store",
115 | default=88,
116 | type=int,
117 | help="Maximum line length of docstrings.",
118 | metavar="int",
119 | )
120 |
121 | self.configuration_group.add_argument(
122 | "--style",
123 | action="extend",
124 | type=str,
125 | nargs="+",
126 | choices=["pep257", "numpydoc"],
127 | help="Docstring styles that are used in the project. Can be more than one.",
128 | )
129 |
130 | def parse_options(
131 | self,
132 | argv: list[str],
133 | ) -> None:
134 | """Load all default option values.
135 |
136 | The order of parsing is:
137 | 1. configuration files, 2. command line arguments, 3. set default values.
138 | """
139 | # pylint: disable=protected-access
140 | toml_parsing.parse_toml_file(self.parser, self.namespace)
141 |
142 | command_line_parsing.parse_command_line_arguments(
143 | self.parser, self.namespace, argv
144 | )
145 |
146 | # 'style' uses the 'extend' action. If we use a normal default value,
147 | # and the default gets supplied as well style == ["pep257", "pep257"].
148 | if self.namespace.style is None:
149 | self.namespace.style = ["pep257"]
150 |
151 | def print_help(self) -> None:
152 | """Print the help or usage message."""
153 | self.parser.print_help()
154 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/boolean_option_action.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 | from collections.abc import Sequence
5 | from typing import Any, Literal
6 |
7 |
8 | class BooleanOptionalAction(argparse.Action):
9 | """Boolean action that combines the '--no' argument.
10 |
11 | This action is directly copied from argparse with minor modifications.
12 | This action class is only available in 3.9+. Hence the need to backport it.
13 | """
14 |
15 | # pylint: disable-next=too-many-arguments,too-many-positional-arguments
16 | def __init__(
17 | self,
18 | option_strings: Sequence[str],
19 | dest: str,
20 | default: bool,
21 | type: Literal[None] = None, # pylint: disable=redefined-builtin
22 | choices: Literal[None] = None,
23 | required: bool = False,
24 | help: str | None = None, # pylint: disable=redefined-builtin
25 | metavar: Literal[None] = None,
26 | ) -> None:
27 | # Non-argparse changes
28 | assert help, "All BooleanOptionalAction's should have a help message."
29 |
30 | # Rest of implementation directly copied from argparse, expect for the asserts
31 | _option_strings = []
32 | for option_string in option_strings:
33 | _option_strings.append(option_string)
34 |
35 | assert option_string.startswith("--")
36 | option_string = "--no-" + option_string[2:]
37 | _option_strings.append(option_string)
38 |
39 | assert help is not None and default is not None
40 | help += " (default: %(default)s)"
41 |
42 | super().__init__(
43 | option_strings=_option_strings,
44 | dest=dest,
45 | nargs=0,
46 | default=default,
47 | type=type,
48 | choices=choices,
49 | required=required,
50 | help=help,
51 | metavar=metavar,
52 | )
53 |
54 | def __call__(
55 | self,
56 | parser: argparse.ArgumentParser,
57 | namespace: argparse.Namespace,
58 | values: str | Sequence[Any] | None,
59 | option_string: str | None = None,
60 | ) -> None:
61 | """Store correct value on the namespace object."""
62 | assert option_string, (
63 | "BooleanOptionalAction can't be a positional argument. "
64 | f"Something is wrong with {self.option_strings[0]}"
65 | )
66 | assert option_string in self.option_strings
67 | setattr(namespace, self.dest, not option_string.startswith("--no-"))
68 |
69 | def format_usage(self) -> str:
70 | """Return usage string."""
71 | return " ".join(self.option_strings)
72 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/command_line_parsing.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 |
5 |
6 | def parse_command_line_arguments(
7 | parser: argparse.ArgumentParser, namespace: argparse.Namespace, args: list[str]
8 | ) -> None:
9 | """Parse all arguments on the provided argument parser."""
10 | parser.parse_known_args(args, namespace)
11 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/formatter_options.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 |
5 | from pydocstringformatter._configuration.boolean_option_action import (
6 | BooleanOptionalAction,
7 | )
8 | from pydocstringformatter._formatting.base import Formatter
9 |
10 |
11 | def register_arguments_formatters(
12 | default_arg_group: argparse._ArgumentGroup,
13 | optional_arg_group: argparse._ArgumentGroup,
14 | formatters: list[Formatter],
15 | ) -> None:
16 | """Register a list of formatters, so they can all be deactivated or activated."""
17 | for formatter in formatters:
18 | arg_group = default_arg_group
19 | if formatter.optional:
20 | arg_group = optional_arg_group
21 |
22 | name = formatter.name
23 | arg_group.add_argument(
24 | formatter.activate_option,
25 | action=BooleanOptionalAction,
26 | dest=name,
27 | help=f"Activate or deactivate {name}: {formatter.__doc__}"
28 | f" Styles: {','.join(formatter.style)}.",
29 | default=not formatter.optional,
30 | )
31 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/toml_parsing.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import argparse
4 | import os
5 | import sys
6 | from typing import Any
7 |
8 | from pydocstringformatter._configuration.boolean_option_action import (
9 | BooleanOptionalAction,
10 | )
11 | from pydocstringformatter._utils.exceptions import TomlParsingError, UnrecognizedOption
12 |
13 | if sys.version_info < (3, 11):
14 | import tomli as tomllib
15 | else:
16 | import tomllib
17 |
18 |
19 | def get_toml_file() -> dict[str, Any] | None:
20 | """See if there is a pyproject.toml and extract the correct section if it exists."""
21 | if os.path.isfile("pyproject.toml"):
22 | with open("pyproject.toml", "rb") as file:
23 | try:
24 | toml_dict = tomllib.load(file)
25 | except tomllib.TOMLDecodeError as exc:
26 | raise TomlParsingError from exc
27 | if tool_section := toml_dict.get("tool", None):
28 | if pydocformat_sect := tool_section.get("pydocstringformatter", None):
29 | assert isinstance(pydocformat_sect, dict)
30 | return pydocformat_sect
31 | return None
32 |
33 |
34 | def parse_toml_option( # pylint: disable=too-many-branches
35 | parser: argparse.ArgumentParser, opt: str, value: Any
36 | ) -> list[str]:
37 | """Parse an options value in the correct argument type for argparse."""
38 | # pylint: disable=protected-access
39 | try:
40 | action = parser._option_string_actions[f"--{opt}"]
41 | except KeyError as exc:
42 | try:
43 | action = parser._option_string_actions[f"-{opt}"]
44 | except KeyError:
45 | raise UnrecognizedOption(f"Don't recognize option {opt}") from exc
46 |
47 | if isinstance(action, BooleanOptionalAction):
48 | if not isinstance(value, bool):
49 | error_msg = f"{{'{value}'}} {type(value)} is not a supported argument for"
50 | error_msg += f" '{opt}', please use either {{true}} or {{false}}."
51 | raise ValueError(error_msg)
52 |
53 | if opt.startswith("no") and f"--{opt[3:]}" in action.option_strings:
54 | opposite_opt = opt[3:]
55 | val = ["false", "true"][value]
56 | opp_val = ["true", "false"][value]
57 | error_msg = (
58 | "TOML file contains an unsupported option "
59 | f"'{opt}: {val}', try using '{opposite_opt}: {opp_val}' instead"
60 | )
61 | raise TomlParsingError(error_msg)
62 |
63 | return [f"--{'no-' if not value else ''}{opt}"]
64 |
65 | if isinstance(action, argparse._StoreTrueAction):
66 | if value is True:
67 | return [action.option_strings[0]]
68 | return []
69 |
70 | if isinstance(action, argparse._StoreAction):
71 | if isinstance(value, int):
72 | value = str(value)
73 | return [action.option_strings[0], value]
74 |
75 | if isinstance(action, argparse._ExtendAction):
76 | out_args = []
77 | if isinstance(value, list):
78 | for item in value:
79 | out_args += [action.option_strings[0], item]
80 | else:
81 | out_args = [action.option_strings[0], value]
82 |
83 | return out_args
84 |
85 | raise NotImplementedError # pragma: no cover
86 |
87 |
88 | def parse_toml_file(
89 | parser: argparse.ArgumentParser, namespace: argparse.Namespace
90 | ) -> None:
91 | """Get and parse the relevant section form a pyproject.toml file."""
92 | if toml_sect := get_toml_file():
93 | arguments: list[str] = []
94 |
95 | for key, value in toml_sect.items():
96 | arguments += parse_toml_option(parser, key, value)
97 |
98 | parser.parse_args(arguments, namespace)
99 |
--------------------------------------------------------------------------------
/pydocstringformatter/_configuration/validators.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from collections.abc import Callable
4 | from typing import Final, List
5 |
6 |
7 | def comma_separated_list_validator(value: str | list[str]) -> list[str]:
8 | """Validate a comma separated list."""
9 | if isinstance(value, list):
10 | return value
11 | return value.split(",")
12 |
13 |
14 | ValidatedTypes = List[str]
15 | VALIDATORS: Final[dict[str, Callable[[str], ValidatedTypes]]] = {
16 | "csv": comma_separated_list_validator
17 | }
18 |
--------------------------------------------------------------------------------
/pydocstringformatter/_formatting/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | __all__ = ["FORMATTERS", "Formatter"]
4 |
5 |
6 | from pydocstringformatter._formatting.base import Formatter
7 | from pydocstringformatter._formatting.formatters_default import (
8 | BeginningQuotesFormatter,
9 | CapitalizeFirstLetterFormatter,
10 | ClosingQuotesFormatter,
11 | FinalPeriodFormatter,
12 | LineWrapperFormatter,
13 | QuotesTypeFormatter,
14 | StripWhitespacesFormatter,
15 | )
16 | from pydocstringformatter._formatting.formatters_numpydoc import (
17 | NumpydocNameColonTypeFormatter,
18 | NumpydocSectionHyphenLengthFormatter,
19 | NumpydocSectionOrderingFormatter,
20 | NumpydocSectionSpacingFormatter,
21 | )
22 | from pydocstringformatter._formatting.formatters_pep257 import (
23 | SplitSummaryAndDocstringFormatter,
24 | )
25 |
26 | # The order of these formatters is important as they are called in order.
27 | # The order is currently:
28 | # String manipulation such as adding extra new lines
29 | # Determine if multi-line or single line and position quotes accordingly
30 | # String manipulation in which being multi-line or single line matters
31 | FORMATTERS: list[Formatter] = [
32 | StripWhitespacesFormatter(),
33 | SplitSummaryAndDocstringFormatter(),
34 | NumpydocSectionOrderingFormatter(),
35 | NumpydocNameColonTypeFormatter(),
36 | NumpydocSectionSpacingFormatter(),
37 | NumpydocSectionHyphenLengthFormatter(),
38 | LineWrapperFormatter(),
39 | BeginningQuotesFormatter(),
40 | ClosingQuotesFormatter(),
41 | CapitalizeFirstLetterFormatter(),
42 | FinalPeriodFormatter(),
43 | QuotesTypeFormatter(),
44 | ]
45 |
--------------------------------------------------------------------------------
/pydocstringformatter/_formatting/_utils.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 |
4 | @functools.lru_cache(maxsize=None)
5 | def is_rst_title(summary: str) -> bool:
6 | """Check if the second line of a summary is one recurring character."""
7 | # If second line is one recurring character we're dealing with a rst title
8 | if not (last_line := summary.splitlines()[-1].lstrip()):
9 | return False
10 | return last_line.count(last_line[0]) == len(last_line)
11 |
--------------------------------------------------------------------------------
/pydocstringformatter/_formatting/formatters_default.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import re
4 | import textwrap
5 | import tokenize
6 | from typing import Literal
7 |
8 | from pydocstringformatter._formatting import _utils
9 | from pydocstringformatter._formatting.base import (
10 | StringAndQuotesFormatter,
11 | StringFormatter,
12 | SummaryFormatter,
13 | )
14 |
15 |
16 | class BeginningQuotesFormatter(StringFormatter):
17 | """Fix the position of the opening quotes."""
18 |
19 | name = "beginning-quotes"
20 | potential_single_line = re.compile(
21 | r"""
22 | ['"]{1,3} # 3 opening quotes
23 | \n\s*.+ # A line with any length of characters
24 | \n\s* # A line with only whitespace
25 | ['"]{1,3} # 3 ending quote
26 | """,
27 | re.X,
28 | )
29 | """Regex pattern to match against a potential single line docstring."""
30 |
31 | def treat_string(self, tokeninfo: tokenize.TokenInfo, _: int) -> str:
32 | new_string = tokeninfo.string
33 | if new_string[3] == "\n":
34 | if (
35 | new_string.count("\n") == 1 # Single line docstring
36 | or self.config.summary_quotes_same_line # Config for multi-line
37 | or self.potential_single_line.match(new_string) # Potential single line
38 | ):
39 | new_string = re.sub(r"\n *", "", new_string, 1)
40 | return new_string
41 |
42 |
43 | class CapitalizeFirstLetterFormatter(StringFormatter):
44 | """Capitalize the first letter of the docstring if appropriate."""
45 |
46 | name = "capitalize-first-letter"
47 | first_letter_re = re.compile(r"""['"]{1,3}\s*(\w)""", re.DOTALL)
48 |
49 | def treat_string(self, tokeninfo: tokenize.TokenInfo, _: int) -> str:
50 | new_string = None
51 | if match := self.first_letter_re.match(tokeninfo.string):
52 | first_letter = match.end() - 1
53 | new_string = (
54 | tokeninfo.string[:first_letter]
55 | + tokeninfo.string[first_letter].upper()
56 | + tokeninfo.string[first_letter + 1 :]
57 | )
58 | return new_string or tokeninfo.string
59 |
60 |
61 | class LineWrapperFormatter(SummaryFormatter):
62 | """Linewrap the docstring by the pre-defined line length."""
63 |
64 | name = "linewrap-full-docstring"
65 | optional = True
66 |
67 | def treat_summary(
68 | self,
69 | summary: str,
70 | indent_length: int,
71 | quotes_length: Literal[1, 3],
72 | description_exists: bool,
73 | ) -> str:
74 | """Wrap the summary of a docstring."""
75 |
76 | line_length = self.config.max_line_length
77 |
78 | # Without a description we need to consider the length including closing quotes
79 | if not description_exists:
80 | # Calculate length without the ending quotes
81 | length_without_ending = indent_length + quotes_length + len(summary)
82 |
83 | # If potential length is less than line length we need to consider ending
84 | # quotes as well for the line length
85 | if length_without_ending < line_length:
86 | # We subtract one more because we don't want a new line with just the
87 | # ending quotes
88 | line_length -= quotes_length + 1
89 |
90 | if not (summary_lines := summary.splitlines()):
91 | summary_lines = [""]
92 |
93 | new_summary = "\n".join(
94 | textwrap.wrap(
95 | summary_lines[0],
96 | width=line_length,
97 | initial_indent=" " * (indent_length + quotes_length),
98 | subsequent_indent=" " * indent_length,
99 | replace_whitespace=True,
100 | )
101 | )[indent_length + quotes_length :]
102 |
103 | if len(summary_lines) > 1:
104 | for line in summary_lines[1:]:
105 | new_summary += "\n"
106 | new_summary += "\n".join(
107 | textwrap.wrap(
108 | line,
109 | width=line_length,
110 | subsequent_indent=" " * indent_length,
111 | replace_whitespace=True,
112 | )
113 | )
114 |
115 | return new_summary
116 |
117 |
118 | class ClosingQuotesFormatter(StringFormatter):
119 | """Fix the position of the closing quotes."""
120 |
121 | name = "closing-quotes"
122 |
123 | def treat_string(self, tokeninfo: tokenize.TokenInfo, _: int) -> str:
124 | """Fix the position of end quotes for multi-line docstrings."""
125 | new_string = tokeninfo.string
126 | if "\n" not in new_string:
127 | # Not a multiline docstring, nothing to do
128 | return new_string
129 | good_end = f"{(tokeninfo.start[1]) * ' '}{(new_string[0]) * 3}"
130 | split_string = new_string.split("\n")
131 |
132 | # Add new line with only quotes
133 | if not new_string.endswith("\n" + good_end):
134 | new_string = new_string[:-3] + "\n" + good_end
135 | # Remove line with only quotes for potential single line string
136 | elif len(split_string) == 2 and split_string[-1] == good_end:
137 | new_string = "\n".join(split_string[:-1]) + tokeninfo.string[0] * 3
138 | return new_string
139 |
140 |
141 | class FinalPeriodFormatter(SummaryFormatter):
142 | """Add a period to the end of single line docstrings and summaries."""
143 |
144 | name = "final-period"
145 | END_OF_SENTENCE_PUNCTUATION = {".", "?", "!", "‽", ":", ";"}
146 |
147 | def treat_summary(
148 | self,
149 | summary: str,
150 | indent_length: int,
151 | quotes_length: Literal[1, 3],
152 | description_exists: bool,
153 | ) -> str:
154 | """Add a period to the end of single-line docstrings and summaries."""
155 | if not summary:
156 | return summary
157 |
158 | if summary[-1] in self.END_OF_SENTENCE_PUNCTUATION:
159 | return summary
160 |
161 | if _utils.is_rst_title(summary):
162 | return summary
163 |
164 | return summary + "."
165 |
166 |
167 | class StripWhitespacesFormatter(StringAndQuotesFormatter):
168 | """Strip 1) docstring start, 2) docstring end and 3) end of line."""
169 |
170 | name = "strip-whitespaces"
171 |
172 | def treat_string(
173 | self,
174 | tokeninfo: tokenize.TokenInfo,
175 | indent_length: int,
176 | quotes: str,
177 | quotes_length: Literal[1, 3],
178 | ) -> str:
179 | """Strip whitespaces."""
180 | lines = tokeninfo.string[quotes_length:-quotes_length].split("\n")
181 | new_lines: list[str] = []
182 |
183 | for index, line in enumerate(lines):
184 | if line == "":
185 | # Remove double white lines
186 | if index and lines[index - 1] == "":
187 | continue
188 |
189 | # On the first line strip from both sides
190 | if index == 0: # pylint: disable=compare-to-zero
191 | new_lines.append(line.lstrip().rstrip())
192 |
193 | # Check last line
194 | elif index == len(lines) - 1:
195 | # If completely whitespace, just return the indent_length
196 | if line.count(" ") == len(line):
197 | new_lines.append(indent_length * " ")
198 | else:
199 | new_lines.append(line)
200 |
201 | # Else, only strip right side
202 | else:
203 | new_lines.append(line.rstrip())
204 |
205 | # Remove a final white line
206 | if len(new_lines) > 3 and new_lines[-2] == "":
207 | new_lines.pop(-2)
208 |
209 | return quotes + "\n".join(new_lines) + quotes
210 |
211 |
212 | class QuotesTypeFormatter(StringAndQuotesFormatter):
213 | """Change all opening and closing quotes to be triple quotes."""
214 |
215 | name = "quotes-type"
216 |
217 | def treat_string(
218 | self,
219 | tokeninfo: tokenize.TokenInfo,
220 | _: int,
221 | __: str,
222 | quotes_length: Literal[1, 3],
223 | ) -> str:
224 | """Change all opening and closing quotes if necessary."""
225 | return f'"""{tokeninfo.string[quotes_length:-quotes_length]}"""'
226 |
--------------------------------------------------------------------------------
/pydocstringformatter/_formatting/formatters_numpydoc.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from collections import OrderedDict
4 |
5 | from pydocstringformatter._formatting.base import NumpydocSectionFormatter
6 |
7 |
8 | class NumpydocSectionOrderingFormatter(NumpydocSectionFormatter):
9 | """Change section order to match numpydoc guidelines."""
10 |
11 | name = "numpydoc-section-order"
12 |
13 | numpydoc_section_order = (
14 | "Summary",
15 | "Parameters",
16 | "Attributes",
17 | "Methods",
18 | "Returns",
19 | "Yields",
20 | "Receives",
21 | "Other Parameters",
22 | "Raises",
23 | "Warns",
24 | "Warnings",
25 | "See Also",
26 | "Notes",
27 | "References",
28 | "Examples",
29 | )
30 |
31 | def treat_sections(
32 | self, sections: OrderedDict[str, list[str]]
33 | ) -> OrderedDict[str, list[str]]:
34 | """Sort the numpydoc sections into the numpydoc order."""
35 | for sec_name in reversed(self.numpydoc_section_order):
36 | try:
37 | sections.move_to_end(sec_name, last=False)
38 | except KeyError:
39 | pass
40 | return sections
41 |
42 |
43 | class NumpydocNameColonTypeFormatter(NumpydocSectionFormatter):
44 | """Ensure proper spacing around the colon separating names from types."""
45 |
46 | name = "numpydoc-name-type-spacing"
47 |
48 | numpydoc_sections_with_parameters = (
49 | "Parameters",
50 | "Attributes",
51 | "Returns",
52 | "Yields",
53 | "Receives",
54 | "Other Parameters",
55 | "See Also",
56 | )
57 |
58 | def treat_sections(
59 | self, sections: OrderedDict[str, list[str]]
60 | ) -> OrderedDict[str, list[str]]:
61 | """Ensure proper spacing around the colon separating names from types."""
62 | for section_name, section_lines in sections.items():
63 | if section_name in self.numpydoc_sections_with_parameters:
64 | # Any section that gets here has a line of hyphens
65 | initial_indent = section_lines[1].index("-")
66 | for index, line in enumerate(section_lines):
67 | if (
68 | # There is content on this line (at least the initial indent)
69 | len(line) > initial_indent
70 | # and the first character after the indent for
71 | # the docstring is a name, not an additional
72 | # indent indicating a description rather than
73 | # a line with name and type
74 | and not line[initial_indent].isspace()
75 | # and there is a colon to separate the name
76 | # from the type (functions returning only one
77 | # thing don't put a name in their "Returns"
78 | # section)
79 | and ":" in line
80 | ):
81 | line_name, line_type = line.split(":", 1)
82 | if line_type:
83 | # Avoid adding trailing whitespace
84 | # Colon ending first line is suggested for long
85 | # "See Also" links
86 | section_lines[index] = (
87 | f"{line_name.rstrip():s} : {line_type.lstrip():s}"
88 | )
89 | return sections
90 |
91 |
92 | class NumpydocSectionSpacingFormatter(NumpydocSectionFormatter):
93 | """Ensure proper spacing between sections."""
94 |
95 | name = "numpydoc-section-spacing"
96 |
97 | def treat_sections(
98 | self, sections: OrderedDict[str, list[str]]
99 | ) -> OrderedDict[str, list[str]]:
100 | """Ensure proper spacing between sections."""
101 | for section_lines in sections.values():
102 | last_line = section_lines[-1]
103 | if not (last_line == "" or last_line.isspace()) and len(sections) > 1:
104 | section_lines.append("")
105 | return sections
106 |
107 |
108 | class NumpydocSectionHyphenLengthFormatter(NumpydocSectionFormatter):
109 | """Ensure hyphens after section header lines are proper length."""
110 |
111 | name = "numpydoc-section-hyphen-length"
112 |
113 | def treat_sections(
114 | self, sections: OrderedDict[str, list[str]]
115 | ) -> OrderedDict[str, list[str]]:
116 | """Ensure section header lines are proper length."""
117 | first_section = True
118 | for section_name, section_lines in sections.items():
119 | if section_name != "Summary":
120 | # Skip the summary, deprecation warning and extended
121 | # summary. They have neither a section header nor the
122 | # line of hyphens after it.
123 | indent_length = section_lines[1].index("-")
124 | section_lines[1] = " " * indent_length + "-" * len(section_name)
125 | if first_section:
126 | # If the second line were not hyphens, the section name
127 | # would be summary. This assumes triple quotes, but that
128 | # seems fine for a multi-line docstring.
129 | section_lines[1] = f"{section_lines[1]:s}---"
130 | first_section = False
131 | return sections
132 |
--------------------------------------------------------------------------------
/pydocstringformatter/_formatting/formatters_pep257.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import re
4 | from typing import Literal
5 |
6 | from pydocstringformatter._formatting import _utils
7 | from pydocstringformatter._formatting.base import SummaryFormatter
8 |
9 |
10 | class SplitSummaryAndDocstringFormatter(SummaryFormatter):
11 | """Split the summary and body of a docstring based on a period and max length.
12 |
13 | The maximum length of a summary can be set with the --max-summary-lines option.
14 | """
15 |
16 | name = "split-summary-body"
17 |
18 | style = ["pep257"]
19 |
20 | end_of_sentence_period = re.compile(
21 | r"""
22 | (? str:
38 | """Split a summary and body if there is a period after the summary."""
39 | new_summary = None
40 |
41 | if not summary:
42 | return summary
43 |
44 | if _utils.is_rst_title(summary):
45 | return summary
46 |
47 | # Try to split on period
48 | if match := re.search(self.end_of_sentence_period, summary):
49 | index = match.start()
50 |
51 | if summary[:index].count("\n") < self.config.max_summary_lines:
52 | if len(summary) == index + 1:
53 | new_summary = summary
54 |
55 | # Handle summaries with more text on same line after the period
56 | elif summary[index + 1] == " ":
57 | new_summary = (
58 | summary[:index]
59 | + f"\n\n{' ' * indent_length}"
60 | + summary[index + 2 :]
61 | )
62 |
63 | # Handle summaries that end with a period and a direct new line
64 | elif summary[index + 1] == "\n":
65 | new_summary = summary[:index] + ".\n\n" + summary[index + 2 :]
66 |
67 | # Try to split on max length
68 | if not new_summary and summary.count("\n") > self.config.max_summary_lines - 1:
69 | lines = summary.splitlines()
70 | new_summary = (
71 | "\n".join(lines[: self.config.max_summary_lines])
72 | + "\n\n"
73 | + "\n".join(lines[self.config.max_summary_lines :])
74 | )
75 |
76 | return new_summary or summary
77 |
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import contextlib
4 | import logging
5 | from pathlib import Path
6 | from types import TracebackType
7 |
8 | import pytest
9 |
10 | from pydocstringformatter import run_docstring_formatter
11 | from pydocstringformatter._formatting import Formatter
12 | from pydocstringformatter._testutils.example_formatters import (
13 | AddBFormatter,
14 | MakeAFormatter,
15 | MakeBFormatter,
16 | )
17 |
18 | LOGGER = logging.getLogger(__name__)
19 |
20 |
21 | class FormatterAsserter(contextlib.AbstractContextManager): # type: ignore[type-arg]
22 | """ContextManager used to assert that a Formatter does something on a docstring.
23 |
24 | Also permit to check that nothing happens if it's deactivated.
25 | """
26 |
27 | def __init__(
28 | self,
29 | docstring: str,
30 | formatters: list[Formatter],
31 | capsys: pytest.CaptureFixture[str],
32 | tmp_path: Path,
33 | ) -> None:
34 | self.formatters = formatters
35 | file_name = "_".join([f.name for f in self.formatters])
36 | self.file_to_format = tmp_path / f"test_{file_name}.py"
37 | self.file_to_format.write_text(docstring)
38 | self.capsys = capsys
39 | names = [f"'{f.name}'" for f in formatters]
40 | verb = "is" if len(names) == 1 else "are"
41 | self.assert_msg = f"""
42 | {{}} was modified but {', '.join(names)} {verb} {{}}.
43 | Temp file is '{self.file_to_format}'
44 | """
45 |
46 | def __enter__(self) -> FormatterAsserter:
47 | return self
48 |
49 | @staticmethod
50 | def __launch(commands: list[str]) -> None:
51 | """Launch pydocstringformatter while logging for easier debugging."""
52 | run_docstring_formatter(commands)
53 | LOGGER.info("Launching 'pydocstringformatter' with: %s", commands)
54 |
55 | def assert_format_when_activated(self) -> None:
56 | """Assert that the formatter does something when activated."""
57 | msg = self.assert_msg.format("Nothing", "activated")
58 | self.__launch(
59 | [str(self.file_to_format)] + [f.activate_option for f in self.formatters]
60 | )
61 | out, err = self.capsys.readouterr()
62 | assert not err
63 | assert "Nothing to do!" not in out, msg
64 | expected = ["---", "@@", "+++"]
65 | assert all(e in out for e in expected), msg
66 |
67 | def assert_no_change_when_deactivated(self) -> None:
68 | """Assert that the formatter does nothing when deactivated."""
69 | self.__launch(
70 | [str(self.file_to_format)] + [f.deactivate_option for f in self.formatters]
71 | )
72 | out, err = self.capsys.readouterr()
73 | assert not err
74 | assert "Nothing to do!" in out, self.assert_msg.format(
75 | "Something", "deactivated"
76 | )
77 |
78 | def __exit__(
79 | self,
80 | exc_type: type[BaseException] | None,
81 | exc_val: BaseException | None,
82 | exc_tb: TracebackType | None,
83 | ) -> None:
84 | return None
85 |
86 |
87 | __all__ = ["FormatterAsserter", "MakeAFormatter", "MakeBFormatter", "AddBFormatter"]
88 |
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/example_formatters.py:
--------------------------------------------------------------------------------
1 | import tokenize
2 |
3 | from pydocstringformatter._formatting import Formatter
4 |
5 |
6 | class MakeAFormatter(Formatter):
7 | """A formatter that makes Bs into As."""
8 |
9 | name = "make-a-formatter"
10 | style = ["default"]
11 |
12 | def treat_token(self, tokeninfo: tokenize.TokenInfo) -> tokenize.TokenInfo:
13 | """Replace Bs with As."""
14 | token_dict = tokeninfo._asdict()
15 | token_dict["string"] = token_dict["string"].replace("B", "A")
16 | return type(tokeninfo)(**token_dict)
17 |
18 |
19 | class MakeBFormatter(Formatter):
20 | """A formatter that makes As into Bs."""
21 |
22 | name = "make-b-formatter"
23 | style = ["default"]
24 |
25 | def treat_token(self, tokeninfo: tokenize.TokenInfo) -> tokenize.TokenInfo:
26 | """Replace As with Bs."""
27 | token_dict = tokeninfo._asdict()
28 | token_dict["string"] = token_dict["string"].replace("A", "B")
29 | return type(tokeninfo)(**token_dict)
30 |
31 |
32 | class AddBFormatter(Formatter):
33 | """A formatter that adds Bs."""
34 |
35 | name = "add-b-formatter"
36 | style = ["default"]
37 |
38 | def treat_token(self, tokeninfo: tokenize.TokenInfo) -> tokenize.TokenInfo:
39 | """Add a B to the end of the string."""
40 | token_dict = tokeninfo._asdict()
41 | token_dict["string"] += "B"
42 | return type(tokeninfo)(**token_dict)
43 |
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/primer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/pydocstringformatter/_testutils/primer/__init__.py
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/primer/const.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | PRIMER_DIRECTORY_PATH = (
4 | Path(__file__).parent.parent.parent.parent / ".pydocstringformatter_primer_tests"
5 | )
6 | """Directory to store anything primer related in."""
7 |
8 | DIFF_OUTPUT = PRIMER_DIRECTORY_PATH / "fulldiff.txt"
9 | """Diff output file location."""
10 |
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/primer/packages.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import logging
4 | import shutil
5 | from dataclasses import dataclass
6 | from pathlib import Path
7 |
8 | import git
9 |
10 | from pydocstringformatter._testutils.primer.const import PRIMER_DIRECTORY_PATH
11 |
12 |
13 | @dataclass
14 | class PackageToPrime:
15 | """Represents data about a package to be tested during primer tests."""
16 |
17 | url: str
18 | """URL of the repository to clone."""
19 |
20 | branch: str
21 | """Branch of the repository to clone."""
22 |
23 | directories: list[str]
24 | """Directories within the repository to run the program over."""
25 |
26 | arguments: list[str]
27 | """List of arguments to pass when priming the package."""
28 |
29 | @property
30 | def clone_directory(self) -> Path:
31 | """Directory to clone repository into."""
32 | clone_name = "/".join(self.url.split("/")[-2:]).replace(".git", "")
33 | return PRIMER_DIRECTORY_PATH / clone_name
34 |
35 | @property
36 | def paths_to_lint(self) -> list[str]:
37 | """The paths we need to run against."""
38 | return [str(self.clone_directory / path) for path in self.directories]
39 |
40 | def lazy_clone(self) -> None:
41 | """Clone the repo or pull any new commits.
42 |
43 | # TODO(#80): Allow re-using an already cloned repistory instead of removing it
44 | Currently this is therefore not really 'lazy'.
45 | """
46 | logging.info("Lazy cloning %s", self.url)
47 |
48 | if self.clone_directory.exists():
49 | shutil.rmtree(self.clone_directory)
50 |
51 | options: dict[str, str | int] = {
52 | "url": self.url,
53 | "to_path": str(self.clone_directory),
54 | "branch": self.branch,
55 | "depth": 1,
56 | }
57 | git.Repo.clone_from(**options)
58 |
59 |
60 | PACKAGES = {
61 | "adventofcode": PackageToPrime(
62 | "https://github.com/DanielNoord/adventofcode",
63 | "main",
64 | ["."],
65 | [],
66 | ),
67 | "ProjectInventarisGezantschappen": PackageToPrime(
68 | "https://github.com/DanielNoord/ProjectInventarisGezantschappen",
69 | "main",
70 | ["python"],
71 | [],
72 | ),
73 | "pylint": PackageToPrime(
74 | "https://github.com/PyCQA/pylint",
75 | "main",
76 | ["pylint"],
77 | ["--max-summary-lines=2"],
78 | ),
79 | "pydocstringformatter": PackageToPrime(
80 | "https://github.com/DanielNoord/pydocstringformatter",
81 | "main",
82 | ["pydocstringformatter"],
83 | [],
84 | ),
85 | "pylint-pytest-plugin": PackageToPrime(
86 | "https://github.com/DanielNoord/pylint-pytest-plugin",
87 | "main",
88 | ["pylint_pytest_plugin"],
89 | ["--linewrap-full-docstring"],
90 | ),
91 | }
92 |
--------------------------------------------------------------------------------
/pydocstringformatter/_testutils/primer/primer.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import subprocess
4 | import sys
5 | from pathlib import Path
6 |
7 | from pydocstringformatter._testutils.primer.const import DIFF_OUTPUT
8 | from pydocstringformatter._testutils.primer.packages import PACKAGES, PackageToPrime
9 |
10 |
11 | def fix_diff(output: str, package: PackageToPrime) -> str:
12 | """Make the diff more readable and useful."""
13 | new_output: list[str] = []
14 |
15 | for index, line in enumerate(output.splitlines()):
16 | if line.startswith("--- "):
17 | if index:
18 | new_output.append("```\n")
19 |
20 | link = line.replace("--- ../.pydocstringformatter_primer_tests/", "")
21 | file = "/".join(link.split("/")[2:])
22 |
23 | new_output.append(f"{package.url}/blob/{package.branch}/{file}")
24 | new_output.append("```diff")
25 |
26 | new_output.append(line)
27 |
28 | return "\n".join(new_output)
29 |
30 |
31 | def run_prepare() -> None:
32 | """Prepare everything for the primer to be run.
33 |
34 | This clones all packages that need to be 'primed' and
35 | does any other necessary setup.
36 | """
37 | for package in PACKAGES.values():
38 | package.lazy_clone()
39 |
40 | print("## Preparation of primer successful!")
41 |
42 |
43 | def run_step_one() -> None:
44 | """Run program over all packages in write mode.
45 |
46 | Runs the program in write mode over all packages that need
47 | to be 'primed'. This should be run when the local repository
48 | is checked out to upstream/main.
49 | """
50 | for package in PACKAGES.values():
51 | subprocess.run(
52 | [sys.executable, "-m", "pydocstringformatter", "-w"]
53 | + package.paths_to_lint
54 | + package.arguments,
55 | cwd=Path(__file__).parent.parent.parent,
56 | capture_output=True,
57 | text=True,
58 | check=False,
59 | )
60 |
61 | print("## Step one of primer successful!")
62 |
63 |
64 | def run_step_two() -> None:
65 | """Run program over all packages and store the diff.
66 |
67 | This reiterates over all packages that need to be 'primed',
68 | runs the program in diff mode and stores the output to file.
69 | """
70 | output: dict[str, str] = {}
71 |
72 | for name, package in PACKAGES.items():
73 | process = subprocess.run(
74 | [sys.executable, "-m", "pydocstringformatter"]
75 | + package.paths_to_lint
76 | + package.arguments,
77 | cwd=Path(__file__).parent.parent.parent,
78 | capture_output=True,
79 | text=True,
80 | check=False,
81 | )
82 | output[name] = fix_diff(process.stdout, package)
83 |
84 | final_output = ""
85 | for name, string in output.items():
86 | if string.startswith("Nothing to do!"):
87 | continue
88 | final_output += f"**{name}:**\n\n{string}\n\n"
89 |
90 | with open(DIFF_OUTPUT, "w", encoding="utf-8") as file:
91 | file.write(final_output)
92 |
93 | print("## Step two of primer successful!")
94 |
95 |
96 | def run_primer() -> None:
97 | """Run the primer test."""
98 | args = sys.argv[1:]
99 |
100 | if "--prepare" in args:
101 | run_prepare()
102 | elif "--step-one" in args:
103 | run_step_one()
104 | elif "--step-two" in args:
105 | run_step_two()
106 |
107 |
108 | if __name__ == "__main__":
109 | run_primer()
110 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/__init__.py:
--------------------------------------------------------------------------------
1 | from pydocstringformatter._utils.exceptions import (
2 | ParsingError,
3 | PydocstringFormatterError,
4 | TomlParsingError,
5 | UnstableResultError,
6 | )
7 | from pydocstringformatter._utils.file_diference import compare_formatters, generate_diff
8 | from pydocstringformatter._utils.find_docstrings import is_docstring
9 | from pydocstringformatter._utils.find_python_file import find_python_files
10 | from pydocstringformatter._utils.issue_template import create_gh_issue_template
11 | from pydocstringformatter._utils.output import print_to_console, sys_exit
12 |
13 | __all__ = [
14 | "find_python_files",
15 | "compare_formatters",
16 | "generate_diff",
17 | "is_docstring",
18 | "ParsingError",
19 | "PydocstringFormatterError",
20 | "TomlParsingError",
21 | "UnstableResultError",
22 | "create_gh_issue_template",
23 | "print_to_console",
24 | "sys_exit",
25 | ]
26 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/exceptions.py:
--------------------------------------------------------------------------------
1 | class PydocstringFormatterError(Exception):
2 | """Base class to inherit all exceptions from."""
3 |
4 |
5 | class ParsingError(PydocstringFormatterError):
6 | """Raised when we the parsing of a file failed."""
7 |
8 |
9 | class UnrecognizedOption(PydocstringFormatterError):
10 | """Raised when an option is encountered that is not recognized."""
11 |
12 |
13 | class TomlParsingError(PydocstringFormatterError):
14 | """Raised when there are errors with the parsing of the toml file."""
15 |
16 |
17 | class UnstableResultError(PydocstringFormatterError):
18 | """Raised when the result of the formatting is unstable."""
19 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/file_diference.py:
--------------------------------------------------------------------------------
1 | import difflib
2 | import tokenize
3 |
4 | from pydocstringformatter._formatting import Formatter
5 |
6 |
7 | def generate_diff(old: str, new: str, filename: str) -> str:
8 | """Generate a printable diff for two strings of sourcecode."""
9 | return (
10 | "\n".join(
11 | difflib.unified_diff(
12 | old.split("\n"),
13 | new.split("\n"),
14 | fromfile=filename,
15 | tofile=filename,
16 | lineterm="",
17 | )
18 | )
19 | + "\n"
20 | )
21 |
22 |
23 | def compare_formatters(
24 | token: tokenize.TokenInfo,
25 | formatter_1: Formatter,
26 | formatter_2: Formatter,
27 | title_extra: str = "",
28 | ) -> str:
29 | """Modifies a token with two formatters and returns the difference."""
30 | out_t1 = formatter_1.treat_token(token)
31 | out_t2 = formatter_2.treat_token(out_t1)
32 |
33 | title = f"{formatter_1.name} vs {formatter_2.name}"
34 | if title_extra:
35 | title += f" {title_extra}"
36 | return generate_diff(out_t1.string, out_t2.string, title)
37 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/find_docstrings.py:
--------------------------------------------------------------------------------
1 | import token
2 | import tokenize
3 |
4 | PREVIOUS_TOKEN_MARKERS = (token.INDENT, token.ENDMARKER, token.NEWLINE)
5 |
6 |
7 | def is_docstring(
8 | tokeninfo: tokenize.TokenInfo, previous_token: tokenize.TokenInfo
9 | ) -> bool:
10 | """Check if a token represents a docstring."""
11 | if (
12 | tokeninfo.type == token.STRING
13 | and (
14 | previous_token.type in PREVIOUS_TOKEN_MARKERS
15 | or previous_token.type == token.NL
16 | and not tokeninfo.start[1]
17 | )
18 | and tokeninfo.line.strip().startswith(("'", '"'))
19 | ):
20 | return True
21 | return False
22 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/find_python_file.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import glob
4 | import os
5 | from pathlib import Path
6 |
7 |
8 | def is_python_file(filename: str) -> bool:
9 | """Check if file is a Python file."""
10 | return filename.endswith(".py")
11 |
12 |
13 | def find_python_files(
14 | filenames: list[str], exclude: list[str], recursive: bool = True
15 | ) -> list[Path]:
16 | """Find all python files for a list of potential file and directory names."""
17 | pathnames: list[Path] = []
18 |
19 | to_exclude: set[str] = set()
20 | for exclude_glob in exclude:
21 | to_exclude.update(set(glob.iglob(exclude_glob, recursive=recursive)))
22 |
23 | for name in filenames:
24 | if os.path.isdir(name):
25 | if recursive:
26 | for root, _, children in os.walk(name):
27 | pathnames += [
28 | Path(os.path.abspath(root)) / child
29 | for child in children
30 | if is_python_file(child)
31 | and str(Path(root) / child) not in to_exclude
32 | ]
33 | else:
34 | pathnames += [
35 | file for file in Path(name).iterdir() if is_python_file(str(file))
36 | ]
37 | elif is_python_file(name):
38 | pathnames.append(Path(name))
39 |
40 | return sorted(pathnames)
41 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/issue_template.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import tokenize
4 |
5 | from pydocstringformatter._formatting import Formatter
6 | from pydocstringformatter._utils.file_diference import compare_formatters
7 |
8 |
9 | def create_gh_issue_template(
10 | token: tokenize.TokenInfo, formatters: dict[str, Formatter], filename: str
11 | ) -> str:
12 | """Make a template for a GitHub issue.
13 |
14 | Args:
15 | token: The token that caused the issue.
16 | formatters: The formatters that caused the issue.
17 | filename: The filename of the file that caused the issue.
18 | """
19 | formatter_names = list(formatters)
20 | msg = ""
21 | if len(formatter_names) > 2:
22 | msg = f"""
23 | Conflicting formatters: {", ".join(formatters)}
24 | """
25 | diff = f"Diff too intricate to compute for {len(formatter_names)} formatters."
26 | else:
27 | if len(formatter_names) == 2:
28 | msg = f"""
29 | Conflicting formatters: {" and ".join(formatter_names)}
30 | These formatters conflict with each other for:
31 |
32 | ```python
33 | {token.string}
34 | ```
35 | """
36 | formatter_1 = formatters[formatter_names[0]]
37 | formatter_2 = formatters[formatter_names[1]]
38 | else:
39 | msg = f"""
40 | Formatter: {formatter_names[0]}
41 | This formatter is not able to make stable changes for:
42 |
43 | ```python
44 | {token.string}
45 | ```
46 | """
47 | formatter_1 = formatters[formatter_names[0]]
48 | formatter_2 = formatter_1
49 |
50 | diff = compare_formatters(
51 | token,
52 | formatter_1,
53 | formatter_2,
54 | title_extra=str(filename),
55 | )
56 |
57 | out = f"""
58 | Unfortunately an error occurred while formatting a docstring.
59 | Please help us fix this bug by opening an issue at:
60 | https://github.com/DanielNoord/pydocstringformatter/issues/new
61 |
62 | {"-" * 80}
63 |
64 | You can use the following template when you open the issue:
65 |
66 | # Description:
67 |
68 | {msg}
69 |
70 | # Diff:
71 |
72 | ```diff
73 | {diff}
74 | ```
75 |
76 | """
77 |
78 | return out
79 |
--------------------------------------------------------------------------------
/pydocstringformatter/_utils/output.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 |
4 | def encode_string(string: str) -> bytes:
5 | """Encode a string to utf-8.
6 |
7 | This can be used to circumvent the issue of the standard encoding
8 | of a windows console not being utf-8.
9 | See: https://github.com/DanielNoord/pydocstringformatter/issues/13
10 | """
11 | return string.encode("utf-8")
12 |
13 |
14 | def print_to_console(string: str, quiet: bool) -> None:
15 | """Print a string to the console while handling edge cases.
16 |
17 | This can be used instead of print() whenever we want to
18 | print emoji's or non-ASCII characters, but also to check if we are
19 | in quiet mode.
20 | """
21 | if not quiet:
22 | sys.stdout.buffer.write(encode_string(string))
23 |
24 |
25 | def sys_exit(value: int, option: bool) -> None:
26 | """Sys.exit if the boolean passed says to do so."""
27 | if option:
28 | sys.exit(value)
29 |
--------------------------------------------------------------------------------
/pydocstringformatter/run.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=too-few-public-methods, protected-access
2 | """Run class."""
3 | from __future__ import annotations
4 |
5 | import os
6 | import sys
7 | import tokenize
8 | from pathlib import Path
9 |
10 | from pydocstringformatter import __version__, _formatting, _utils
11 | from pydocstringformatter._configuration.arguments_manager import ArgumentsManager
12 | from pydocstringformatter._utils.exceptions import UnstableResultError
13 |
14 |
15 | class _Run:
16 | """Main class that represent a run of the program."""
17 |
18 | def __init__(self, argv: list[str] | None) -> None:
19 | # Load ArgumentsManager and set its namespace as instance's config attribute
20 | self._arguments_manager = ArgumentsManager(__version__, _formatting.FORMATTERS)
21 | self.config = self._arguments_manager.namespace
22 |
23 | # Display help message if nothing is passed
24 | if not (argv := argv or sys.argv[1:]):
25 | self._arguments_manager.print_help()
26 | return
27 |
28 | # Parse options and register on formatters
29 | self._arguments_manager.parse_options(argv)
30 | for formatter in _formatting.FORMATTERS:
31 | formatter.set_config_namespace(self.config)
32 |
33 | self.enabled_formatters = self.get_enabled_formatters()
34 | self.check_files(self.config.files)
35 |
36 | # pylint: disable-next=inconsistent-return-statements
37 | def check_files(self, files: list[str]) -> None:
38 | """Find all files and perform the formatting."""
39 | filepaths = _utils.find_python_files(files, self.config.exclude)
40 |
41 | is_changed = self.format_files(filepaths)
42 |
43 | if is_changed: # pylint: disable=consider-using-assignment-expr
44 | return _utils.sys_exit(32, self.config.exit_code)
45 |
46 | files_string = f"{len(filepaths)} "
47 | files_string += "files" if len(filepaths) != 1 else "file"
48 | _utils.print_to_console(
49 | f"Nothing to do! All docstrings in {files_string} are correct 🎉\n",
50 | self.config.quiet,
51 | )
52 |
53 | _utils.sys_exit(0, self.config.exit_code)
54 |
55 | def format_file(self, filename: Path) -> bool:
56 | """Format a file."""
57 | with tokenize.open(filename) as file:
58 | try:
59 | tokens = list(tokenize.generate_tokens(file.readline))
60 | except tokenize.TokenError as exc:
61 | raise _utils.ParsingError(
62 | f"Can't parse {os.path.relpath(filename)}. Is it valid Python code?"
63 | ) from exc
64 | # Record type of newlines so we can make sure to use
65 | # the same later on.
66 | newlines = file.newlines
67 |
68 | formatted_tokens, is_changed = self.format_file_tokens(tokens, filename)
69 |
70 | if is_changed:
71 | try:
72 | filename_str = os.path.relpath(filename)
73 | except ValueError:
74 | # On Windows relpath raises ValueError's when the mounts differ
75 | filename_str = str(filename)
76 |
77 | if self.config.write:
78 | if isinstance(newlines, tuple):
79 | newlines = newlines[0]
80 | print(
81 | "Found multiple newline variants in "
82 | f"{os.path.abspath(filename_str)}. "
83 | "Using variant that occurred first.",
84 | file=sys.stderr,
85 | )
86 | with open(filename, "w", encoding="utf-8", newline=newlines) as file:
87 | file.write(tokenize.untokenize(formatted_tokens))
88 | _utils.print_to_console(
89 | f"Formatted {filename_str} 📖\n", self.config.quiet
90 | )
91 | else:
92 | sys.stdout.write(
93 | _utils.generate_diff(
94 | tokenize.untokenize(tokens),
95 | tokenize.untokenize(formatted_tokens),
96 | filename_str,
97 | )
98 | )
99 |
100 | return is_changed
101 |
102 | def get_enabled_formatters(self) -> dict[str, _formatting.Formatter]:
103 | """Returns a dict of the enabled formatters."""
104 |
105 | enabled = {}
106 | for formatter in _formatting.FORMATTERS:
107 | if (
108 | "default" in formatter.style
109 | or any(i in formatter.style for i in self.config.style)
110 | ) and getattr(self.config, formatter.name):
111 | enabled[formatter.name] = formatter
112 |
113 | return enabled
114 |
115 | def format_file_tokens(
116 | self, tokens: list[tokenize.TokenInfo], filename: Path
117 | ) -> tuple[list[tokenize.TokenInfo], bool]:
118 | """Format a list of tokens.
119 |
120 | tokens: List of tokens to format.
121 | filename: Name of the file the tokens are from.
122 |
123 | Returns:
124 | A tuple containing [1] the formatted tokens in a list
125 | and [2] a boolean indicating if the tokens were changed.
126 |
127 | Raises:
128 | UnstableResultError::
129 | If the formatters are not able to get to a stable result.
130 | It reports what formatters are still modifying the tokens.
131 | """
132 | formatted_tokens: list[tokenize.TokenInfo] = []
133 | is_changed = False
134 |
135 | for index, tokeninfo in enumerate(tokens):
136 | new_tokeninfo = tokeninfo
137 |
138 | if _utils.is_docstring(new_tokeninfo, tokens[index - 1]):
139 | new_tokeninfo, changers = self.apply_formatters(new_tokeninfo)
140 | is_changed = is_changed or bool(changers)
141 |
142 | # Run formatters again (3rd time) to check if the result is stable
143 | _, changers = self._apply_formatters_once(
144 | new_tokeninfo,
145 | )
146 |
147 | if changers:
148 | conflicting_formatters = {
149 | k: v
150 | for k, v in self.enabled_formatters.items()
151 | if k in changers
152 | }
153 | template = _utils.create_gh_issue_template(
154 | new_tokeninfo, conflicting_formatters, str(filename)
155 | )
156 |
157 | raise UnstableResultError(template)
158 |
159 | formatted_tokens.append(new_tokeninfo)
160 |
161 | return formatted_tokens, is_changed
162 |
163 | def apply_formatters(
164 | self, token: tokenize.TokenInfo
165 | ) -> tuple[tokenize.TokenInfo, set[str]]:
166 | """Apply the formatters twice to a token.
167 |
168 | Also tracks which formatters changed the token.
169 |
170 | Returns:
171 | A tuple containing:
172 | [1] the formatted token and
173 | [2] a set of formatters that changed the token.
174 | """
175 | token, changers = self._apply_formatters_once(token)
176 | if changers:
177 | token, changers2 = self._apply_formatters_once(token)
178 | changers.update(changers2)
179 | return token, changers
180 |
181 | def _apply_formatters_once(
182 | self, token: tokenize.TokenInfo
183 | ) -> tuple[tokenize.TokenInfo, set[str]]:
184 | """Applies formatters to a token and keeps track of what changes it.
185 |
186 | token: Token to apply formatters to
187 |
188 | Returns:
189 | A tuple containing [1] the formatted token and [2] a set
190 | of formatters that changed the token.
191 | """
192 | changers: set[str] = set()
193 | for formatter_name, formatter in self.enabled_formatters.items():
194 | if (new_token := formatter.treat_token(token)) != token:
195 | changers.add(formatter_name)
196 | token = new_token
197 |
198 | return token, changers
199 |
200 | def format_files(self, filepaths: list[Path]) -> bool:
201 | """Format a list of files."""
202 | is_changed = [self.format_file(file) for file in filepaths]
203 | return any(is_changed)
204 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools >= 62",
4 | "wheel >= 0.37",
5 | ]
6 | build-backend = "setuptools.build_meta"
7 |
8 | [project]
9 | name = "pydocstringformatter"
10 | dynamic = ["version"]
11 | authors = [
12 | {name = "Daniël van Noord", email = "13665637+DanielNoord@users.noreply.github.com"}
13 | ]
14 | description = "A tool to automatically format Python docstrings that tries to follow recommendations from PEP 8 and PEP 257."
15 | readme = "README.md"
16 | license = {text = "MIT"}
17 | classifiers = [
18 | "Development Status :: 4 - Beta",
19 | "Environment :: Console",
20 | "Intended Audience :: Developers",
21 | "License :: OSI Approved :: MIT License",
22 | "Operating System :: OS Independent",
23 | "Programming Language :: Python",
24 | "Programming Language :: Python :: 3",
25 | "Programming Language :: Python :: 3 :: Only",
26 | "Programming Language :: Python :: 3.8",
27 | "Programming Language :: Python :: 3.9",
28 | "Programming Language :: Python :: 3.10",
29 | "Programming Language :: Python :: 3.11",
30 | "Programming Language :: Python :: 3.12",
31 | "Programming Language :: Python :: Implementation :: CPython",
32 | "Topic :: Software Development :: Quality Assurance",
33 | ]
34 | keywords = ["python", "docstring", "format"]
35 | requires-python = ">=3.8"
36 | dependencies = ["tomli>=1.1.0;python_version<'3.11'"]
37 |
38 | [project.urls]
39 | "Repository" = "https://github.com/DanielNoord/pydocstringformatter"
40 | "Releases" = "https://github.com/DanielNoord/pydocstringformatter/releases"
41 | "Bug Tracker" = "https://github.com/DanielNoord/pydocstringformatter/issues"
42 |
43 | [project.scripts]
44 | pydocstringformatter = "pydocstringformatter:run_docstring_formatter"
45 |
46 | [tool.setuptools]
47 | license-files = ["LICENSE"]
48 |
49 | [tool.setuptools.dynamic]
50 | version = {attr = "pydocstringformatter.__version__"}
51 |
52 | [tool.setuptools.packages.find]
53 | include = ["pydocstringformatter*"]
54 |
55 | [tool.pytest]
56 | testpaths = "tests"
57 |
58 | [tool.mypy]
59 | files = "pydocstringformatter,tests"
60 | exclude = "tests/data.*"
61 | strict = true
62 | show_error_codes = true
63 | enable_error_code = "ignore-without-code"
64 |
65 | [[tool.mypy.overrides]]
66 | module = ["git.*"]
67 | ignore_missing_imports = true
68 |
69 | [tool.pylint]
70 | load-plugins=[
71 | "pylint.extensions.check_elif",
72 | "pylint.extensions.code_style",
73 | "pylint.extensions.confusing_elif",
74 | "pylint.extensions.docparams",
75 | "pylint.extensions.docstyle",
76 | "pylint.extensions.empty_comment",
77 | "pylint.extensions.for_any_all",
78 | "pylint.extensions.set_membership",
79 | "pylint.extensions.typing"
80 | ]
81 | py-version="3.8"
82 | disable = [
83 | "missing-module-docstring",
84 | "too-few-public-methods",
85 | "duplicate-code"
86 | ]
87 | enable = [
88 | "c-extension-no-member",
89 | ]
90 | good-names = "f"
91 | notes-rgx = 'TODO(?!\(#\d+\))'
92 |
93 | [tool.isort]
94 | profile = "black"
95 | known_third_party = ["pytest"]
96 | skip_glob = "tests/data/**"
97 |
98 | [tool.coverage.run]
99 | branch = true
100 | relative_files = true
101 |
102 | [tool.black]
103 | # Use force-exclude due to the way that pre-commit passes filenames to black
104 | # See https://github.com/psf/black/issues/1985
105 | force-exclude = ".*tests/data/.*"
106 | quiet = true
107 |
--------------------------------------------------------------------------------
/requirements-coverage.txt:
--------------------------------------------------------------------------------
1 | -e .
2 | coverage[toml]==7.8.2
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Coverage + local install of package
2 | -e .
3 | -r requirements-coverage.txt
4 |
5 | # Requirements for testing and linting
6 | pytest==8.4.0
7 | pytest-cov==6.1.1
8 | gitpython>=3
9 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture
7 | def test_file(tmp_path: Path) -> str:
8 | """A test file to be used by tests."""
9 | filename = tmp_path / "test.py"
10 | with open(filename, "w", encoding="utf-8") as file:
11 | file.write('"""A multi-line\ndocstring."""')
12 | return str(filename)
13 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | exclude = "**/test_package/**"
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_csv/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | exclude = "**/test_packages/**,**/test_package/**"
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_csv/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_csv_list/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | exclude = ["**/test_package/**", "**/test_packages/**"]
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_csv_list/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_inner/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | exclude = "**/inner_dir/**"
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_match_inner/test_package/inner_dir/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_non_match/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | exclude = "**test_packages**"
3 |
--------------------------------------------------------------------------------
/tests/data/config/exclude_non_match/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/invalid_toml/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write true
3 |
--------------------------------------------------------------------------------
/tests/data/config/invalid_toml/test_package/correct_docstring.py:
--------------------------------------------------------------------------------
1 | """A docstring"""
2 |
--------------------------------------------------------------------------------
/tests/data/config/no_toml/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/non_existing_options/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | non_existing_option = true
3 |
--------------------------------------------------------------------------------
/tests/data/config/non_existing_options/test_package/correct_docstring.py:
--------------------------------------------------------------------------------
1 | """A docstring"""
2 |
--------------------------------------------------------------------------------
/tests/data/config/non_valid_toml_boolopt/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = ["numpydoc"]
4 | strip-whitespaces = true
5 | split-summary-body = false
6 | no-numpydoc-section-hyphen-length = true
7 |
--------------------------------------------------------------------------------
/tests/data/config/non_valid_toml_boolopt/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/non_valid_toml_boolopt_two/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = ["numpydoc"]
4 | strip-whitespaces = true
5 | split-summary-body = false
6 | numpydoc-section-hyphen-length = 'true'
7 |
--------------------------------------------------------------------------------
/tests/data/config/non_valid_toml_boolopt_two/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = true
3 | max-summary-lines = 2
4 | # Also test short options
5 | w = true
6 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml/test_package/correct_docstring.py:
--------------------------------------------------------------------------------
1 | """A docstring."""
2 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_boolopt/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = ["numpydoc"]
4 | strip-whitespaces = true
5 | split-summary-body = false
6 | numpydoc-section-hyphen-length = false
7 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_boolopt/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_numpydoc/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = 'numpydoc'
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_numpydoc/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_numpydoc_pep257/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = ["numpydoc","pep257"]
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_numpydoc_pep257/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_pep257/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 | style = 'pep257'
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_pep257/test_package/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_two/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.pydocstringformatter]
2 | write = false
3 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_two/test_package/incorrect_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_without_section/pyproject.toml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/config/valid_toml_without_section/pyproject.toml
--------------------------------------------------------------------------------
/tests/data/config/valid_toml_without_section/test_package/correct_docstring.py:
--------------------------------------------------------------------------------
1 | """A docstring"""
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line
3 | docstring"""
4 |
5 | class InnerClass:
6 | """
7 | A multi-line
8 | docstring"""
9 |
10 |
11 | class MyClass:
12 | """A multi-line
13 | docstring
14 | """
15 |
16 | class InnerClass:
17 | """A multi-line
18 | docstring
19 | """
20 |
21 |
22 | class MyClass:
23 | """
24 | A docstring."""
25 |
26 | class InnerClass:
27 | """
28 | A docstring."""
29 |
30 |
31 | class MyClass:
32 | """A docstring."""
33 |
34 | class InnerClass:
35 | """A docstring."""
36 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """
9 | A multi-line.
10 |
11 | docstring
12 | """
13 |
14 |
15 | class MyClass:
16 | """A multi-line.
17 |
18 | docstring
19 | """
20 |
21 | class InnerClass:
22 | """A multi-line.
23 |
24 | docstring
25 | """
26 |
27 |
28 | class MyClass:
29 | """A docstring."""
30 |
31 | class InnerClass:
32 | """A docstring."""
33 |
34 |
35 | class MyClass:
36 | """A docstring."""
37 |
38 | class InnerClass:
39 | """A docstring."""
40 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring."""
4 |
5 | def inner_func():
6 | """
7 | A docstring."""
8 |
9 |
10 | def func():
11 | """
12 | A multi-line
13 | docstring
14 | """
15 |
16 | def inner_func():
17 | """A multi-line
18 | docstring"""
19 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func():
9 | """
10 | A multi-line.
11 |
12 | docstring
13 | """
14 |
15 | def inner_func():
16 | """A multi-line.
17 |
18 | docstring
19 | """
20 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/module_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring."""
3 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A docstring."""
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """
3 | A multi-line
4 | docstring"""
5 |
6 | class InnerClass:
7 | """
8 | A multi-line
9 | docstring"""
10 |
11 |
12 | class MyClass:
13 | """A multi-line
14 | docstring
15 | """
16 |
17 | class InnerClass:
18 | """A multi-line
19 | docstring
20 | """
21 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """
3 | A multi-line.
4 |
5 | docstring
6 | """
7 |
8 | class InnerClass:
9 | """
10 | A multi-line.
11 |
12 | docstring
13 | """
14 |
15 |
16 | class MyClass:
17 | """A multi-line.
18 |
19 | docstring
20 | """
21 |
22 | class InnerClass:
23 | """A multi-line.
24 |
25 | docstring
26 | """
27 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A multi-line
4 | docstring
5 | """
6 |
7 | def inner_func():
8 | """
9 | A multi-line
10 | docstring"""
11 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A multi-line.
4 |
5 | docstring
6 | """
7 |
8 | def inner_func():
9 | """
10 | A multi-line.
11 |
12 | docstring
13 | """
14 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/module_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | Summary.
3 |
4 | Body
5 | """
6 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """
2 | Summary.
3 |
4 | Body
5 | """
6 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """
3 | A multi-line
4 | docstring"""
5 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_no_push/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """
3 | A multi-line.
4 |
5 | docstring
6 | """
7 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/class_docstring.args:
--------------------------------------------------------------------------------
1 | --summary-quotes-same-line
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """
3 | A multi-line
4 | docstring"""
5 |
6 | class InnerClass:
7 | """
8 | A multi-line
9 | docstring"""
10 |
11 |
12 | class MyClass:
13 | """A multi-line
14 | docstring
15 | """
16 |
17 | class InnerClass:
18 | """A multi-line
19 | docstring
20 | """
21 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
13 |
14 | class MyClass:
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 | class InnerClass:
21 | """A multi-line.
22 |
23 | docstring
24 | """
25 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/function_docstrings.args:
--------------------------------------------------------------------------------
1 | --summary-quotes-same-line
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A multi-line
4 | docstring
5 | """
6 |
7 | def inner_func():
8 | """
9 | A multi-line
10 | docstring"""
11 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | def inner_func():
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/module_docstring.args:
--------------------------------------------------------------------------------
1 | --summary-quotes-same-line
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/module_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | Summary.
3 |
4 | Body
5 | """
6 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """Summary.
2 |
3 | Body
4 | """
5 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/variable_docstring.args:
--------------------------------------------------------------------------------
1 | --summary-quotes-same-line
2 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """
3 | A multi-line
4 | docstring"""
5 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/multi_line_push/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line
3 | docstring"""
4 |
--------------------------------------------------------------------------------
/tests/data/format/beginning_quote/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """a multi-line
3 | docstring"""
4 |
5 | class InnerClass:
6 | """
7 | a multi-line
8 | docstring"""
9 |
10 |
11 | class MyClass:
12 | """a multi-line
13 | docstring
14 | """
15 |
16 | class InnerClass:
17 | """a multi-line
18 | docstring
19 | """
20 |
21 |
22 | class MyClass:
23 | """
24 | a docstring"""
25 |
26 | class InnerClass:
27 | """
28 | a docstring"""
29 |
30 |
31 | class MyClass:
32 | """A docstring"""
33 |
34 | class InnerClass:
35 | """A docstring"""
36 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """
9 | A multi-line.
10 |
11 | docstring
12 | """
13 |
14 |
15 | class MyClass:
16 | """A multi-line.
17 |
18 | docstring
19 | """
20 |
21 | class InnerClass:
22 | """A multi-line.
23 |
24 | docstring
25 | """
26 |
27 |
28 | class MyClass:
29 | """A docstring."""
30 |
31 | class InnerClass:
32 | """A docstring."""
33 |
34 |
35 | class MyClass:
36 | """A docstring."""
37 |
38 | class InnerClass:
39 | """A docstring."""
40 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | a docstring"""
4 |
5 | def inner_func():
6 | """
7 | a docstring"""
8 |
9 |
10 | def func(named_parameter: str) -> None:
11 | """'named_parameter' should have a nice name
12 |
13 | Other information here
14 | """
15 | print(named_parameter)
16 |
17 |
18 | def func():
19 | """
20 | a multi-line
21 | docstring
22 | """
23 |
24 | def inner_func():
25 | """a multi-line
26 | docstring"""
27 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func(named_parameter: str) -> None:
9 | """'named_parameter' should have a nice name.
10 |
11 | Other information here
12 | """
13 | print(named_parameter)
14 |
15 |
16 | def func():
17 | """
18 | A multi-line.
19 |
20 | docstring
21 | """
22 |
23 | def inner_func():
24 | """A multi-line.
25 |
26 | docstring
27 | """
28 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/module_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | a docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A docstring."""
2 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/single_quote_docstring.args:
--------------------------------------------------------------------------------
1 | --no-quotes-type
2 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/single_quote_docstring.py:
--------------------------------------------------------------------------------
1 | def func():
2 | "a docstring."
3 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/single_quote_docstring.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | "A docstring."
3 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/uncapitalized_starting_letter.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """this is a single line docstring in plain english"""
3 |
4 |
5 | class MyClass:
6 | """1 cannot always be capitalized."""
7 |
8 |
9 | class MyClass:
10 | """在某些语言中,大写字母是没有意义的"""
11 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/uncapitalized_starting_letter.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """This is a single line docstring in plain english."""
3 |
4 |
5 | class MyClass:
6 | """1 cannot always be capitalized."""
7 |
8 |
9 | class MyClass:
10 | """在某些语言中,大写字母是没有意义的."""
11 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """
3 | a multi-line
4 | docstring"""
5 |
6 | MYVAR = 1
7 | """a multi-line
8 | docstring
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/capitalization_first_letter/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """
3 | A multi-line.
4 |
5 | docstring
6 | """
7 |
8 | MYVAR = 1
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """
3 | A docstring.
4 | """
5 |
6 | class InnerClass:
7 | """
8 | A docstring.
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A docstring."""
3 |
4 | class InnerClass:
5 | """A docstring."""
6 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring.
3 | """
4 |
5 | def inner_func():
6 | """A docstring.
7 | """
8 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/module_docstring.py:
--------------------------------------------------------------------------------
1 | """
2 | A docstring.
3 | """
4 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A docstring."""
2 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A docstring.
3 | """
4 |
--------------------------------------------------------------------------------
/tests/data/format/ending_quote/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A docstring."""
3 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """Docstring"""
3 |
4 | class InnerClass:
5 | """Docstring"""
6 |
7 |
8 | class MyClass:
9 | """A multi-line
10 | docstring
11 | """
12 |
13 | class InnerClass:
14 | """A multi-line
15 | docstring
16 | """
17 |
18 |
19 | class MyClass:
20 | """Summary
21 |
22 | docstring
23 | """
24 |
25 | class InnerClass:
26 | """Summary
27 |
28 | docstring
29 | """
30 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """Docstring."""
3 |
4 | class InnerClass:
5 | """Docstring."""
6 |
7 |
8 | class MyClass:
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
14 | class InnerClass:
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 |
21 | class MyClass:
22 | """Summary.
23 |
24 | docstring
25 | """
26 |
27 | class InnerClass:
28 | """Summary.
29 |
30 | docstring
31 | """
32 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/exotic_line_ending.py:
--------------------------------------------------------------------------------
1 | def function():
2 | """Check the name of first argument, expect:
3 |
4 | *'self' for a regular method
5 | *'cls' for a class method or a metaclass regular method"""
6 |
7 | def function():
8 | """This has peculiar line ending;
9 |
10 | but it should not matter.
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/exotic_line_ending.py.out:
--------------------------------------------------------------------------------
1 | def function():
2 | """Check the name of first argument, expect:
3 |
4 | *'self' for a regular method
5 | *'cls' for a class method or a metaclass regular method
6 | """
7 |
8 | def function():
9 | """This has peculiar line ending;
10 |
11 | but it should not matter.
12 | """
13 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func()
2 | """Docstring"""
3 |
4 | def inner_func()
5 | """Docstring"""
6 |
7 |
8 | def func()
9 | """A multi-line
10 | docstring
11 | """
12 |
13 | def inner_func()
14 | """A multi-line
15 | docstring
16 | """
17 |
18 |
19 | def func()
20 | """Summary
21 |
22 | docstring
23 | """
24 |
25 | def inner_func()
26 | """Summary
27 |
28 | docstring
29 | """
30 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func()
2 | """Docstring."""
3 |
4 | def inner_func()
5 | """Docstring."""
6 |
7 |
8 | def func()
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
14 | def inner_func()
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 |
21 | def func()
22 | """Summary.
23 |
24 | docstring
25 | """
26 |
27 | def inner_func()
28 | """Summary.
29 |
30 | docstring
31 | """
32 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/function_title_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | def inner_func():
3 | """Summary
4 | ==========
5 |
6 | docstring
7 | """
8 |
9 | def inner_func():
10 | """Summary
11 | ----------
12 |
13 | docstring
14 | """
15 |
16 | def inner_func():
17 | """Summary
18 | ^^^^^^^^^^
19 |
20 | docstring
21 | """
22 |
23 | def inner_func():
24 | """Summary
25 | **********
26 |
27 | docstring
28 | """
29 |
30 | def inner_func():
31 | """Summary
32 | ^^^^^^^^^^
33 |
34 | docstring
35 | """
36 |
37 | def inner_func():
38 | """Summary
39 | aaaaaaaaaa
40 |
41 | docstring
42 | """
43 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/function_title_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | def inner_func():
3 | """Summary
4 | ==========
5 |
6 | docstring
7 | """
8 |
9 | def inner_func():
10 | """Summary
11 | ----------
12 |
13 | docstring
14 | """
15 |
16 | def inner_func():
17 | """Summary
18 | ^^^^^^^^^^
19 |
20 | docstring
21 | """
22 |
23 | def inner_func():
24 | """Summary
25 | **********
26 |
27 | docstring
28 | """
29 |
30 | def inner_func():
31 | """Summary
32 | ^^^^^^^^^^
33 |
34 | docstring
35 | """
36 |
37 | def inner_func():
38 | """Summary
39 | aaaaaaaaaa
40 |
41 | docstring
42 | """
43 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/module_docstring.py:
--------------------------------------------------------------------------------
1 | """Docstring"""
2 |
3 | """A multi-line
4 | docstring
5 | """
6 |
7 | """Summary
8 |
9 | docstring
10 | """
11 |
12 | """Docstring?"""
13 |
14 | """Docstring!"""
15 |
16 | """Docstring‽"""
17 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """Docstring."""
2 |
3 | """A multi-line.
4 |
5 | docstring
6 | """
7 |
8 | """Summary.
9 |
10 | docstring
11 | """
12 |
13 | """Docstring?"""
14 |
15 | """Docstring!"""
16 |
17 | """Docstring‽"""
18 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/single_quote_docstring.args:
--------------------------------------------------------------------------------
1 | --no-quotes-type
2 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/single_quote_docstring.py:
--------------------------------------------------------------------------------
1 | def func():
2 | "A docstring"
3 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/single_quote_docstring.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | "A docstring."
3 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """Docstring"""
3 |
4 | MYVAR = 1
5 | """A multi-line
6 | docstring
7 | """
8 |
9 | MYVAR = 1
10 | """Summary
11 |
12 | A docstring
13 | """
14 |
--------------------------------------------------------------------------------
/tests/data/format/final_period/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """Docstring."""
3 |
4 | MYVAR = 1
5 | """A multi-line.
6 |
7 | docstring
8 | """
9 |
10 | MYVAR = 1
11 | """Summary.
12 |
13 | A docstring
14 | """
15 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/function_docstrings.args:
--------------------------------------------------------------------------------
1 | --linewrap-full-docstring
2 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """A very long summary line that needs to be wrapped, A very long summary line that needs to be wrapped.
3 |
4 | A description that is not too long.
5 | """
6 |
7 |
8 | def func():
9 | """A very long multi-line summary line that needs to be wrapped, A very long multi-line summary line that needs to be wrapped.
10 | A very long summary line that needs to be wrapped.
11 |
12 | A description that is not too long.
13 | """
14 |
15 |
16 | def func():
17 | """A multi-line summary that can be on one line,
18 | But it has a new line so it isn't.
19 |
20 | A description that is not too long.
21 | """
22 |
23 |
24 | # Since the ending quotes will be appended on the same line this
25 | # exceeds the max length.
26 | def func():
27 | """A multi-line summary that can be on one line, Something that is just too longgg."""
28 |
29 |
30 | def func():
31 | """A multi-line summary that can be on one line, Something that is just too long."""
32 |
33 |
34 | def func():
35 | """A multi-line summary that can be on one line, Something that is just too lon."""
36 |
37 |
38 | # Regression for bug found in pylint
39 | # We should re-add the quotes to line length if they will never be on the first line.
40 | class LinesChunk:
41 | """The LinesChunk object computes and stores the hash of some consecutive stripped lines of a lineset."""
42 |
43 |
44 | # Test for multiple periods at the end of the line
45 | def func():
46 | """A very long summary line that needs to be wrapped, A very long summary line that needs to be wrapp...
47 |
48 | A description that is not too long.
49 | """
50 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A very long summary line that needs to be wrapped, A very long summary line that.
3 |
4 | needs to be wrapped.
5 |
6 | A description that is not too long.
7 | """
8 |
9 |
10 | def func():
11 | """A very long multi-line summary line that needs to be wrapped, A very long multi-.
12 |
13 | line summary line that needs to be wrapped.
14 |
15 | A very long summary line that needs to be wrapped.
16 |
17 | A description that is not too long.
18 | """
19 |
20 |
21 | def func():
22 | """A multi-line summary that can be on one line,.
23 |
24 | But it has a new line so it isn't.
25 |
26 | A description that is not too long.
27 | """
28 |
29 |
30 | # Since the ending quotes will be appended on the same line this
31 | # exceeds the max length.
32 | def func():
33 | """A multi-line summary that can be on one line, Something that is just too.
34 |
35 | longgg.
36 | """
37 |
38 |
39 | def func():
40 | """A multi-line summary that can be on one line, Something that is just too.
41 |
42 | long.
43 | """
44 |
45 |
46 | def func():
47 | """A multi-line summary that can be on one line, Something that is just too lon."""
48 |
49 |
50 | # Regression for bug found in pylint
51 | # We should re-add the quotes to line length if they will never be on the first line.
52 | class LinesChunk:
53 | """The LinesChunk object computes and stores the hash of some consecutive stripped.
54 |
55 | lines of a lineset.
56 | """
57 |
58 |
59 | # Test for multiple periods at the end of the line
60 | def func():
61 | """A very long summary line that needs to be wrapped, A very long summary line that.
62 |
63 | needs to be wrapp...
64 |
65 | A description that is not too long.
66 | """
67 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/max_line_length_50.args:
--------------------------------------------------------------------------------
1 | --linewrap-full-docstring
2 | --max-line-length=50
3 | --max-summary-lines=2
4 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/max_line_length_50.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """A very long line that needs to be wrapped especially this sentence."""
3 |
4 | def func():
5 | """Event multi line docstrings need to be wrapped.
6 |
7 | This description is way too long. It definitely needs to be wrapped.
8 | """
9 |
10 | # Regression for bug found in pylint
11 | # We should re-add the quotes to line length if they will never be on the first line.
12 | class LinesChunk:
13 | """The LinesChunk object computes and store."""
14 |
--------------------------------------------------------------------------------
/tests/data/format/linewrap_summary/max_line_length_50.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A very long line that needs to be wrapped
3 | especially this sentence.
4 | """
5 |
6 | def func():
7 | """Event multi line docstrings need to be
8 | wrapped.
9 |
10 | This description is way too long. It definitely needs to be wrapped.
11 | """
12 |
13 | # Regression for bug found in pylint
14 | # We should re-add the quotes to line length if they will never be on the first line.
15 | class LinesChunk:
16 | """The LinesChunk object computes and
17 | store.
18 | """
19 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line
3 | docstring"""
4 |
5 | class InnerClass:
6 | """A multi-line
7 | docstring"""
8 |
9 |
10 | class MyClass:
11 | """A multi-line
12 | docstring
13 | """
14 |
15 | class InnerClass:
16 | """A multi-line
17 | docstring
18 | """
19 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
13 |
14 | class MyClass:
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 | class InnerClass:
21 | """A multi-line.
22 |
23 | docstring
24 | """
25 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func():
9 | """A multi-line
10 | docstring"""
11 |
12 | def inner_func():
13 | """A multi-line
14 | docstring"""
15 |
16 |
17 | def func():
18 | """A multi-line
19 | docstring
20 | """
21 |
22 | def inner_func():
23 | """A multi-line
24 | docstring
25 | """
26 |
27 | def func():
28 | '''A multi-line
29 | docstring'''
30 |
31 | def inner_func():
32 | '''A multi-line
33 | docstring'''
34 |
35 |
36 | def func():
37 | '''A multi-line
38 | docstring
39 | '''
40 |
41 | def inner_func():
42 | '''A multi-line
43 | docstring
44 | '''
45 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func():
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
14 | def inner_func():
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 |
21 | def func():
22 | """A multi-line.
23 |
24 | docstring
25 | """
26 |
27 | def inner_func():
28 | """A multi-line.
29 |
30 | docstring
31 | """
32 |
33 | def func():
34 | """A multi-line.
35 |
36 | docstring
37 | """
38 |
39 | def inner_func():
40 | """A multi-line.
41 |
42 | docstring
43 | """
44 |
45 |
46 | def func():
47 | """A multi-line.
48 |
49 | docstring
50 | """
51 |
52 | def inner_func():
53 | """A multi-line.
54 |
55 | docstring
56 | """
57 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/module_docstring.py:
--------------------------------------------------------------------------------
1 | """A multi-line
2 | docstring"""
3 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A multi-line.
2 |
3 | docstring
4 | """
5 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line
3 | docstring"""
4 |
5 | MYVAR = 1
6 | """A multi-line
7 | docstring
8 | """
9 |
--------------------------------------------------------------------------------
/tests/data/format/multi_line/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | MYVAR = 1
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/dos.py:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 | docstring."""
4 |
5 | var = 1
6 | """A single-line docstring.
7 | """
8 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/dos.py.out:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 |
4 | docstring.
5 | """
6 |
7 | var = 1
8 | """A single-line docstring."""
9 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/mac.py:
--------------------------------------------------------------------------------
1 | var = 1
"""A multi-line.
docstring."""
var = 1
"""A single-line docstring.
"""
--------------------------------------------------------------------------------
/tests/data/format/newlines/mac.py.out:
--------------------------------------------------------------------------------
1 | var = 1
"""A multi-line.
docstring.
"""
var = 1
"""A single-line docstring."""
--------------------------------------------------------------------------------
/tests/data/format/newlines/mixed.err:
--------------------------------------------------------------------------------
1 | Found multiple newline variants in {testfile}. Using variant that occurred first.
2 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/mixed.py:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 | docstring."""
4 |
5 | var = 1
6 | """A single-line docstring.
7 | """
8 |
9 | var = 1
10 | """A multi-line.
11 | docstring."""
12 |
13 | var = 1
14 | """A single-line docstring.
15 | """
16 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/mixed.py.out:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 |
4 | docstring.
5 | """
6 |
7 | var = 1
8 | """A single-line docstring."""
9 |
10 | var = 1
11 | """A multi-line.
12 |
13 | docstring.
14 | """
15 |
16 | var = 1
17 | """A single-line docstring."""
18 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/none.args:
--------------------------------------------------------------------------------
1 | --linewrap-full-docstring
2 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/none.py:
--------------------------------------------------------------------------------
1 | """A multi-line docstring that should get split at this period. and not afterwards as it is way too long."""
--------------------------------------------------------------------------------
/tests/data/format/newlines/none.py-0d0a.out:
--------------------------------------------------------------------------------
1 | """A multi-line docstring that should get split at this period.
2 |
3 | and not afterwards as it is way too long.
4 | """
--------------------------------------------------------------------------------
/tests/data/format/newlines/none.py.out:
--------------------------------------------------------------------------------
1 | """A multi-line docstring that should get split at this period.
2 |
3 | and not afterwards as it is way too long.
4 | """
--------------------------------------------------------------------------------
/tests/data/format/newlines/unix.py:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 | docstring."""
4 |
5 | var = 1
6 | """A single-line docstring.
7 | """
8 |
--------------------------------------------------------------------------------
/tests/data/format/newlines/unix.py.out:
--------------------------------------------------------------------------------
1 | var = 1
2 | """A multi-line.
3 |
4 | docstring.
5 | """
6 |
7 | var = 1
8 | """A single-line docstring."""
9 |
--------------------------------------------------------------------------------
/tests/data/format/no_changes/multi_line_variables.py:
--------------------------------------------------------------------------------
1 | var = """A multi-line
2 | docstring"""
3 |
4 | var = """A multi-line
5 | docstring
6 | """
7 |
--------------------------------------------------------------------------------
/tests/data/format/no_changes/multi_line_variables.py.out:
--------------------------------------------------------------------------------
1 | var = """A multi-line
2 | docstring"""
3 |
4 | var = """A multi-line
5 | docstring
6 | """
7 |
--------------------------------------------------------------------------------
/tests/data/format/no_whitespace_stripper/module_docstring.args:
--------------------------------------------------------------------------------
1 | --no-strip-whitespaces
2 |
--------------------------------------------------------------------------------
/tests/data/format/no_whitespace_stripper/module_docstring.py:
--------------------------------------------------------------------------------
1 | """ A multi-line
2 | docstring
3 | """
4 |
5 | """ A multi-line
6 | docstring
7 | """
8 |
9 | """ My docstring.
10 |
11 | My indented section
12 | """
13 |
--------------------------------------------------------------------------------
/tests/data/format/no_whitespace_stripper/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """ A multi-line .
2 |
3 | docstring
4 | """
5 |
6 | """ A multi-line .
7 |
8 | docstring
9 |
10 | """
11 |
12 | """ My docstring.
13 |
14 |
15 |
16 | My indented section
17 |
18 | """
19 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_closing_quotes.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-closing-quotes
3 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_closing_quotes.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
5 | def func():
6 | """
7 | A docstring
8 | """
9 |
10 | def func():
11 | """A docstring."""
12 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_closing_quotes.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def func():
5 | """A docstring.
6 | """
7 |
8 | def func():
9 | """A docstring."""
10 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_header_line.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-numpydoc-name-type-spacing
3 | --no-numpydoc-section-order
4 | --no-numpydoc-section-spacing
5 | --no-final-period
6 | --no-closing-quotes
7 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_header_line.py:
--------------------------------------------------------------------------------
1 | numpydoc_style.py
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_header_line.py.out:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 | References
3 | ----------
4 | NumPy docstring style guide:
5 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules"""
6 | import math
7 |
8 | EULER_NUMBER = math.e
9 | """Euler's number.
10 |
11 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
12 | constant.
13 | References
14 | ----------
15 | E (mathematical constant)
16 | https://en.wikipedia.org/wiki/E_(mathematical_constant)
17 | Notes
18 | -----
19 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
20 | is used in the equation for continuously-compouned interest.
21 |
22 | It is also the sum of the reciprocals of the whole numbers starting
23 | with zero, which is related to some calculus-related properties
24 | mathemeticians find elegant.
25 | """
26 |
27 |
28 | def sincos(theta):
29 | """Returns
30 | ----------
31 | sin: float
32 | the sine of theta
33 | cos: float
34 | the cosine of theta
35 | Raises
36 | ------
37 | TypeError
38 | If `theta` is not a float.
39 | Parameters
40 | ----------
41 | theta: float
42 | the angle at which to calculate the sine and cosine.
43 | """
44 | return math.sin(theta), math.cos(theta)
45 |
46 |
47 | def fibbonacci():
48 | """Generate the Fibonacci sequence.
49 |
50 | Each term is the sum of the two previous; conventionally starts
51 | with two ones.
52 | References
53 | ----------
54 | Fibonacci numbers
55 | https://en.wikipedia.org/wiki/Fibonacci_number
56 | Yields
57 | ------
58 | int"""
59 | curr = 1
60 | last = 0
61 | while True:
62 | yield curr
63 | curr, last = curr + last, curr
64 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_parameter_spacing.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --numpydoc-name-type-spacing
3 | --no-numpydoc-section-order
4 | --no-numpydoc-section-spacing
5 | --no-numpydoc-section-hyphen-length
6 | --no-final-period
7 | --no-closing-quotes
8 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_parameter_spacing.py:
--------------------------------------------------------------------------------
1 | numpydoc_style.py
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_parameter_spacing.py.out:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 | References
3 | -----
4 | NumPy docstring style guide:
5 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules"""
6 | import math
7 |
8 | EULER_NUMBER = math.e
9 | """Euler's number.
10 |
11 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
12 | constant.
13 | References
14 | -----
15 | E (mathematical constant)
16 | https://en.wikipedia.org/wiki/E_(mathematical_constant)
17 | Notes
18 | ---
19 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
20 | is used in the equation for continuously-compouned interest.
21 |
22 | It is also the sum of the reciprocals of the whole numbers starting
23 | with zero, which is related to some calculus-related properties
24 | mathemeticians find elegant.
25 | """
26 |
27 |
28 | def sincos(theta):
29 | """Returns
30 | ----
31 | sin : float
32 | the sine of theta
33 | cos : float
34 | the cosine of theta
35 | Raises
36 | ---
37 | TypeError
38 | If `theta` is not a float.
39 | Parameters
40 | -----
41 | theta : float
42 | the angle at which to calculate the sine and cosine.
43 | """
44 | return math.sin(theta), math.cos(theta)
45 |
46 |
47 | def fibbonacci():
48 | """Generate the Fibonacci sequence.
49 |
50 | Each term is the sum of the two previous; conventionally starts
51 | with two ones.
52 | References
53 | -----
54 | Fibonacci numbers
55 | https://en.wikipedia.org/wiki/Fibonacci_number
56 | Yields
57 | ---
58 | int"""
59 | curr = 1
60 | last = 0
61 | while True:
62 | yield curr
63 | curr, last = curr + last, curr
64 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_regression.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_regression.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """
3 | A docstring"""
4 |
5 | def func():
6 | """
7 | A docstring
8 | """
9 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_regression.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def func():
5 | """A docstring."""
6 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_ordering.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-numpydoc-name-type-spacing
3 | --no-numpydoc-section-spacing
4 | --no-numpydoc-section-hyphen-length
5 | --no-final-period
6 | --no-closing-quotes
7 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_ordering.py:
--------------------------------------------------------------------------------
1 | numpydoc_style.py
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_ordering.py.out:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 | References
3 | -----
4 | NumPy docstring style guide:
5 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules"""
6 | import math
7 |
8 | EULER_NUMBER = math.e
9 | """Euler's number.
10 |
11 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
12 | constant.
13 | Notes
14 | ---
15 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
16 | is used in the equation for continuously-compouned interest.
17 |
18 | It is also the sum of the reciprocals of the whole numbers starting
19 | with zero, which is related to some calculus-related properties
20 | mathemeticians find elegant.
21 |
22 | References
23 | -----
24 | E (mathematical constant)
25 | https://en.wikipedia.org/wiki/E_(mathematical_constant)"""
26 |
27 |
28 | def sincos(theta):
29 | """Parameters
30 | -----
31 | theta: float
32 | the angle at which to calculate the sine and cosine.
33 |
34 | Returns
35 | ----
36 | sin: float
37 | the sine of theta
38 | cos: float
39 | the cosine of theta
40 | Raises
41 | ---
42 | TypeError
43 | If `theta` is not a float."""
44 | return math.sin(theta), math.cos(theta)
45 |
46 |
47 | def fibbonacci():
48 | """Generate the Fibonacci sequence.
49 |
50 | Each term is the sum of the two previous; conventionally starts
51 | with two ones.
52 | Yields
53 | ---
54 | int
55 | References
56 | -----
57 | Fibonacci numbers
58 | https://en.wikipedia.org/wiki/Fibonacci_number"""
59 | curr = 1
60 | last = 0
61 | while True:
62 | yield curr
63 | curr, last = curr + last, curr
64 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_spacing.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-numpydoc-name-type-spacing
3 | --no-numpydoc-section-order
4 | --no-numpydoc-section-hyphen-length
5 | --no-final-period
6 | --no-closing-quotes
7 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_spacing.py:
--------------------------------------------------------------------------------
1 | numpydoc_style.py
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_section_spacing.py.out:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 |
3 | References
4 | -----
5 | NumPy docstring style guide:
6 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules
7 | """
8 | import math
9 |
10 | EULER_NUMBER = math.e
11 | """Euler's number.
12 |
13 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
14 | constant.
15 |
16 | References
17 | -----
18 | E (mathematical constant)
19 | https://en.wikipedia.org/wiki/E_(mathematical_constant)
20 |
21 | Notes
22 | ---
23 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
24 | is used in the equation for continuously-compouned interest.
25 |
26 | It is also the sum of the reciprocals of the whole numbers starting
27 | with zero, which is related to some calculus-related properties
28 | mathemeticians find elegant.
29 | """
30 |
31 |
32 | def sincos(theta):
33 | """Returns
34 | ----
35 | sin: float
36 | the sine of theta
37 | cos: float
38 | the cosine of theta
39 |
40 | Raises
41 | ---
42 | TypeError
43 | If `theta` is not a float.
44 |
45 | Parameters
46 | -----
47 | theta: float
48 | the angle at which to calculate the sine and cosine.
49 | """
50 | return math.sin(theta), math.cos(theta)
51 |
52 |
53 | def fibbonacci():
54 | """Generate the Fibonacci sequence.
55 |
56 | Each term is the sum of the two previous; conventionally starts
57 | with two ones.
58 |
59 | References
60 | -----
61 | Fibonacci numbers
62 | https://en.wikipedia.org/wiki/Fibonacci_number
63 |
64 | Yields
65 | ---
66 | int
67 | """
68 | curr = 1
69 | last = 0
70 | while True:
71 | yield curr
72 | curr, last = curr + last, curr
73 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-final-period
3 | --no-closing-quotes
4 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style.py:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 | References
3 | -----
4 | NumPy docstring style guide:
5 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules"""
6 | import math
7 |
8 | EULER_NUMBER = math.e
9 | """Euler's number.
10 |
11 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
12 | constant.
13 | References
14 | -----
15 | E (mathematical constant)
16 | https://en.wikipedia.org/wiki/E_(mathematical_constant)
17 | Notes
18 | ---
19 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
20 | is used in the equation for continuously-compouned interest.
21 |
22 | It is also the sum of the reciprocals of the whole numbers starting
23 | with zero, which is related to some calculus-related properties
24 | mathemeticians find elegant.
25 | """
26 |
27 |
28 | def sincos(theta):
29 | """Returns
30 | ----
31 | sin: float
32 | the sine of theta
33 | cos: float
34 | the cosine of theta
35 | Raises
36 | ---
37 | TypeError
38 | If `theta` is not a float.
39 | Parameters
40 | -----
41 | theta: float
42 | the angle at which to calculate the sine and cosine.
43 | """
44 | return math.sin(theta), math.cos(theta)
45 |
46 |
47 | def fibbonacci():
48 | """Generate the Fibonacci sequence.
49 |
50 | Each term is the sum of the two previous; conventionally starts
51 | with two ones.
52 | References
53 | -----
54 | Fibonacci numbers
55 | https://en.wikipedia.org/wiki/Fibonacci_number
56 | Yields
57 | ---
58 | int"""
59 | curr = 1
60 | last = 0
61 | while True:
62 | yield curr
63 | curr, last = curr + last, curr
64 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style.py.out:
--------------------------------------------------------------------------------
1 | """Example module for numpydoc docstring style.
2 |
3 | References
4 | ----------
5 | NumPy docstring style guide:
6 | https://numpydoc.readthedocs.io/en/latest/format.html#documenting-modules
7 | """
8 | import math
9 |
10 | EULER_NUMBER = math.e
11 | """Euler's number.
12 |
13 | Not related to Euler's constant (sometimes called the Euler-Mascheroni
14 | constant.
15 |
16 | Notes
17 | -----
18 | It is the limit of ``(1 + 1/n)**n`` as n approaches infinity, so it
19 | is used in the equation for continuously-compouned interest.
20 |
21 | It is also the sum of the reciprocals of the whole numbers starting
22 | with zero, which is related to some calculus-related properties
23 | mathemeticians find elegant.
24 |
25 | References
26 | ----------
27 | E (mathematical constant)
28 | https://en.wikipedia.org/wiki/E_(mathematical_constant)
29 | """
30 |
31 |
32 | def sincos(theta):
33 | """Parameters
34 | -------------
35 | theta : float
36 | the angle at which to calculate the sine and cosine.
37 |
38 | Returns
39 | -------
40 | sin : float
41 | the sine of theta
42 | cos : float
43 | the cosine of theta
44 |
45 | Raises
46 | ------
47 | TypeError
48 | If `theta` is not a float.
49 | """
50 | return math.sin(theta), math.cos(theta)
51 |
52 |
53 | def fibbonacci():
54 | """Generate the Fibonacci sequence.
55 |
56 | Each term is the sum of the two previous; conventionally starts
57 | with two ones.
58 |
59 | Yields
60 | ------
61 | int
62 |
63 | References
64 | ----------
65 | Fibonacci numbers
66 | https://en.wikipedia.org/wiki/Fibonacci_number
67 | """
68 | curr = 1
69 | last = 0
70 | while True:
71 | yield curr
72 | curr, last = curr + last, curr
73 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style_without_type.args:
--------------------------------------------------------------------------------
1 | --style=numpydoc
2 | --no-final-period
3 | --no-closing-quotes
4 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style_without_type.py:
--------------------------------------------------------------------------------
1 | def sincos(theta):
2 | """Returns
3 | ----
4 | sin: float
5 | the sine of theta
6 | cos: float
7 | the cosine of theta
8 | Parameters
9 | -----
10 | theta:
11 | the angle at which to calculate the sine and cosine.
12 | """
13 | return math.sin(theta), math.cos(theta)
14 |
--------------------------------------------------------------------------------
/tests/data/format/numpydoc/numpydoc_style_without_type.py.out:
--------------------------------------------------------------------------------
1 | def sincos(theta):
2 | """Parameters
3 | -------------
4 | theta:
5 | the angle at which to calculate the sine and cosine.
6 |
7 | Returns
8 | -------
9 | sin : float
10 | the sine of theta
11 | cos : float
12 | the cosine of theta
13 | """
14 | return math.sin(theta), math.cos(theta)
15 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | ''' A multi-line
3 | docstring
4 | '''
5 |
6 | class InnerClass:
7 | ''' A multi-line
8 | docstring
9 | '''
10 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | ''' A docstring. '''
3 |
4 | def inner_func():
5 | ''' A docstring. '''
6 |
7 |
8 | def func():
9 | ''' A multi-line
10 | docstring
11 | '''
12 |
13 | def inner_func():
14 | ''' A multi-line
15 | docstring
16 | '''
17 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func():
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
14 | def inner_func():
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/module_docstring.py:
--------------------------------------------------------------------------------
1 | ''' A multi-line
2 | docstring
3 | '''
4 |
5 | ''' A multi-line
6 | docstring
7 | '''
8 |
9 | ''' My docstring.
10 |
11 | My indented section
12 | '''
13 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A multi-line.
2 |
3 | docstring
4 | """
5 |
6 | """A multi-line.
7 |
8 | docstring
9 | """
10 |
11 | """My docstring.
12 |
13 | My indented section
14 | """
15 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/single_quote_docstring.args:
--------------------------------------------------------------------------------
1 | --no-quotes-type
2 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/single_quote_docstring.py:
--------------------------------------------------------------------------------
1 | def func():
2 | " A docstring. "
3 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/single_quote_docstring.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | "A docstring."
3 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | ''' A multi-line
3 | docstring
4 | '''
5 |
6 | MYVAR = 1
7 | ''' A multi-line
8 | docstring
9 | '''
10 |
--------------------------------------------------------------------------------
/tests/data/format/quotes_type/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | MYVAR = 1
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """Summary. Body."""
3 |
4 | class InnerClass:
5 | """Summary. Body."""
6 |
7 |
8 | class MyClass:
9 | """Summary without period Body."""
10 |
11 | class InnerClass:
12 | """Summary without period Body."""
13 |
14 |
15 | class MyClass:
16 | """Summary. Body without period"""
17 |
18 | class InnerClass:
19 | """Summary. Body without period"""
20 |
21 |
22 | class MyClass:
23 | """Summary.
24 | Body without period"""
25 |
26 | class InnerClass:
27 | """Summary.
28 | Body without period"""
29 |
30 |
31 | class MyClass:
32 | """Summary over multiple
33 | lines.
34 | """
35 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """Summary.
3 |
4 | Body.
5 | """
6 |
7 | class InnerClass:
8 | """Summary.
9 |
10 | Body.
11 | """
12 |
13 |
14 | class MyClass:
15 | """Summary without period Body."""
16 |
17 | class InnerClass:
18 | """Summary without period Body."""
19 |
20 |
21 | class MyClass:
22 | """Summary.
23 |
24 | Body without period
25 | """
26 |
27 | class InnerClass:
28 | """Summary.
29 |
30 | Body without period
31 | """
32 |
33 |
34 | class MyClass:
35 | """Summary.
36 |
37 | Body without period
38 | """
39 |
40 | class InnerClass:
41 | """Summary.
42 |
43 | Body without period
44 | """
45 |
46 |
47 | class MyClass:
48 | """Summary over multiple.
49 |
50 | lines.
51 | """
52 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/dots.args:
--------------------------------------------------------------------------------
1 | --max-summary-lines=2
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/dots.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """We shouldn't split sys.path."""
3 |
4 |
5 | def func():
6 | """We should not add extra lines after the dot
7 | to it.
8 | """
9 |
10 |
11 | def func():
12 | """We should not split for e.g. here."""
13 |
14 |
15 | def func():
16 | """We should not split for i.e. here."""
17 |
18 |
19 | def func():
20 | """We should not add line after i.e., sys.path, e.g., etc. but etc. in particular
21 |
22 | is harder right ?
23 | """
24 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/dots.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """We shouldn't split sys.path."""
3 |
4 |
5 | def func():
6 | """We should not add extra lines after the dot
7 | to it.
8 | """
9 |
10 |
11 | def func():
12 | """We should not split for e.g. here."""
13 |
14 |
15 | def func():
16 | """We should not split for i.e. here."""
17 |
18 |
19 | def func():
20 | """We should not add line after i.e., sys.path, e.g., etc. but etc. in particular.
21 |
22 | is harder right ?
23 | """
24 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/empty_summary.args:
--------------------------------------------------------------------------------
1 | --linewrap-full-docstring
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/empty_summary.py:
--------------------------------------------------------------------------------
1 | class GithubIssue151:
2 | """ """
3 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/empty_summary.py.out:
--------------------------------------------------------------------------------
1 | class GithubIssue151:
2 | """"""
3 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/ending_quotes.args:
--------------------------------------------------------------------------------
1 | --no-closing-quotes
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/ending_quotes.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """We should keep the position of the quotes
3 | as is.
4 | """
5 |
6 |
7 | def func():
8 | """We should keep the position of the quotes
9 | as is."""
10 |
11 |
12 | def func():
13 | """We should keep the position of the quotes
14 |
15 | as is.
16 | """
17 |
18 |
19 | def func():
20 | """We should keep the position of the quotes
21 |
22 | as is."""
23 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/ending_quotes.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """We should keep the position of the quotes.
3 |
4 | as is.
5 | """
6 |
7 |
8 | def func():
9 | """We should keep the position of the quotes.
10 |
11 | as is."""
12 |
13 |
14 | def func():
15 | """We should keep the position of the quotes.
16 |
17 | as is.
18 | """
19 |
20 |
21 | def func():
22 | """We should keep the position of the quotes.
23 |
24 | as is."""
25 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """Summary. Body."""
3 |
4 | def inner_func():
5 | """Summary. Body."""
6 |
7 |
8 | def func():
9 | """Summary without period Body."""
10 |
11 | def inner_func():
12 | """Summary without period Body."""
13 |
14 |
15 | def func():
16 | """Summary. Body without period"""
17 |
18 | def inner_func():
19 | """Summary. Body without period"""
20 |
21 |
22 | def func():
23 | """Summary.
24 | Body without period"""
25 |
26 | def inner_func():
27 | """Summary.
28 | Body without period"""
29 |
30 |
31 | def func():
32 | """
33 | Summary.
34 |
35 | Body of the docstring.
36 | """
37 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """Summary.
3 |
4 | Body.
5 | """
6 |
7 | def inner_func():
8 | """Summary.
9 |
10 | Body.
11 | """
12 |
13 |
14 | def func():
15 | """Summary without period Body."""
16 |
17 | def inner_func():
18 | """Summary without period Body."""
19 |
20 |
21 | def func():
22 | """Summary.
23 |
24 | Body without period
25 | """
26 |
27 | def inner_func():
28 | """Summary.
29 |
30 | Body without period
31 | """
32 |
33 |
34 | def func():
35 | """Summary.
36 |
37 | Body without period
38 | """
39 |
40 | def inner_func():
41 | """Summary.
42 |
43 | Body without period
44 | """
45 |
46 |
47 | def func():
48 | """
49 | Summary.
50 |
51 | Body of the docstring.
52 | """
53 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_1.args:
--------------------------------------------------------------------------------
1 | --max-summary-lines=1
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_1.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary
4 | is way
5 | too long.
6 |
7 | Description
8 | """
9 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_1.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long.
3 |
4 | summary
5 | is way
6 | too long.
7 |
8 | Description
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_2.args:
--------------------------------------------------------------------------------
1 | --max-summary-lines=2
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_2.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary
4 | is way
5 | too long.
6 |
7 | Description
8 | """
9 |
10 |
11 | def func():
12 | """A long summary
13 | without a period
14 | """
15 |
16 |
17 | def func():
18 | """A summary.
19 |
20 | A body
21 | """
22 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_2.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary.
4 |
5 | is way
6 | too long.
7 |
8 | Description
9 | """
10 |
11 |
12 | def func():
13 | """A long summary
14 | without a period.
15 | """
16 |
17 |
18 | def func():
19 | """A summary.
20 |
21 | A body
22 | """
23 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_3.args:
--------------------------------------------------------------------------------
1 | --max-summary-lines=3
2 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_3.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary
4 | is way
5 | too long.
6 |
7 | Description
8 | """
9 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_3.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary
4 | is way.
5 |
6 | too long.
7 |
8 | Description
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_default.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long
3 | summary
4 | is way
5 | too long.
6 |
7 | Description
8 | """
9 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_is_default.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long.
3 |
4 | summary
5 | is way
6 | too long.
7 |
8 | Description
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_with_dot.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long. summary
3 | is way
4 | too long.
5 |
6 | Description
7 | """
8 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/max_summary_lines/max_lines_with_dot.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """My long.
3 |
4 | summary
5 | is way
6 | too long.
7 |
8 | Description
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/module_docstring.py:
--------------------------------------------------------------------------------
1 | """Summary. Body."""
2 |
3 | """Summary without period Body."""
4 |
5 | """Summary. Body without period"""
6 |
7 | """Summary.
8 | Body without period"""
9 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """Summary.
2 |
3 | Body.
4 | """
5 |
6 | """Summary without period Body."""
7 |
8 | """Summary.
9 |
10 | Body without period
11 | """
12 |
13 | """Summary.
14 |
15 | Body without period
16 | """
17 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/regression_151.py:
--------------------------------------------------------------------------------
1 | class GithubIssue151:
2 | """ """
3 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/regression_151.py.out:
--------------------------------------------------------------------------------
1 | class GithubIssue151:
2 | """"""
3 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """Summary. Body."""
3 |
4 | MYVAR = 1
5 | """Summary without period Body."""
6 |
7 | MYVAR = 1
8 | """Summary. Body without period"""
9 |
10 | MYVAR = 1
11 | """Summary.
12 | Body without period"""
13 |
--------------------------------------------------------------------------------
/tests/data/format/summary_splitter/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """Summary.
3 |
4 | Body.
5 | """
6 |
7 | MYVAR = 1
8 | """Summary without period Body."""
9 |
10 | MYVAR = 1
11 | """Summary.
12 |
13 | Body without period
14 | """
15 |
16 | MYVAR = 1
17 | """Summary.
18 |
19 | Body without period
20 | """
21 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/class_docstring.py:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """ A multi-line
3 | docstring
4 | """
5 |
6 | class InnerClass:
7 | """ A multi-line
8 | docstring
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/class_docstring.py.out:
--------------------------------------------------------------------------------
1 | class MyClass:
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | class InnerClass:
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """ A docstring. """
3 |
4 | def inner_func():
5 | """ A docstring. """
6 |
7 |
8 | def func():
9 | """ A multi-line
10 | docstring
11 | """
12 |
13 | def inner_func():
14 | """ A multi-line
15 | docstring
16 | """
17 |
18 |
19 | def func():
20 | """Summary.
21 |
22 | Body.
23 |
24 | """
25 |
26 | def inner_func():
27 | """Summary.
28 |
29 | Body.
30 |
31 |
32 |
33 | """
34 |
35 | def inner_func():
36 | """Summary.
37 |
38 |
39 | Body.
40 |
41 | """
42 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/function_docstrings.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring."""
3 |
4 | def inner_func():
5 | """A docstring."""
6 |
7 |
8 | def func():
9 | """A multi-line.
10 |
11 | docstring
12 | """
13 |
14 | def inner_func():
15 | """A multi-line.
16 |
17 | docstring
18 | """
19 |
20 |
21 | def func():
22 | """Summary.
23 |
24 | Body.
25 | """
26 |
27 | def inner_func():
28 | """Summary.
29 |
30 | Body.
31 | """
32 |
33 | def inner_func():
34 | """Summary.
35 |
36 | Body.
37 | """
38 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/module_docstring.py:
--------------------------------------------------------------------------------
1 | """ A multi-line
2 | docstring
3 | """
4 |
5 | """ A multi-line
6 | docstring
7 | """
8 |
9 | """ My docstring.
10 |
11 | My indented section
12 | """
13 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/module_docstring.py.out:
--------------------------------------------------------------------------------
1 | """A multi-line.
2 |
3 | docstring
4 | """
5 |
6 | """A multi-line.
7 |
8 | docstring
9 | """
10 |
11 | """My docstring.
12 |
13 | My indented section
14 | """
15 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/single_quote_docstring.args:
--------------------------------------------------------------------------------
1 | --no-quotes-type
2 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/single_quote_docstring.py:
--------------------------------------------------------------------------------
1 | def func():
2 | " A docstring. "
3 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/single_quote_docstring.py.out:
--------------------------------------------------------------------------------
1 | def func():
2 | "A docstring."
3 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/variable_docstring.py:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """ A multi-line
3 | docstring
4 | """
5 |
6 | MYVAR = 1
7 | """ A multi-line
8 | docstring
9 | """
10 |
--------------------------------------------------------------------------------
/tests/data/format/whitespace_stripper/variable_docstring.py.out:
--------------------------------------------------------------------------------
1 | MYVAR = 1
2 | """A multi-line.
3 |
4 | docstring
5 | """
6 |
7 | MYVAR = 1
8 | """A multi-line.
9 |
10 | docstring
11 | """
12 |
--------------------------------------------------------------------------------
/tests/data/incorrect_python_file.py:
--------------------------------------------------------------------------------
1 | var = """Unclosed string
2 |
--------------------------------------------------------------------------------
/tests/data/utils/find_docstrings/dictionary.py:
--------------------------------------------------------------------------------
1 | MY_DICT = {
2 | "key": "value",
3 | }
4 |
--------------------------------------------------------------------------------
/tests/data/utils/find_docstrings/function_docstrings.py:
--------------------------------------------------------------------------------
1 | def func():
2 | """A docstring"""
3 |
4 | def inner_func():
5 | """A docstring"""
6 |
7 |
8 | def func():
9 | """A multi-line
10 | docstring"""
11 |
12 | def inner_func():
13 | """A multi-line
14 | docstring"""
15 |
16 |
17 | def func():
18 | """A multi-line
19 | docstring
20 | """
21 |
22 | def inner_func():
23 | """A multi-line
24 | docstring
25 | """
26 |
27 |
28 | def func():
29 | '''A docstring'''
30 |
31 | def inner_func():
32 | '''A docstring'''
33 |
34 |
35 | def func():
36 | '''A multi-line
37 | docstring'''
38 |
39 | def inner_func():
40 | '''A multi-line
41 | docstring'''
42 |
43 |
44 | def func():
45 | '''A multi-line
46 | docstring
47 | '''
48 |
49 | def inner_func():
50 | '''A multi-line
51 | docstring
52 | '''
53 |
--------------------------------------------------------------------------------
/tests/data/utils/find_docstrings/module_docstrings.py:
--------------------------------------------------------------------------------
1 | # Some comments
2 |
3 | """A multi-line docstring
4 | """
5 |
--------------------------------------------------------------------------------
/tests/data/utils/find_nothing/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_nothing/README.md
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/file_one.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/file_one.py
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/file_two.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/file_two.txt
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/inner_directory/inner_file_one.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/inner_directory/inner_file_one.py
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/inner_directory/inner_file_two.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/inner_directory/inner_file_two.txt
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/inner_directory/inner_inner_directory/inner_inner_file_one.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/inner_directory/inner_inner_directory/inner_inner_file_one.py
--------------------------------------------------------------------------------
/tests/data/utils/find_recursive_files/inner_directory/inner_inner_directory/inner_inner_file_one.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_recursive_files/inner_directory/inner_inner_directory/inner_inner_file_one.txt
--------------------------------------------------------------------------------
/tests/data/utils/find_underscore_files/____file_five.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_underscore_files/____file_five.py
--------------------------------------------------------------------------------
/tests/data/utils/find_underscore_files/___file_four.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_underscore_files/___file_four.txt
--------------------------------------------------------------------------------
/tests/data/utils/find_underscore_files/__file_three.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_underscore_files/__file_three.py
--------------------------------------------------------------------------------
/tests/data/utils/find_underscore_files/_file_two.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_underscore_files/_file_two.py
--------------------------------------------------------------------------------
/tests/data/utils/find_underscore_files/file_one.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielNoord/pydocstringformatter/60e016d59f4b02cdd07a0f1a9492b807be76ed85/tests/data/utils/find_underscore_files/file_one.py
--------------------------------------------------------------------------------
/tests/test_conflicting_formatters.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from collections.abc import Generator
4 | from contextlib import contextmanager
5 | from pathlib import Path
6 |
7 | import pytest
8 |
9 | from pydocstringformatter import _formatting
10 | from pydocstringformatter import _testutils as test_utils
11 | from pydocstringformatter._formatting import Formatter
12 | from pydocstringformatter._utils import UnstableResultError
13 | from pydocstringformatter.run import _Run
14 |
15 |
16 | @contextmanager
17 | def patched_run(formatters: list[Formatter]) -> Generator[type[_Run]]:
18 | """Patches formatters so Run only uses the provided formatters."""
19 | old_formatters = _formatting.FORMATTERS
20 | _formatting.FORMATTERS = formatters
21 | yield _Run
22 | _formatting.FORMATTERS = old_formatters
23 |
24 |
25 | @pytest.mark.parametrize(
26 | "formatters,expected_errors",
27 | [
28 | (
29 | [test_utils.MakeAFormatter(), test_utils.MakeBFormatter()],
30 | ["Conflicting formatters"],
31 | ),
32 | (
33 | [test_utils.MakeBFormatter(), test_utils.AddBFormatter()],
34 | ["not able to make stable changes"],
35 | ),
36 | (
37 | [
38 | test_utils.MakeBFormatter(),
39 | test_utils.AddBFormatter(),
40 | test_utils.MakeAFormatter(),
41 | ],
42 | ["Conflicting formatters:", "Diff too intricate to compute"],
43 | ),
44 | ],
45 | )
46 | def test_conflicting_formatters(
47 | formatters: list[Formatter],
48 | expected_errors: list[str],
49 | tmp_path: Path,
50 | ) -> None:
51 | """Tests that conflicting formatters raise an error."""
52 | tmp_file = tmp_path / "test.py"
53 | with open(tmp_file, "w", encoding="utf-8") as f:
54 | content = [
55 | '"""AAA AA AAA"""',
56 | ]
57 | f.writelines(content)
58 |
59 | with patched_run(formatters) as run:
60 | with pytest.raises(UnstableResultError) as err:
61 | run([str(tmp_file)])
62 |
63 | for expect_err in expected_errors:
64 | assert expect_err in str(err.value), str(err.value)
65 |
--------------------------------------------------------------------------------
/tests/test_exceptions.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from pydocstringformatter import _utils, run_docstring_formatter
6 |
7 | HERE = Path(__file__)
8 | DATA = HERE.parent / "data"
9 |
10 |
11 | def test_exception_on_incorrect_python() -> None:
12 | """Test that we raise the correct exception on unparsable python files."""
13 | with pytest.raises(_utils.ParsingError):
14 | run_docstring_formatter([str(DATA / "incorrect_python_file.py")])
15 |
--------------------------------------------------------------------------------
/tests/test_formatter.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from pydocstringformatter._formatting import FORMATTERS
4 |
5 |
6 | def test_formatter_names() -> None:
7 | """Test that each formatter name exists and is unique."""
8 | formatter_names: set[str] = set()
9 | for formatter in FORMATTERS:
10 | assert formatter.name, "Each formatter should have a name set."
11 | assert (
12 | formatter.name not in formatter_names
13 | ), "Each formatter should have an unique name."
14 | formatter_names.add(formatter.name)
15 |
--------------------------------------------------------------------------------
/tests/test_formatting.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import os
4 | import pathlib
5 | from pathlib import Path
6 |
7 | import pytest
8 |
9 | import pydocstringformatter
10 |
11 | HERE = Path(__file__)
12 | TEST_DATA = HERE.parent / "data" / "format"
13 |
14 | # Get all the test files
15 | TESTS: list[str] = []
16 | TEST_NAMES: list[str] = []
17 | for dirname, _, files in os.walk(TEST_DATA):
18 | for file in files:
19 | if file.endswith(".py"):
20 | dirpath = Path(dirname)
21 | TESTS.append(str(dirpath / file))
22 | TEST_NAMES.append(f"{dirpath.stem}-{file}")
23 |
24 |
25 | @pytest.mark.parametrize(
26 | "test_file",
27 | TESTS,
28 | ids=TEST_NAMES,
29 | )
30 | def test_formatting(
31 | test_file: str, capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path
32 | ) -> None:
33 | """Test that we correctly format all files in the format directory.
34 |
35 | We create and write to a temporary file so that the original test file
36 | isn't overwritten and the 'py.out' file can represent an actual
37 | python file instead of a diff.
38 |
39 | Everything is tested in bytes as we want to preserve the type of the
40 | line endings of the original file.
41 | """
42 | # Setup
43 | temp_file_name = str(tmp_path / "test_file.py")
44 |
45 | # Check if there is a specific output for the standard newline of this OS.
46 | # For example, this allows testing that in files without any initial newlines
47 | # we use the standard newline whenever we need one.
48 | test_name = f"{test_file}-{os.linesep.encode().hex()}.out"
49 | if not os.path.isfile(test_name):
50 | # This is the regular output file.
51 | test_name = test_file + ".out"
52 |
53 | with open(test_name, "rb") as f:
54 | expected_output = f.read()
55 |
56 | # Get original lines from test file and write to temporary file
57 | with open(test_file, "rb") as f:
58 | original_bytes = f.read()
59 | with open(temp_file_name, "wb") as f:
60 | f.write(original_bytes)
61 |
62 | # Get any additional args as specified by an .args file
63 | additional_args: list[str] = []
64 | if os.path.exists(test_file.replace(".py", ".args")):
65 | with open(test_file.replace(".py", ".args"), encoding="utf-8") as f:
66 | additional_args = [i.rstrip("\n") for i in f.readlines()]
67 |
68 | # Get message on stderr
69 | if os.path.exists(test_file.replace(".py", ".err")):
70 | with open(test_file.replace(".py", ".err"), encoding="utf-8") as f:
71 | error_message = f.read()
72 | else:
73 | error_message = ""
74 |
75 | pydocstringformatter.run_docstring_formatter(
76 | [temp_file_name, "--write"] + additional_args
77 | )
78 |
79 | output = capsys.readouterr()
80 | assert output.err == error_message.format(testfile=os.path.abspath(temp_file_name))
81 | with open(temp_file_name, "rb") as f:
82 | assert f.read() == expected_output
83 |
--------------------------------------------------------------------------------
/tests/test_run.py:
--------------------------------------------------------------------------------
1 | # pylint: disable = redefined-outer-name
2 | import os
3 | import sys
4 | from pathlib import Path
5 |
6 | import pytest
7 |
8 | import pydocstringformatter
9 | from pydocstringformatter._formatting import FORMATTERS
10 | from pydocstringformatter._formatting.base import StringFormatter
11 | from pydocstringformatter._formatting.formatters_pep257 import (
12 | SplitSummaryAndDocstringFormatter,
13 | )
14 | from pydocstringformatter._testutils import FormatterAsserter
15 |
16 |
17 | def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None:
18 | """Test that we display a help message when no arguments are provided."""
19 | sys.argv = ["pydocstringformatter"]
20 | pydocstringformatter.run_docstring_formatter()
21 | out, err = capsys.readouterr()
22 | assert out.startswith("usage: pydocstringformatter [-h]")
23 |
24 | # Test that we print help messages for individual formatters as well
25 | # Default formatter
26 | assert "--strip-whitespaces" in out
27 | assert "Activate or deactivate strip-whitespaces" in out
28 | # Optional formatter
29 | assert "--split-summary-body" in out
30 | assert "Activate or deactivate split-summary-body" in out
31 | assert not err
32 |
33 |
34 | def test_formatter_help_categories(capsys: pytest.CaptureFixture[str]) -> None:
35 | """Test that formatter messages are in the correct group."""
36 |
37 | class OptionalFormatter(StringFormatter): # pylint: disable=abstract-method
38 | """An optional formatter."""
39 |
40 | name = "optional-formatter"
41 | optional = True
42 |
43 | class NonOptionalFormatter(StringFormatter): # pylint: disable=abstract-method
44 | """A non-optional formatter."""
45 |
46 | name = "non-optional-formatter"
47 |
48 | FORMATTERS.append(OptionalFormatter()) # type: ignore[abstract]
49 | FORMATTERS.append(NonOptionalFormatter()) # type: ignore[abstract]
50 | sys.argv = ["pydocstringformatter"]
51 | pydocstringformatter.run_docstring_formatter()
52 | out, _ = capsys.readouterr()
53 | categories = out.replace("\n\n ", "\n").split("\n\n")
54 | for category in categories:
55 | if category.startswith("optional formatters"):
56 | assert "--optional-formatter" in category
57 | assert "--non-optional-formatter" not in category
58 | elif category.startswith("default formatters"):
59 | assert "--optional-formatter" not in category
60 | assert "--non-optional-formatter" in category
61 | FORMATTERS.pop()
62 | FORMATTERS.pop()
63 |
64 |
65 | def test_sys_agv_as_arguments(
66 | capsys: pytest.CaptureFixture[str], test_file: str
67 | ) -> None:
68 | """Test running with arguments in sys.argv."""
69 | sys.argv = ["pydocstringformatter", test_file]
70 | pydocstringformatter.run_docstring_formatter()
71 |
72 | with open(test_file, encoding="utf-8") as file:
73 | assert "".join(file.readlines()) == '"""A multi-line\ndocstring."""'
74 |
75 | output = capsys.readouterr()
76 | assert output.out.endswith(
77 | '''
78 | @@ -1,2 +1,4 @@
79 | -"""A multi-line
80 | -docstring."""
81 | +"""A multi-line.
82 | +
83 | +docstring.
84 | +"""
85 | '''
86 | )
87 | assert not output.err
88 |
89 |
90 | def test_output_message_nothing_done(
91 | capsys: pytest.CaptureFixture[str], test_file: str
92 | ) -> None:
93 | """Test that we emit the correct message when nothing was done."""
94 | with open(test_file, "w", encoding="utf-8") as file:
95 | file.write('"""A multi-line.\n\ndocstring.\n"""')
96 | with open(test_file.replace(".py", "2.py"), "w", encoding="utf-8") as file:
97 | file.write('"""A multi-line.\n\ndocstring.\n"""')
98 |
99 | pydocstringformatter.run_docstring_formatter(
100 | [str(Path(test_file).parent), "--write"]
101 | )
102 |
103 | output = capsys.readouterr()
104 | assert output.out == "Nothing to do! All docstrings in 2 files are correct 🎉\n"
105 | assert not output.err
106 |
107 |
108 | def test_output_message_one_file(
109 | capsys: pytest.CaptureFixture[str], test_file: str
110 | ) -> None:
111 | """Test that we emit the correct message when one out of two files was formatted."""
112 | try:
113 | expected_path = os.path.relpath(test_file)
114 | except ValueError:
115 | expected_path = test_file
116 |
117 | with open(test_file.replace(".py", "2.py"), "w", encoding="utf-8") as file:
118 | file.write('"""A multi-line.\n\ndocstring.\n"""')
119 |
120 | pydocstringformatter.run_docstring_formatter(
121 | [str(Path(test_file).parent), "--write"]
122 | )
123 |
124 | output = capsys.readouterr()
125 | assert output.out == f"Formatted {expected_path} 📖\n"
126 | assert not output.err
127 |
128 |
129 | def test_output_message_two_files(
130 | capsys: pytest.CaptureFixture[str], test_file: str
131 | ) -> None:
132 | """Test that we emit the correct message when two files were formatted."""
133 | second_file = test_file.replace(".py", "2.py")
134 |
135 | try:
136 | expected_path = os.path.relpath(test_file)
137 | expected_second_path = os.path.relpath(second_file)
138 | except ValueError:
139 | expected_path = test_file
140 | expected_second_path = second_file
141 |
142 | with open(second_file, "w", encoding="utf-8") as file:
143 | file.write('"""A multi-line\ndocstring"""')
144 |
145 | pydocstringformatter.run_docstring_formatter(
146 | [str(Path(test_file).parent), "--write"]
147 | )
148 |
149 | output = capsys.readouterr()
150 | assert (
151 | output.out
152 | == f"""Formatted {expected_path} 📖
153 | Formatted {expected_second_path} 📖
154 | """
155 | )
156 | assert not output.err
157 |
158 |
159 | def test_begin_quote_formatters(
160 | capsys: pytest.CaptureFixture[str], tmp_path: Path
161 | ) -> None:
162 | """Test that (optional) formatters are activated or not depending on options."""
163 | with FormatterAsserter(
164 | f'"""{"a" * 120}\n{"b" * 120}"""', FORMATTERS, capsys, tmp_path
165 | ) as asserter:
166 | asserter.assert_format_when_activated()
167 | asserter.assert_no_change_when_deactivated()
168 |
169 |
170 | def test_optional_formatters_argument(
171 | capsys: pytest.CaptureFixture[str], tmp_path: Path
172 | ) -> None:
173 | """Test that an optional formatter is correctly turned on and off with arguments."""
174 | with FormatterAsserter(
175 | '"""Summary. Body."""', [SplitSummaryAndDocstringFormatter()], capsys, tmp_path
176 | ) as asserter:
177 | asserter.assert_format_when_activated()
178 | asserter.assert_no_change_when_deactivated()
179 |
180 |
181 | class TestExitCodes:
182 | """Tests for the --exit-code option."""
183 |
184 | @staticmethod
185 | def test_exit_code_with_write(test_file: str) -> None:
186 | """Test that we emit the correct exit code in write mode."""
187 | with pytest.raises(SystemExit) as exit_exec:
188 | pydocstringformatter.run_docstring_formatter(
189 | [str(Path(test_file)), "--write", "--exit-code"]
190 | )
191 |
192 | assert exit_exec.value.code == 32
193 |
194 | # After first writing changes, now we expect no changes
195 | with pytest.raises(SystemExit) as exit_exec:
196 | pydocstringformatter.run_docstring_formatter(
197 | [str(Path(test_file)), "--write", "--exit-code"]
198 | )
199 |
200 | assert not exit_exec.value.code
201 |
202 | @staticmethod
203 | def test_exit_code_without_write(test_file: str) -> None:
204 | """Test that we emit the correct exit code in write mode."""
205 | with pytest.raises(SystemExit) as exit_exec:
206 | pydocstringformatter.run_docstring_formatter(
207 | [str(Path(test_file)), "--exit-code"]
208 | )
209 |
210 | assert exit_exec.value.code == 32
211 |
212 | # We expect an exit code on both occassions
213 | with pytest.raises(SystemExit) as exit_exec:
214 | pydocstringformatter.run_docstring_formatter(
215 | [str(Path(test_file)), "--exit-code"]
216 | )
217 |
218 | assert exit_exec.value.code == 32
219 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import sys
4 | import tokenize
5 | from pathlib import Path
6 |
7 | import pytest
8 |
9 | import pydocstringformatter
10 | from pydocstringformatter._testutils import MakeAFormatter, MakeBFormatter
11 | from pydocstringformatter._utils import (
12 | compare_formatters,
13 | find_python_files,
14 | is_docstring,
15 | )
16 |
17 | HERE = Path(__file__)
18 | UTILS_DATA = HERE.parent / "data" / "utils"
19 |
20 |
21 | class TestPythonFileFinder:
22 | """Test the python file finder."""
23 |
24 | @staticmethod
25 | def test_underscores_files() -> None:
26 | """Test that we can find files with leading underscores."""
27 | pathnames = find_python_files([str(UTILS_DATA / "find_underscore_files")], [])
28 | expected_paths = [
29 | UTILS_DATA / "find_underscore_files" / "file_one.py",
30 | UTILS_DATA / "find_underscore_files" / "_file_two.py",
31 | UTILS_DATA / "find_underscore_files" / "__file_three.py",
32 | UTILS_DATA / "find_underscore_files" / "____file_five.py",
33 | ]
34 | assert sorted(expected_paths) == pathnames
35 |
36 | @staticmethod
37 | def test_recursive_files() -> None:
38 | """Test that we can find files recursively."""
39 | pathnames = find_python_files(
40 | [str(UTILS_DATA / "find_recursive_files")], [], recursive=True
41 | )
42 | expected_paths = [
43 | UTILS_DATA / "find_recursive_files" / "file_one.py",
44 | UTILS_DATA
45 | / "find_recursive_files"
46 | / "inner_directory"
47 | / "inner_file_one.py",
48 | UTILS_DATA
49 | / "find_recursive_files"
50 | / "inner_directory"
51 | / "inner_inner_directory"
52 | / "inner_inner_file_one.py",
53 | ]
54 | assert sorted(expected_paths) == pathnames
55 |
56 | @staticmethod
57 | def test_recursive_files_standard() -> None:
58 | """Test that we can find files recursively even if argument is not supplied."""
59 | pathnames = find_python_files([str(UTILS_DATA / "find_recursive_files")], [])
60 | expected_paths = [
61 | UTILS_DATA / "find_recursive_files" / "file_one.py",
62 | UTILS_DATA
63 | / "find_recursive_files"
64 | / "inner_directory"
65 | / "inner_file_one.py",
66 | UTILS_DATA
67 | / "find_recursive_files"
68 | / "inner_directory"
69 | / "inner_inner_directory"
70 | / "inner_inner_file_one.py",
71 | ]
72 | assert sorted(expected_paths) == pathnames
73 |
74 | @staticmethod
75 | def test_ignore_recursive_files() -> None:
76 | """Test that we ignore inner directories if recusrive is False."""
77 | pathnames = find_python_files(
78 | [str(UTILS_DATA / "find_recursive_files")], [], recursive=False
79 | )
80 | expected_paths = [UTILS_DATA / "find_recursive_files" / "file_one.py"]
81 | assert sorted(expected_paths) == pathnames
82 |
83 | @staticmethod
84 | def test_ignore_non_python_file() -> None:
85 | """Test that we ignore a non Python file."""
86 | pathnames = find_python_files(
87 | [str(UTILS_DATA / "find_nothing" / "README.md")], []
88 | )
89 | assert not pathnames
90 |
91 |
92 | class TestDocstringFinder:
93 | """Test the docstring finder."""
94 |
95 | docstring_data = UTILS_DATA / "find_docstrings"
96 |
97 | def test_function_docstrings(self) -> None:
98 | """Test that we can find docstrings for function definitions."""
99 | docstrings: list[tuple[tuple[int, int], tuple[int, int]]] = []
100 | with open(
101 | self.docstring_data / "function_docstrings.py", encoding="utf-8"
102 | ) as file:
103 | tokens = list(tokenize.generate_tokens(file.readline))
104 | for index, tokeninfo in enumerate(tokens):
105 | if is_docstring(tokeninfo, tokens[index - 1]):
106 | docstrings.append((tokeninfo.start, tokeninfo.end))
107 |
108 | assert docstrings == [
109 | ((2, 4), (2, 21)),
110 | ((5, 8), (5, 25)),
111 | ((9, 4), (10, 16)),
112 | ((13, 8), (14, 20)),
113 | ((18, 4), (20, 7)),
114 | ((23, 8), (25, 11)),
115 | ((29, 4), (29, 21)),
116 | ((32, 8), (32, 25)),
117 | ((36, 4), (37, 16)),
118 | ((40, 8), (41, 20)),
119 | ((45, 4), (47, 7)),
120 | ((50, 8), (52, 11)),
121 | ]
122 |
123 | def test_dictionary_key_value_line(self) -> None:
124 | """Test that string key-value pairs are not considered a docstring."""
125 | with open(self.docstring_data / "dictionary.py", encoding="utf-8") as file:
126 | tokens = list(tokenize.generate_tokens(file.readline))
127 | for index, tokeninfo in enumerate(tokens):
128 | assert not is_docstring(tokeninfo, tokens[index - 1])
129 |
130 | def test_module_docstrings(self) -> None:
131 | """Test that we find the correct module docstring."""
132 | docstrings: list[tuple[tuple[int, int], tuple[int, int]]] = []
133 | with open(
134 | self.docstring_data / "module_docstrings.py", encoding="utf-8"
135 | ) as file:
136 | tokens = list(tokenize.generate_tokens(file.readline))
137 | for index, tokeninfo in enumerate(tokens):
138 | if is_docstring(tokeninfo, tokens[index - 1]):
139 | docstrings.append((tokeninfo.start, tokeninfo.end))
140 |
141 | assert docstrings == [((3, 0), (4, 3))]
142 |
143 |
144 | def test_encoding_of_console_messages(
145 | capsys: pytest.CaptureFixture[str], test_file: str
146 | ) -> None:
147 | """Test that we can print emoji's to non utf-8 consoles.
148 |
149 | Regression test for:
150 | https://github.com/DanielNoord/pydocstringformatter/issues/13
151 | """
152 | sys.stdout.reconfigure(encoding="cp1252") # type: ignore[union-attr]
153 | with open(test_file, "w", encoding="utf-8") as file:
154 | file.write('"""A multi-line.\n\ndocstring.\n"""')
155 |
156 | pydocstringformatter.run_docstring_formatter([test_file, "--write"])
157 |
158 | output = capsys.readouterr()
159 | assert output.out == "Nothing to do! All docstrings in 1 file are correct 🎉\n"
160 | assert not output.err
161 |
162 |
163 | def test_formatter_comparer() -> None:
164 | """Test the compare_formatters utility function."""
165 | tokeninfo = tokenize.TokenInfo(
166 | 1, '"""AAA AA AAA"""', (1, 0), (1, 0), '"""AAA AA AAA"""'
167 | )
168 |
169 | diff = compare_formatters(tokeninfo, MakeAFormatter(), MakeBFormatter(), "test")
170 |
171 | expected_sections = [
172 | "--- make-a-formatter vs make-b-formatter test\n",
173 | '-"""AAA AA AAA"""\n',
174 | '+"""BBB BB BBB"""\n',
175 | ]
176 |
177 | for section in expected_sections:
178 | assert section in diff
179 |
180 | diff = compare_formatters(tokeninfo, MakeAFormatter(), MakeBFormatter())
181 |
182 | expected_sections = [
183 | "--- make-a-formatter vs make-b-formatter\n",
184 | '-"""AAA AA AAA"""\n',
185 | '+"""BBB BB BBB"""\n',
186 | ]
187 |
188 | for section in expected_sections:
189 | assert section in diff
190 |
--------------------------------------------------------------------------------