Skip to content

Commit

Permalink
[symilar] Migrate from getopt to argparse (#9731)
Browse files Browse the repository at this point in the history
Co-authored-by: Roger Sheu <[email protected]>
  • Loading branch information
Pierre-Sassoulas and rogersheu authored Aug 25, 2024
1 parent d048dcc commit 23de2d5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 76 deletions.
6 changes: 6 additions & 0 deletions doc/whatsnew/fragments/9731.user_action
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
We migrated ``symilar`` to argparse, from getopt, so the error and help output changed
(for the better). We exit with 2 instead of sometime 1, sometime 2. The error output
is not captured by the runner anymore. It's not possible to use a value for the
boolean options anymore (``--ignore-comments 1`` should become ``--ignore-comments``).

Refs #9731
118 changes: 54 additions & 64 deletions pylint/checkers/symilar.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import warnings
from collections import defaultdict
from collections.abc import Callable, Generator, Iterable, Sequence
from getopt import GetoptError, getopt
from io import BufferedIOBase, BufferedReader, BytesIO
from itertools import chain
from typing import TYPE_CHECKING, NamedTuple, NewType, NoReturn, TextIO, Union
Expand Down Expand Up @@ -750,15 +749,20 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):

name = "similarities"
msgs = MSGS
# for available dict keys/values see the optik parser 'add_option' method
MIN_SIMILARITY_HELP = "Minimum lines number of a similarity."
IGNORE_COMMENTS_HELP = "Comments are removed from the similarity computation"
IGNORE_DOCSTRINGS_HELP = "Docstrings are removed from the similarity computation"
IGNORE_IMPORTS_HELP = "Imports are removed from the similarity computation"
IGNORE_SIGNATURES_HELP = "Signatures are removed from the similarity computation"
# for available dict keys/values see the option parser 'add_option' method
options: Options = (
(
"min-similarity-lines",
{
"default": DEFAULT_MIN_SIMILARITY_LINE,
"type": "int",
"metavar": "<int>",
"help": "Minimum lines number of a similarity.",
"help": MIN_SIMILARITY_HELP,
},
),
(
Expand All @@ -767,7 +771,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
"default": True,
"type": "yn",
"metavar": "<y or n>",
"help": "Comments are removed from the similarity computation",
"help": IGNORE_COMMENTS_HELP,
},
),
(
Expand All @@ -776,7 +780,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
"default": True,
"type": "yn",
"metavar": "<y or n>",
"help": "Docstrings are removed from the similarity computation",
"help": IGNORE_DOCSTRINGS_HELP,
},
),
(
Expand All @@ -785,7 +789,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
"default": True,
"type": "yn",
"metavar": "<y or n>",
"help": "Imports are removed from the similarity computation",
"help": IGNORE_IMPORTS_HELP,
},
),
(
Expand All @@ -794,7 +798,7 @@ class SimilaritiesChecker(BaseRawFileChecker, Symilar):
"default": True,
"type": "yn",
"metavar": "<y or n>",
"help": "Signatures are removed from the similarity computation",
"help": IGNORE_SIGNATURES_HELP,
},
),
)
Expand Down Expand Up @@ -876,67 +880,53 @@ def register(linter: PyLinter) -> None:
linter.register_checker(SimilaritiesChecker(linter))


def usage(status: int = 0) -> NoReturn:
"""Display command line usage information."""
print("finds copy pasted blocks in a set of files")
print()
print(
"Usage: symilar [-d|--duplicates min_duplicated_lines] \
[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] [--ignore-signatures] file1..."
)
sys.exit(status)


def Run(argv: Sequence[str] | None = None) -> NoReturn:
"""Standalone command line access point."""
if argv is None:
argv = sys.argv[1:]

s_opts = "hd:i:"
l_opts = [
"help",
"duplicates=",
"ignore-comments",
"ignore-imports",
"ignore-docstrings",
"ignore-signatures",
]
min_lines = DEFAULT_MIN_SIMILARITY_LINE
ignore_comments = False
ignore_docstrings = False
ignore_imports = False
ignore_signatures = False
try:
opts, args = getopt(list(argv), s_opts, l_opts)
except GetoptError as e:
print(e)
usage(2)
for opt, val in opts:
if opt in {"-d", "--duplicates"}:
try:
min_lines = int(val)
except ValueError as e:
print(e)
usage(2)
elif opt in {"-h", "--help"}:
usage()
elif opt in {"-i", "--ignore-comments"}:
ignore_comments = True
elif opt in {"--ignore-docstrings"}:
ignore_docstrings = True
elif opt in {"--ignore-imports"}:
ignore_imports = True
elif opt in {"--ignore-signatures"}:
ignore_signatures = True
if not args:
usage(1)
sim = Symilar(
min_lines, ignore_comments, ignore_docstrings, ignore_imports, ignore_signatures
parser = argparse.ArgumentParser(
prog="symilar", description="Finds copy pasted blocks in a set of files."
)
parser.add_argument("files", nargs="+")
parser.add_argument(
"-d",
"--duplicates",
type=int,
default=DEFAULT_MIN_SIMILARITY_LINE,
help=SimilaritiesChecker.MIN_SIMILARITY_HELP,
)
parser.add_argument(
"-i",
"--ignore-comments",
action="store_true",
help=SimilaritiesChecker.IGNORE_COMMENTS_HELP,
)
parser.add_argument(
"--ignore-docstrings",
action="store_true",
help=SimilaritiesChecker.IGNORE_DOCSTRINGS_HELP,
)
parser.add_argument(
"--ignore-imports",
action="store_true",
help=SimilaritiesChecker.IGNORE_IMPORTS_HELP,
)
parser.add_argument(
"--ignore-signatures",
action="store_true",
help=SimilaritiesChecker.IGNORE_SIGNATURES_HELP,
)
parsed_args = parser.parse_args(args=argv)
similar_runner = Symilar(
min_lines=parsed_args.duplicates,
ignore_comments=parsed_args.ignore_comments,
ignore_docstrings=parsed_args.ignore_docstrings,
ignore_imports=parsed_args.ignore_imports,
ignore_signatures=parsed_args.ignore_signatures,
)
for filename in args:
for filename in parsed_args.files:
with open(filename, encoding="utf-8") as stream:
sim.append_stream(filename, stream)
sim.run()
similar_runner.append_stream(filename, stream)
similar_runner.run()
# the sys exit must be kept because of the unit tests that rely on it
sys.exit(0)


Expand Down
28 changes: 16 additions & 12 deletions tests/checkers/unittest_symilar.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest
from _pytest.capture import CaptureFixture

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


def test_no_args() -> None:
def test_no_args(capsys: CaptureFixture) -> None:
output = StringIO()
with redirect_stdout(output):
try:
symilar.Run([])
except SystemExit as ex:
assert ex.code == 1
assert ex.code == 2
out, err = capsys.readouterr()
assert not out
assert "the following arguments are required: files" in err
else:
pytest.fail("not system exit")

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


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


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


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

0 comments on commit 23de2d5

Please sign in to comment.