Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ Use `pre-commit install` to install the pre-commit hook for the repository.

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

### Testing

Expand Down
10 changes: 6 additions & 4 deletions pydocstringformatter/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ class _Run:

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

if argv := argv or sys.argv[1:]:
utils._parse_toml_file(self.arg_parser, self.config)
utils._parse_command_line_arguments(self.arg_parser, argv, self.config)

utils._parse_options(
self.arg_parser, self.config, argv, formatting.FORMATTERS
)
self._check_files(self.config.files)
else:
self.arg_parser.print_help()
Expand Down Expand Up @@ -49,7 +50,8 @@ def _format_file(self, filename: Path) -> bool:

if utils._is_docstring(new_tokeninfo, tokens[index - 1]):
for formatter in formatting.FORMATTERS:
new_tokeninfo = formatter.treat_token(new_tokeninfo)
if getattr(self.config, formatter.name):
new_tokeninfo = formatter.treat_token(new_tokeninfo)
changed_tokens.append(new_tokeninfo)

if tokeninfo != new_tokeninfo:
Expand Down
8 changes: 4 additions & 4 deletions pydocstringformatter/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pydocstringformatter.utils.argument_parsing import (
_parse_command_line_arguments,
_parse_toml_file,
_parse_options,
_register_arguments,
_register_arguments_formatters,
)
from pydocstringformatter.utils.exceptions import (
ParsingError,
Expand All @@ -17,11 +17,11 @@
"_find_python_files",
"_generate_diff",
"_is_docstring",
"_parse_command_line_arguments",
"_parse_toml_file",
"ParsingError",
"PydocstringFormatterError",
"_register_arguments",
"_register_arguments_formatters",
"TomlParsingError",
"_parse_options",
"_print_to_console",
]
53 changes: 53 additions & 0 deletions pydocstringformatter/utils/argument_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import tomli

from pydocstringformatter.formatting.base import Formatter
from pydocstringformatter.utils.exceptions import TomlParsingError, UnrecognizedOption

OPTIONS_TYPES = {"write": "store_true"}
Expand Down Expand Up @@ -36,7 +37,25 @@ def _register_arguments(version: str) -> argparse.ArgumentParser:
version=version,
help="Show version number and exit",
)
return parser


def _register_arguments_formatters(
parser: argparse.ArgumentParser, formatters: List[Formatter]
) -> argparse.ArgumentParser:
"""Register a list of formatters, so they can all be deactivated or activated."""
for formatter in formatters:
name = formatter.name
help_text = f"ctivate the {name} formatter"
parser.add_argument(
f"--{name}",
action="store_true",
dest=name,
help=f"A{help_text} : {formatter.__doc__}",
)
parser.add_argument(
f"--no-{name}", action="store_false", dest=name, help=f"Dea{help_text}"
)
return parser


Expand Down Expand Up @@ -80,3 +99,37 @@ def _parse_toml_file(
arguments += _parse_toml_option(key, value)

parser.parse_args(arguments, namespace)


def _load_formatters_default_option(
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
formatters: List[Formatter],
) -> None:
"""Parse the state of the list of formatters based on their 'optional' attribute."""
arguments: List[str] = []
for formatter in formatters:
if formatter.optional:
arguments.append(f"--no-{formatter.name}")
elif not formatter.optional:
arguments.append(f"--{formatter.name}")

parser.parse_known_args(arguments, namespace)


def _parse_options(
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
argv: List[str],
formatters: List[Formatter],
) -> None:
"""Load all default option values.

The order of parsing is:
1. default values, 2. configuration files, 3. command line arguments.
"""
_load_formatters_default_option(parser, namespace, formatters)

_parse_toml_file(parser, namespace)

_parse_command_line_arguments(parser, argv, namespace)
45 changes: 41 additions & 4 deletions tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
import os
import sys
from pathlib import Path
from typing import List

import pytest

import pydocstringformatter
from pydocstringformatter.formatting import FORMATTERS


def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None:
"""Test that we warn when no arguments are provided"""
"""Test that we display a help message when no arguments are provided."""
sys.argv = ["pydocstringformatter"]
pydocstringformatter.run_docstring_formatter()
output = capsys.readouterr()
assert output.out.startswith("usage: pydocstringformatter [-h]")
assert not output.err
out, err = capsys.readouterr()
assert out.startswith("usage: pydocstringformatter [-h]")

# Test that we print help messages for individual formatters as well
assert "--beginning-quotes" in out
assert "Activate the beginning-quotes formatter" in out
assert "--no-beginning-quotes" in out
assert "Deactivate the beginning-quotes formatter" in out
assert not err


def test_sys_agv_as_arguments(
Expand Down Expand Up @@ -106,3 +114,32 @@ def test_output_message_two_files(
"""
)
assert not output.err


@pytest.mark.parametrize(
"args,should_format",
[
[[f"--no-{f.name}" for f in FORMATTERS], False],
[[f"--{f.name}" for f in FORMATTERS], True],
],
)
def test_optional_formatters(
args: List[str],
should_format: bool,
capsys: pytest.CaptureFixture[str],
tmp_path: Path,
) -> None:
"""Test that (optional) formatters are activated or not depending on options."""
bad_docstring = tmp_path / "bad_docstring.py"
bad_docstring.write_text(f'"""{"a" * 120}\n{"b" * 120}"""')
pydocstringformatter.run_docstring_formatter([str(bad_docstring)] + args)
out, err = capsys.readouterr()
assert not err
if should_format:
msg = "Nothing was modified, but all formatters are activated."
assert "Nothing to do!" not in out
expected = ["---", "@@", "+++"]
assert all(e in out for e in expected), msg
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite a neat way to test changes occurred! Might use this myself for future tests as well!

else:
msg = "Something was modified, but all formatter are deactivated."
assert "Nothing to do!" in out, msg