Skip to content

Commit cf0cf4c

Browse files
authored
feat(cli): add categories enable/disable flags (jxmorris12#202)
1 parent 9094935 commit cf0cf4c

4 files changed

Lines changed: 127 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
77
## [Unreleased]
88

99
### Added
10+
- 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`.
1011
- Added `premium_key` property to `language_tool_python.server.LanguageTool` to attach a premium API key for LanguageTool API requests.
1112
- Added `premium_username` property to `language_tool_python.server.LanguageTool` to attach a username for premium LanguageTool API requests.
1213
- 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`).

docs/source/references/cli.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ Options
3636
- Comma-separated list of rule IDs to disable.
3737
* - ``-e, --enable RULES``
3838
- Comma-separated list of rule IDs to enable.
39+
* - ``-D, --disable-categories CATEGORIES``
40+
- Comma-separated list of category IDs to disable (e.g. ``TYPOS``, ``GRAMMAR``).
41+
* - ``-E, --enable-categories CATEGORIES``
42+
- Comma-separated list of category IDs to enable.
3943
* - ``--enabled-only``
40-
- Run only the rules listed with ``--enable``, ignoring all others.
44+
- Run only the rules listed with ``--enable`` and the categories listed with
45+
``--enable-categories``, ignoring all others.
4146
* - ``-p, --picky``
4247
- Enable stricter (picky) checking mode.
4348
* - ``-a, --apply``
@@ -91,6 +96,12 @@ Examples
9196
# Disable specific rules
9297
language_tool_python -l en-US -d RULE_ID1,RULE_ID2 input.txt
9398
99+
# Disable an entire category
100+
language_tool_python -l en-US -D TYPOS input.txt
101+
102+
# Disable multiple categories
103+
language_tool_python -l en-US -D TYPOS,GRAMMAR input.txt
104+
94105
# Run only one specific rule
95106
language_tool_python -l en-US --enabled-only -e MORFOLOGIK_RULE_EN_US input.txt
96107

