Skip to content

Commit 23de2d5

Browse files
[symilar] Migrate from getopt to argparse (#9731)
Co-authored-by: Roger Sheu <[email protected]>
1 parent d048dcc commit 23de2d5

File tree

3 files changed

+76
-76
lines changed

3 files changed

+76
-76
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
We migrated ``symilar`` to argparse, from getopt, so the error and help output changed
2+
(for the better). We exit with 2 instead of sometime 1, sometime 2. The error output
3+
is not captured by the runner anymore. It's not possible to use a value for the
4+
boolean options anymore (``--ignore-comments 1`` should become ``--ignore-comments``).
5+
6+
Refs #9731

pylint/checkers/symilar.py

Lines changed: 54 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import warnings
4040
from collections import defaultdict
4141
from collections.abc import Callable, Generator, Iterable, Sequence
42-
from getopt import GetoptError, getopt
4342
from io import BufferedIOBase, BufferedReader, BytesIO
4443
from itertools import chain
4544
from typing import TYPE_CHECKING, NamedTuple, NewType, NoReturn, TextIO, Union
@@ -750,15 +749,20 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
750749

751750
name = "similarities"
752751
msgs = MSGS
753-
# for available dict keys/values see the optik parser 'add_option' method
752+
MIN_SIMILARITY_HELP = "Minimum lines number of a similarity."
753+
IGNORE_COMMENTS_HELP = "Comments are removed from the similarity computation"
754+
IGNORE_DOCSTRINGS_HELP = "Docstrings are removed from the similarity computation"
755+
IGNORE_IMPORTS_HELP = "Imports are removed from the similarity computation"
756+
IGNORE_SIGNATURES_HELP = "Signatures are removed from the similarity computation"
757+
# for available dict keys/values see the option parser 'add_option' method
754758
options: Options = (
755759
(
756760
"min-similarity-lines",
757761
{
758762
"default": DEFAULT_MIN_SIMILARITY_LINE,
759763
"type": "int",
760764
"metavar": "<int>",
761-
"help": "Minimum lines number of a similarity.",
765+
"help": MIN_SIMILARITY_HELP,
762766
},
763767
),
764768
(
@@ -767,7 +771,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
767771
"default": True,
768772
"type": "yn",
769773
"metavar": "<y or n>",
770-
"help": "Comments are removed from the similarity computation",
774+
"help": IGNORE_COMMENTS_HELP,
771775
},
772776
),
773777
(
@@ -776,7 +780,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
776780
"default": True,
777781
"type": "yn",
778782
"metavar": "<y or n>",
779-
"help": "Docstrings are removed from the similarity computation",
783+
"help": IGNORE_DOCSTRINGS_HELP,
780784
},
781785
),
782786
(
@@ -785,7 +789,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
785789
"default": True,
786790
"type": "yn",
787791
"metavar": "<y or n>",
788-
"help": "Imports are removed from the similarity computation",
792+
"help": IGNORE_IMPORTS_HELP,
789793
},
790794
),
791795
(
@@ -794,7 +798,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
794798
"default": True,
795799
"type": "yn",
796800
"metavar": "<y or n>",
797-
"help": "Signatures are removed from the similarity computation",
801+
"help": IGNORE_SIGNATURES_HELP,
798802
},
799803
),
800804
)
@@ -876,67 +880,53 @@ def register(linter: PyLinter) -> None:
876880
linter.register_checker(SimilaritiesChecker(linter))
877881

878882

