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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
## [Unreleased]

### Added
- Added `-D`/`--disable-categories` and `-E`/`--enable-categories` CLI options to disable or enable LanguageTool rule categories (e.g. `TYPOS`, `GRAMMAR`). `--enabled-only` now also applies to categories specified via `--enable-categories`.
- Added `premium_key` property to `language_tool_python.server.LanguageTool` to attach a premium API key for LanguageTool API requests.
- Added `premium_username` property to `language_tool_python.server.LanguageTool` to attach a username for premium LanguageTool API requests.
- Added `language_tool_python.match.is_check_match` type guard function to verify that a value is a `CheckMatch` (type from `language_tool_python._internals`).
Expand Down
13 changes: 12 additions & 1 deletion docs/source/references/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,13 @@ Options
- Comma-separated list of rule IDs to disable.
* - ``-e, --enable RULES``
- Comma-separated list of rule IDs to enable.
* - ``-D, --disable-categories CATEGORIES``
- Comma-separated list of category IDs to disable (e.g. ``TYPOS``, ``GRAMMAR``).
* - ``-E, --enable-categories CATEGORIES``
- Comma-separated list of category IDs to enable.
* - ``--enabled-only``
- Run only the rules listed with ``--enable``, ignoring all others.
- Run only the rules listed with ``--enable`` and the categories listed with
``--enable-categories``, ignoring all others.
* - ``-p, --picky``
- Enable stricter (picky) checking mode.
* - ``-a, --apply``
Expand Down Expand Up @@ -91,6 +96,12 @@ Examples
# Disable specific rules
language_tool_python -l en-US -d RULE_ID1,RULE_ID2 input.txt

# Disable an entire category
language_tool_python -l en-US -D TYPOS input.txt

# Disable multiple categories
language_tool_python -l en-US -D TYPOS,GRAMMAR input.txt

# Run only one specific rule
language_tool_python -l en-US --enabled-only -e MORFOLOGIK_RULE_EN_US input.txt