src/language_tool_python/__main__.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class CliArgs(argparse.Namespace):
8585
mother_tongue: str | None
8686
disable: set[str]
8787
enable: set[str]
88+
disable_categories: set[str]
89+
enable_categories: set[str]
8890
enabled_only: bool
8991
picky: bool
9092
apply: bool
@@ -137,10 +139,29 @@ def parse_args(argv: Sequence[str] | None = None) -> CliArgs:
137139
default=set[str](),
138140
help="list of rule IDs to be enabled",
139141
)
142+
parser.add_argument(
143+
"-D",
144+
"--disable-categories",
145+
metavar="CATEGORIES",
146+
type=get_rules,
147+
action=RulesAction,
148+
default=set[str](),
149+
help="list of category IDs to be disabled",
150+
)
151+
parser.add_argument(
152+
"-E",
153+
"--enable-categories",
154+
metavar="CATEGORIES",
155+
type=get_rules,
156+
action=RulesAction,
157+
default=set[str](),
158+
help="list of category IDs to be enabled",
159+
)
140160
parser.add_argument(
141161
"--enabled-only",
142162
action="store_true",
143-
help="disable all rules except those specified in --enable",
163+
help="disable all rules and categories except those specified in "
164+
"--enable or --enable-categories",
144165
)
145166
parser.add_argument(
146167
"-p",
@@ -185,8 +206,11 @@ def parse_args(argv: Sequence[str] | None = None) -> CliArgs:
185206
if args.disable:
186207
parser.error("--enabled-only cannot be used with --disable")
187208

188-
if not args.enable:
189-
parser.error("--enabled-only requires --enable")
209+
if args.disable_categories:
210+
parser.error("--enabled-only cannot be used with --disable-categories")
211+
212+
if not args.enable and not args.enable_categories:
213+
parser.error("--enabled-only requires --enable or --enable-categories")
190214

191215
return args
192216

@@ -230,6 +254,10 @@ def __call__(
230254
cli_args.disable.update(rule_values)
231255
elif self.dest == "enable":
232256
cli_args.enable.update(rule_values)
257+
elif self.dest == "disable_categories":
258+
cli_args.disable_categories.update(rule_values)
259+
elif self.dest == "enable_categories":
260+
cli_args.enable_categories.update(rule_values)
233261
else:
234262
err = f"unexpected rules destination: {self.dest}"
235263
raise ValueError(err)
@@ -362,6 +390,8 @@ def process_file(
362390

363391
lang_tool.disabled_rules.update(args.disable)
364392
lang_tool.enabled_rules.update(args.enable)
393+
lang_tool.disabled_categories.update(args.disable_categories)
394+
lang_tool.enabled_categories.update(args.enable_categories)
365395
lang_tool.enabled_rules_only = args.enabled_only
366396

367397
if args.picky:

tests/test_cli.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
import language_tool_python
10-
from language_tool_python.__main__ import main
10+
from language_tool_python.__main__ import main, parse_args
1111

1212

1313
@pytest.mark.parametrize(
@@ -38,6 +38,31 @@
3838
True,
3939
),
4040
(["-l", "en-US", "--ignore-lines=^#", "-"], '# These are "dumb".\n', True),
41+
(
42+
["-l", "en-US", "-D", "TYPOS", "-"],
43+
"This is noot okay.\n",
44+
True,
45+
),
46+
(
47+
["-l", "en-US", "--disable-categories=TYPOS", "-"],
48+
"This is noot okay.\n",
49+
True,
50+
),
51+
(
52+
["-l", "en-US", "-E", "TYPOS", "-"],
53+
"This is okay.\n",
54+
True,
55+
),
56+
(
57+
["-l", "en-US", "--enabled-only", "-E", "TYPOS", "-"],
58+
"This is noot okay.\n",
59+
False,
60+
),
61+
(
62+
["-l", "en-US", "--enabled-only", "-E", "TYPOS", "-"],
63+
"This is okay.\n",
64+
True,
65+
),
4166
],
4267
)
4368
def test_cli_exit_codes(
@@ -132,6 +157,61 @@ def test_cli_remote_error(remote_server: tuple[str, int]) -> None:
132157
assert code != 0
133158

134159

160+
def test_parse_args_enabled_only_with_enable_categories() -> None:
161+
"""Test that --enabled-only is accepted when only --enable-categories is provided.
162+
163+
:raises AssertionError: If parse_args raises an error for this valid combination.
164+
"""
165+
args = parse_args(["-l", "en-US", "--enabled-only", "-E", "TYPOS", "file.txt"])
166+
assert args.enabled_only is True
167+
assert args.enable_categories == {"TYPOS"}
168+
169+
170+
def test_parse_args_enabled_only_rejects_disable_categories() -> None:
171+
"""Test that --enabled-only cannot be combined with --disable-categories.
172+
173+
:raises SystemExit: Expected, as argparse calls sys.exit on error.
174+
"""
175+
with pytest.raises(SystemExit):
176+
parse_args(
177+
["-l", "en-US", "--enabled-only", "-e", "RULE", "-D", "TYPOS", "file.txt"]
178+
)
179+
180+
181+
def test_parse_args_enabled_only_requires_enable_or_enable_categories() -> None:
182+
"""Test that --enabled-only requires at least --enable or --enable-categories.
183+
184+
:raises SystemExit: Expected, as argparse calls sys.exit on error.
185+
"""
186+
with pytest.raises(SystemExit):
187+
parse_args(["-l", "en-US", "--enabled-only", "file.txt"])
188+
189+
190+
def test_parse_args_categories() -> None:
191+
"""Test that --disable-categories and --enable-categories are parsed correctly.
192+
193+
:raises AssertionError: If the parsed category sets do not match the expected
194+
values.
195+
"""
196+
args = parse_args(
197+
["-l", "en-US", "-D", "TYPOS,GRAMMAR", "-E", "PUNCTUATION", "file.txt"]
198+
)
199+
assert args.disable_categories == {"TYPOS", "GRAMMAR"}
200+
assert args.enable_categories == {"PUNCTUATION"}
201+
202+
203+
def test_parse_args_categories_multiple_flags() -> None:
204+
"""Test that repeated -D/-E flags accumulate into the same set.
205+
206+
:raises AssertionError: If the category sets do not accumulate correctly.
207+
"""
208+
args = parse_args(
209+
["-l", "en-US", "-D", "TYPOS", "-D", "GRAMMAR", "-E", "PUNCTUATION", "file.txt"]
210+
)
211+
assert args.disable_categories == {"TYPOS", "GRAMMAR"}
212+
assert args.enable_categories == {"PUNCTUATION"}
213+
214+
135215
def main_with_stdin(argv: list[str], stdin: str) -> int:
136216
"""Execute the main CLI with simulated stdin input.
137217

0 commit comments

Comments
 (0)