879-
def usage(status: int = 0) -> NoReturn:
880-
"""Display command line usage information."""
881-
print("finds copy pasted blocks in a set of files")
882-
print()
883-
print(
884-
"Usage: symilar [-d|--duplicates min_duplicated_lines] \
885-
[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] [--ignore-signatures] file1..."
886-
)
887-
sys.exit(status)
888-
889-
890883
def Run(argv: Sequence[str] | None = None) -> NoReturn:
891884
"""Standalone command line access point."""
892-
if argv is None:
893-
argv = sys.argv[1:]
894-
895-
s_opts = "hd:i:"
896-
l_opts = [
897-
"help",
898-
"duplicates=",
899-
"ignore-comments",
900-
"ignore-imports",
901-
"ignore-docstrings",
902-
"ignore-signatures",
903-
]
904-
min_lines = DEFAULT_MIN_SIMILARITY_LINE
905-
ignore_comments = False
906-
ignore_docstrings = False
907-
ignore_imports = False
908-
ignore_signatures = False
909-
try:
910-
opts, args = getopt(list(argv), s_opts, l_opts)
911-
except GetoptError as e:
912-
print(e)
913-
usage(2)
914-
for opt, val in opts:
915-
if opt in {"-d", "--duplicates"}:
916-
try:
917-
min_lines = int(val)
918-
except ValueError as e:
919-
print(e)
920-
usage(2)
921-
elif opt in {"-h", "--help"}:
922-
usage()
923-
elif opt in {"-i", "--ignore-comments"}:
924-
ignore_comments = True
925-
elif opt in {"--ignore-docstrings"}:
926-
ignore_docstrings = True
927-
elif opt in {"--ignore-imports"}:
928-
ignore_imports = True
929-
elif opt in {"--ignore-signatures"}:
930-
ignore_signatures = True
931-
if not args:
932-
usage(1)
933-
sim = Symilar(
934-
min_lines, ignore_comments, ignore_docstrings, ignore_imports, ignore_signatures
885+
parser = argparse.ArgumentParser(
886+
prog="symilar", description="Finds copy pasted blocks in a set of files."
887+
)
888+
parser.add_argument("files", nargs="+")
889+
parser.add_argument(
890+
"-d",
891+
"--duplicates",
892+
type=int,
893+
default=DEFAULT_MIN_SIMILARITY_LINE,
894+
help=SimilaritiesChecker.MIN_SIMILARITY_HELP,
895+
)
896+
parser.add_argument(
897+
"-i",
898+
"--ignore-comments",
899+
action="store_true",
900+
help=SimilaritiesChecker.IGNORE_COMMENTS_HELP,
901+
)
902+
parser.add_argument(
903+
"--ignore-docstrings",
904+
action="store_true",
905+
help=SimilaritiesChecker.IGNORE_DOCSTRINGS_HELP,
906+
)
907+
parser.add_argument(
908+
"--ignore-imports",
909+
action="store_true",
910+
help=SimilaritiesChecker.IGNORE_IMPORTS_HELP,
911+
)
912+
parser.add_argument(
913+
"--ignore-signatures",
914+
action="store_true",
915+
help=SimilaritiesChecker.IGNORE_SIGNATURES_HELP,
916+
)
917+
parsed_args = parser.parse_args(args=argv)
918+
similar_runner = Symilar(
919+
min_lines=parsed_args.duplicates,
920+
ignore_comments=parsed_args.ignore_comments,
921+
ignore_docstrings=parsed_args.ignore_docstrings,
922+
ignore_imports=parsed_args.ignore_imports,
923+
ignore_signatures=parsed_args.ignore_signatures,
935924
)
936-
for filename in args:
925+
for filename in parsed_args.files:
937926
with open(filename, encoding="utf-8") as stream:
938-
sim.append_stream(filename, stream)
939-
sim.run()
927+
similar_runner.append_stream(filename, stream)
928+
similar_runner.run()
929+
# the sys exit must be kept because of the unit tests that rely on it
940930
sys.exit(0)
941931

942932

tests/checkers/unittest_symilar.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pathlib import Path
88

99
import pytest
10+
from _pytest.capture import CaptureFixture
1011

1112
from pylint.checkers import symilar
1213
from pylint.lint import PyLinter
@@ -364,13 +365,16 @@ def test_help() -> None:
364365
pytest.fail("not system exit")
365366

366367

367-
def test_no_args() -> None:
368+
def test_no_args(capsys: CaptureFixture) -> None:
368369
output = StringIO()
369370
with redirect_stdout(output):
370371
try:
371372
symilar.Run([])
372373
except SystemExit as ex:
373-
assert ex.code == 1
374+
assert ex.code == 2
375+
out, err = capsys.readouterr()
376+
assert not out
377+
assert "the following arguments are required: files" in err
374378
else:
375379
pytest.fail("not system exit")
376380

@@ -494,30 +498,30 @@ def test_set_duplicate_lines_to_zero() -> None:
494498
assert output.getvalue() == ""
495499

496500

497-
@pytest.mark.parametrize("v", ["d"])
498-
def test_bad_equal_short_form_option(v: str) -> None:
501+
def test_equal_short_form_option() -> None:
499502
"""Regression test for https://github.com/pylint-dev/pylint/issues/9343"""
500503
output = StringIO()
501504
with redirect_stdout(output), pytest.raises(SystemExit) as ex:
502-
symilar.Run([f"-{v}=0", SIMILAR1, SIMILAR2])
503-
assert ex.value.code == 2
504-
assert "invalid literal for int() with base 10: '=0'" in output.getvalue()
505+
symilar.Run(["-d=2", SIMILAR1, SIMILAR2])
506+
assert ex.value.code == 0
507+
assert "similar lines in" in output.getvalue()
505508

506509

507-
@pytest.mark.parametrize("v", ["i", "d"])
508-
def test_space_short_form_option(v: str) -> None:
510+
def test_space_short_form_option() -> None:
509511
"""Regression test for https://github.com/pylint-dev/pylint/issues/9343"""
510512
output = StringIO()
511513
with redirect_stdout(output), pytest.raises(SystemExit) as ex:
512-
symilar.Run([f"-{v} 2", SIMILAR1, SIMILAR2])
514+
symilar.Run(["-d 2", SIMILAR1, SIMILAR2])
513515
assert ex.value.code == 0
514516
assert "similar lines in" in output.getvalue()
515517

516518

517-
def test_bad_short_form_option() -> None:
519+
def test_bad_short_form_option(capsys: CaptureFixture) -> None:
518520
"""Regression test for https://github.com/pylint-dev/pylint/issues/9343"""
519521
output = StringIO()
520522
with redirect_stdout(output), pytest.raises(SystemExit) as ex:
521523
symilar.Run(["-j=0", SIMILAR1, SIMILAR2])
524+
out, err = capsys.readouterr()
522525
assert ex.value.code == 2
523-
assert "option -j not recognized" in output.getvalue()
526+
assert not out
527+
assert "unrecognized arguments: -j=0" in err

0 commit comments

Comments
 (0)