Skip to content

Commit 0ded93a

Browse files
Make every formatter optional (#15)
* Add automatically generated options * Refactor to avoid using a default value in argparse See discussion here: #15 (comment) And here #21 Co-authored-by: Daniël van Noord <[email protected]>
1 parent e237d03 commit 0ded93a

File tree

5 files changed

+108
-14
lines changed

5 files changed

+108
-14
lines changed

.github/CONTRIBUTING.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ Use `pre-commit install` to install the pre-commit hook for the repository.
88

99
- Implement a Formatter by inheriting from `pydocstringformatter.formatting.Formatter`
1010
- Add your new formatter to `pydocstringformatter.formatting.FORMATTERS`
11-
- Choose a proper name because this will be user-facing: the name will be used for
12-
options of the CLI.
11+
- Write a clear docstring because this will be user-facing: it's what will be seen in
12+
the help message for the formatter's command line option.
13+
- Choose a proper name because this will be user-facing: the name will be used to turn
14+
the formatter on and off via the command line or config files.
1315

1416
### Testing
1517

pydocstringformatter/run.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ class _Run:
1616

1717
def __init__(self, argv: Union[List[str], None]) -> None:
1818
self.arg_parser = utils._register_arguments(__version__)
19+
utils._register_arguments_formatters(self.arg_parser, formatting.FORMATTERS)
1920
self.config = argparse.Namespace()
2021

2122
if argv := argv or sys.argv[1:]:
22-
utils._parse_toml_file(self.arg_parser, self.config)
23-
utils._parse_command_line_arguments(self.arg_parser, argv, self.config)
24-
23+
utils._parse_options(
24+
self.arg_parser, self.config, argv, formatting.FORMATTERS
25+
)
2526
self._check_files(self.config.files)
2627
else:
2728
self.arg_parser.print_help()
@@ -49,7 +50,8 @@ def _format_file(self, filename: Path) -> bool:
4950

5051
if utils._is_docstring(new_tokeninfo, tokens[index - 1]):
5152
for formatter in formatting.FORMATTERS:
52-
new_tokeninfo = formatter.treat_token(new_tokeninfo)
53+
if getattr(self.config, formatter.name):
54+
new_tokeninfo = formatter.treat_token(new_tokeninfo)
5355
changed_tokens.append(new_tokeninfo)
5456

5557
if tokeninfo != new_tokeninfo:

pydocstringformatter/utils/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pydocstringformatter.utils.argument_parsing import (
2-
_parse_command_line_arguments,
3-
_parse_toml_file,
2+
_parse_options,
43
_register_arguments,
4+
_register_arguments_formatters,
55
)
66
from pydocstringformatter.utils.exceptions import (
77
ParsingError,
@@ -17,11 +17,11 @@
1717
"_find_python_files",
1818
"_generate_diff",
1919
"_is_docstring",
20-
"_parse_command_line_arguments",
21-
"_parse_toml_file",
2220
"ParsingError",
2321
"PydocstringFormatterError",
2422
"_register_arguments",
23+
"_register_arguments_formatters",
2524
"TomlParsingError",
25+
"_parse_options",
2626
"_print_to_console",
2727
]

pydocstringformatter/utils/argument_parsing.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import tomli
66

7+
from pydocstringformatter.formatting.base import Formatter
78
from pydocstringformatter.utils.exceptions import TomlParsingError, UnrecognizedOption
89

910
OPTIONS_TYPES = {"write": "store_true"}
@@ -36,7 +37,25 @@ def _register_arguments(version: str) -> argparse.ArgumentParser:
3637
version=version,
3738
help="Show version number and exit",
3839
)
40+
return parser
41+
3942

43+
def _register_arguments_formatters(
44+
parser: argparse.ArgumentParser, formatters: List[Formatter]
45+
) -> argparse.ArgumentParser:
46+
"""Register a list of formatters, so they can all be deactivated or activated."""
47+
for formatter in formatters:
48+
name = formatter.name
49+
help_text = f"ctivate the {name} formatter"
50+
parser.add_argument(
51+
f"--{name}",
52+
action="store_true",
53+
dest=name,
54+
help=f"A{help_text} : {formatter.__doc__}",
55+
)
56+
parser.add_argument(
57+
f"--no-{name}", action="store_false", dest=name, help=f"Dea{help_text}"
58+
)
4059
return parser
4160