Expand Down
36 changes: 33 additions & 3 deletions src/language_tool_python/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class CliArgs(argparse.Namespace):
mother_tongue: str | None
disable: set[str]
enable: set[str]
disable_categories: set[str]
enable_categories: set[str]
enabled_only: bool
picky: bool
apply: bool
Expand Down Expand Up @@ -137,10 +139,29 @@ def parse_args(argv: Sequence[str] | None = None) -> CliArgs:
default=set[str](),
help="list of rule IDs to be enabled",
)
parser.add_argument(
"-D",
"--disable-categories",
metavar="CATEGORIES",
type=get_rules,
action=RulesAction,
default=set[str](),
help="list of category IDs to be disabled",
)
parser.add_argument(
"-E",
"--enable-categories",
metavar="CATEGORIES",
type=get_rules,
action=RulesAction,
default=set[str](),
help="list of category IDs to be enabled",
)
parser.add_argument(
"--enabled-only",
action="store_true",
help="disable all rules except those specified in --enable",
help="disable all rules and categories except those specified in "
"--enable or --enable-categories",
)
parser.add_argument(
"-p",
Expand Down Expand Up @@ -185,8 +206,11 @@ def parse_args(argv: Sequence[str] | None = None) -> CliArgs:
if args.disable:
parser.error("--enabled-only cannot be used with --disable")

if not args.enable:
parser.error("--enabled-only requires --enable")
if args.disable_categories:
parser.error("--enabled-only cannot be used with --disable-categories")

if not args.enable and not args.enable_categories:
parser.error("--enabled-only requires --enable or --enable-categories")

return args

Expand Down Expand Up @@ -230,6 +254,10 @@ def __call__(
cli_args.disable.update(rule_values)
elif self.dest == "enable":
cli_args.enable.update(rule_values)
elif self.dest == "disable_categories":
cli_args.disable_categories.update(rule_values)
elif self.dest == "enable_categories":
cli_args.enable_categories.update(rule_values)
else:
err = f"unexpected rules destination: {self.dest}"
raise ValueError(err)
Expand Down Expand Up @@ -362,6 +390,8 @@ def process_file(

lang_tool.disabled_rules.update(args.disable)
lang_tool.enabled_rules.update(args.enable)
lang_tool.disabled_categories.update(args.disable_categories)
lang_tool.enabled_categories.update(args.enable_categories)
lang_tool.enabled_rules_only = args.enabled_only

if args.picky:
Expand Down
82 changes: 81 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

import language_tool_python
from language_tool_python.__main__ import main
from language_tool_python.__main__ import main, parse_args


@pytest.mark.parametrize(
Expand Down Expand Up @@ -38,6 +38,31 @@
True,
),
(["-l", "en-US", "--ignore-lines=^#", "-"], '# These are "dumb".\n', True),
(
["-l", "en-US", "-D", "TYPOS", "-"],
"This is noot okay.\n",
True,
),
(
["-l", "en-US", "--disable-categories=TYPOS", "-"],
"This is noot okay.\n",
True,
),
(
["-l", "en-US", "-E", "TYPOS", "-"],
"This is okay.\n",
True,
),
(
["-l", "en-US", "--enabled-only", "-E", "TYPOS", "-"],
"This is noot okay.\n",
False,
),
(
["-l", "en-US", "--enabled-only", "-E", "TYPOS", "-"],
"This is okay.\n",
True,
),
],
)
def test_cli_exit_codes(
Expand Down Expand Up @@ -132,6 +157,61 @@ def test_cli_remote_error(remote_server: tuple[str, int]) -> None:
assert code != 0


def test_parse_args_enabled_only_with_enable_categories() -> None:
"""Test that --enabled-only is accepted when only --enable-categories is provided.

:raises AssertionError: If parse_args raises an error for this valid combination.
"""
args = parse_args(["-l", "en-US", "--enabled-only", "-E", "TYPOS", "file.txt"])
assert args.enabled_only is True
assert args.enable_categories == {"TYPOS"}


def test_parse_args_enabled_only_rejects_disable_categories() -> None:
"""Test that --enabled-only cannot be combined with --disable-categories.

:raises SystemExit: Expected, as argparse calls sys.exit on error.
"""
with pytest.raises(SystemExit):
parse_args(
["-l", "en-US", "--enabled-only", "-e", "RULE", "-D", "TYPOS", "file.txt"]
)


def test_parse_args_enabled_only_requires_enable_or_enable_categories() -> None:
"""Test that --enabled-only requires at least --enable or --enable-categories.

:raises SystemExit: Expected, as argparse calls sys.exit on error.
"""
with pytest.raises(SystemExit):
parse_args(["-l", "en-US", "--enabled-only", "file.txt"])


def test_parse_args_categories() -> None:
"""Test that --disable-categories and --enable-categories are parsed correctly.

:raises AssertionError: If the parsed category sets do not match the expected
values.
"""
args = parse_args(
["-l", "en-US", "-D", "TYPOS,GRAMMAR", "-E", "PUNCTUATION", "file.txt"]
)
assert args.disable_categories == {"TYPOS", "GRAMMAR"}
assert args.enable_categories == {"PUNCTUATION"}


def test_parse_args_categories_multiple_flags() -> None:
"""Test that repeated -D/-E flags accumulate into the same set.

:raises AssertionError: If the category sets do not accumulate correctly.
"""
args = parse_args(
["-l", "en-US", "-D", "TYPOS", "-D", "GRAMMAR", "-E", "PUNCTUATION", "file.txt"]
)
assert args.disable_categories == {"TYPOS", "GRAMMAR"}
assert args.enable_categories == {"PUNCTUATION"}


def main_with_stdin(argv: list[str], stdin: str) -> int:
"""Execute the main CLI with simulated stdin input.

Expand Down
Loading