4261

@@ -80,3 +99,37 @@ def _parse_toml_file(
8099
arguments += _parse_toml_option(key, value)
81100

82101
parser.parse_args(arguments, namespace)
102+
103+
104+
def _load_formatters_default_option(
105+
parser: argparse.ArgumentParser,
106+
namespace: argparse.Namespace,
107+
formatters: List[Formatter],
108+
) -> None:
109+
"""Parse the state of the list of formatters based on their 'optional' attribute."""
110+
arguments: List[str] = []
111+
for formatter in formatters:
112+
if formatter.optional:
113+
arguments.append(f"--no-{formatter.name}")
114+
elif not formatter.optional:
115+
arguments.append(f"--{formatter.name}")
116+
117+
parser.parse_known_args(arguments, namespace)
118+
119+
120+
def _parse_options(
121+
parser: argparse.ArgumentParser,
122+
namespace: argparse.Namespace,
123+
argv: List[str],
124+
formatters: List[Formatter],
125+
) -> None:
126+
"""Load all default option values.
127+
128+
The order of parsing is:
129+
1. default values, 2. configuration files, 3. command line arguments.
130+
"""
131+
_load_formatters_default_option(parser, namespace, formatters)
132+
133+
_parse_toml_file(parser, namespace)
134+
135+
_parse_command_line_arguments(parser, argv, namespace)

tests/test_run.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@
22
import os
33
import sys
44
from pathlib import Path
5+
from typing import List
56

67
import pytest
78

89
import pydocstringformatter
10+
from pydocstringformatter.formatting import FORMATTERS
911

1012

1113
def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None:
12-
"""Test that we warn when no arguments are provided"""
14+
"""Test that we display a help message when no arguments are provided."""
1315
sys.argv = ["pydocstringformatter"]
1416
pydocstringformatter.run_docstring_formatter()
15-
output = capsys.readouterr()
16-
assert output.out.startswith("usage: pydocstringformatter [-h]")
17-
assert not output.err
17+
out, err = capsys.readouterr()
18+
assert out.startswith("usage: pydocstringformatter [-h]")
19+
20+
# Test that we print help messages for individual formatters as well
21+
assert "--beginning-quotes" in out
22+
assert "Activate the beginning-quotes formatter" in out
23+
assert "--no-beginning-quotes" in out
24+
assert "Deactivate the beginning-quotes formatter" in out
25+
assert not err
1826

1927

2028
def test_sys_agv_as_arguments(
@@ -106,3 +114,32 @@ def test_output_message_two_files(
106114
"""
107115
)
108116
assert not output.err
117+
118+
119+
@pytest.mark.parametrize(
120+
"args,should_format",
121+
[
122+
[[f"--no-{f.name}" for f in FORMATTERS], False],
123+
[[f"--{f.name}" for f in FORMATTERS], True],
124+
],
125+
)
126+
def test_optional_formatters(
127+
args: List[str],
128+
should_format: bool,
129+
capsys: pytest.CaptureFixture[str],
130+
tmp_path: Path,
131+
) -> None:
132+
"""Test that (optional) formatters are activated or not depending on options."""
133+
bad_docstring = tmp_path / "bad_docstring.py"
134+
bad_docstring.write_text(f'"""{"a" * 120}\n{"b" * 120}"""')
135+
pydocstringformatter.run_docstring_formatter([str(bad_docstring)] + args)
136+
out, err = capsys.readouterr()
137+
assert not err
138+
if should_format:
139+
msg = "Nothing was modified, but all formatters are activated."
140+
assert "Nothing to do!" not in out
141+
expected = ["---", "@@", "+++"]
142+
assert all(e in out for e in expected), msg
143+
else:
144+
msg = "Something was modified, but all formatter are deactivated."
145+
assert "Nothing to do!" in out, msg

0 commit comments

Comments
 (0)