From 27e92ba1e9725fe99e6e1c326a27b0115f300f77 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:54:21 +0200 Subject: [PATCH] Move some `utils.py` helpers to Darkgraylib --- src/darker/__main__.py | 4 +- src/darker/chooser.py | 2 +- src/darker/diff.py | 3 +- src/darker/import_sorting.py | 4 +- src/darker/tests/test_command_line.py | 3 +- src/darker/tests/test_fstring.py | 3 +- src/darker/tests/test_import_sorting.py | 3 +- src/darker/tests/test_main.py | 3 +- src/darker/tests/test_utils.py | 379 +----------------------- src/darker/tests/test_verification.py | 3 +- src/darker/utils.py | 56 +--- src/darker/verification.py | 4 +- 12 files changed, 17 insertions(+), 450 deletions(-) diff --git a/src/darker/__main__.py b/src/darker/__main__.py index 76c3702d6..a894cfc39 100644 --- a/src/darker/__main__.py +++ b/src/darker/__main__.py @@ -32,7 +32,7 @@ ) from darker.help import get_extra_instruction from darker.import_sorting import apply_isort, isort -from darker.utils import GIT_DATEFORMAT, DiffChunk, debug_dump, glob_any +from darker.utils import debug_dump, glob_any from darker.verification import ASTVerifier, BinarySearch, NotEquivalentError from darkgraylib.black_compat import find_project_root from darkgraylib.config import show_config_if_debug @@ -46,7 +46,7 @@ from darkgraylib.highlighting import colorize, should_use_color from darkgraylib.log import setup_logging from darkgraylib.main import resolve_paths -from darkgraylib.utils import TextDocument +from darkgraylib.utils import GIT_DATEFORMAT, DiffChunk, TextDocument from graylint.linting import run_linters logger = logging.getLogger(__name__) diff --git a/src/darker/chooser.py b/src/darker/chooser.py index 0c9f0d6b2..d93a90626 100644 --- a/src/darker/chooser.py +++ b/src/darker/chooser.py @@ -32,7 +32,7 @@ import logging from typing import Generator, Iterable, List -from darker.utils import DiffChunk +from darkgraylib.utils import DiffChunk logger = logging.getLogger(__name__) diff --git a/src/darker/diff.py b/src/darker/diff.py index b3c2020f4..83e7d9abd 100644 --- a/src/darker/diff.py +++ b/src/darker/diff.py @@ -68,9 +68,8 @@ from typing import Generator, List, Sequence, Tuple from darker.multiline_strings import find_overlap -from darker.utils import DiffChunk from darkgraylib.diff import diff_and_get_opcodes, validate_opcodes -from darkgraylib.utils import TextDocument +from darkgraylib.utils import DiffChunk, TextDocument logger = logging.getLogger(__name__) diff --git a/src/darker/import_sorting.py b/src/darker/import_sorting.py index d01dd55cd..7239dd3ec 100644 --- a/src/darker/import_sorting.py +++ b/src/darker/import_sorting.py @@ -7,9 +7,9 @@ from darker.diff import diff_chunks from darker.exceptions import IncompatiblePackageError, MissingPackageError from darker.git import EditedLinenumsDiffer -from darker.utils import DiffChunk, glob_any +from darker.utils import glob_any from darkgraylib.black_compat import find_project_root -from darkgraylib.utils import TextDocument +from darkgraylib.utils import DiffChunk, TextDocument try: import isort diff --git a/src/darker/tests/test_command_line.py b/src/darker/tests/test_command_line.py index a2b56fc28..719e5a9fe 100644 --- a/src/darker/tests/test_command_line.py +++ b/src/darker/tests/test_command_line.py @@ -19,11 +19,10 @@ from darker.command_line import make_argument_parser, parse_command_line from darker.config import Exclusions from darker.tests.helpers import flynt_present, isort_present -from darker.utils import joinlines from darkgraylib.config import ConfigurationError from darkgraylib.git import RevisionRange from darkgraylib.testtools.helpers import raises_if_exception -from darkgraylib.utils import TextDocument +from darkgraylib.utils import TextDocument, joinlines pytestmark = pytest.mark.usefixtures("find_project_root_cache_clear") diff --git a/src/darker/tests/test_fstring.py b/src/darker/tests/test_fstring.py index c283942d2..749d44c1a 100644 --- a/src/darker/tests/test_fstring.py +++ b/src/darker/tests/test_fstring.py @@ -10,9 +10,8 @@ import darker.fstring from darker.git import EditedLinenumsDiffer from darker.tests.helpers import flynt_present -from darker.utils import joinlines from darkgraylib.git import RevisionRange -from darkgraylib.utils import TextDocument +from darkgraylib.utils import TextDocument, joinlines ORIGINAL_SOURCE = ("'{}'.format(x)", "#", "'{0}'.format(42)") MODIFIED_SOURCE = ("'{}'.format( x)", "#", "'{0}'.format( 42)") diff --git a/src/darker/tests/test_import_sorting.py b/src/darker/tests/test_import_sorting.py index bfefdd498..0203826e8 100644 --- a/src/darker/tests/test_import_sorting.py +++ b/src/darker/tests/test_import_sorting.py @@ -11,9 +11,8 @@ import darker.import_sorting from darker.git import EditedLinenumsDiffer from darker.tests.helpers import isort_present -from darker.utils import joinlines from darkgraylib.git import RevisionRange -from darkgraylib.utils import TextDocument +from darkgraylib.utils import TextDocument, joinlines ORIGINAL_SOURCE = ("import sys", "import os", "", "print(42)") ISORTED_SOURCE = ("import os", "import sys", "", "print(42)") diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index 293c97659..7f8453330 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -24,11 +24,10 @@ from darker.git import EditedLinenumsDiffer from darker.tests.helpers import isort_present from darker.tests.test_fstring import FLYNTED_SOURCE, MODIFIED_SOURCE, ORIGINAL_SOURCE -from darker.utils import joinlines from darker.verification import NotEquivalentError from darkgraylib.git import WORKTREE, RevisionRange from darkgraylib.testtools.highlighting_helpers import BLUE, CYAN, RESET, WHITE, YELLOW -from darkgraylib.utils import TextDocument +from darkgraylib.utils import TextDocument, joinlines pytestmark = pytest.mark.usefixtures("find_project_root_cache_clear") diff --git a/src/darker/tests/test_utils.py b/src/darker/tests/test_utils.py index 655f1dca2..880a69dba 100644 --- a/src/darker/tests/test_utils.py +++ b/src/darker/tests/test_utils.py @@ -3,71 +3,9 @@ # pylint: disable=comparison-with-callable,redefined-outer-name,use-dict-literal import logging -import os -from pathlib import Path from textwrap import dedent -import pytest - -from darker.utils import ( - debug_dump, - detect_newline, - get_common_root, - get_path_ancestry, - joinlines, -) -from darkgraylib.utils import TextDocument - - -@pytest.fixture(params=[TextDocument.from_file, TextDocument.from_bytes]) -def textdocument_factory(request): - """Fixture for a factory function that creates a ``TextDocument`` - - The fixture can be parametrized with `(bytes) -> TextDocument` functions - that take the raw bytes of the document. - - By default, it is parametrized with the ``TextDocument.from_file()`` (for - which it creates a temporary file) and the ``TextDocument.from_bytes()`` - classmethods. - """ - if request.param == TextDocument.from_file: - - def factory(content): - tmp_path = request.getfixturevalue("tmp_path") - path = tmp_path / "test.py" - path.write_bytes(content) - return TextDocument.from_file(path) - - return factory - - return request.param - - -@pytest.fixture -def textdocument(request, textdocument_factory): - """Fixture for a ``TextDocument`` - - The fixture must be parametrized with the raw bytes of the document. - """ - return textdocument_factory(request.param) - - -@pytest.mark.kwparametrize( - dict(string="", expect="\n"), - dict(string="\n", expect="\n"), - dict(string="\r\n", expect="\r\n"), - dict(string="one line\n", expect="\n"), - dict(string="one line\r\n", expect="\r\n"), - dict(string="first line\nsecond line\n", expect="\n"), - dict(string="first line\r\nsecond line\r\n", expect="\r\n"), - dict(string="first unix\nthen windows\r\n", expect="\n"), - dict(string="first windows\r\nthen unix\n", expect="\r\n"), -) -def test_detect_newline(string, expect): - """``detect_newline()`` gives correct results""" - result = detect_newline(string) - - assert result == expect +from darker.utils import debug_dump def test_debug_dump(caplog, capsys): @@ -84,318 +22,3 @@ def test_debug_dump(caplog, capsys): """ ) ) - - -def test_joinlines(): - """``joinlines() concatenates and adds a newline after each given string item""" - result = joinlines(("a", "b", "c")) - assert result == "a\nb\nc\n" - - -def test_get_common_root_empty(): - """``get_common_root()`` raises a ``ValueError`` if ``paths`` argument is empty""" - with pytest.raises(ValueError): - - get_common_root([]) - - -def test_get_common_root(tmpdir): - """``get_common_root()`` traverses backwards correctly""" - tmpdir = Path(tmpdir) - path1 = tmpdir / "a" / "b" / "c" / "d" - path2 = tmpdir / "a" / "e" / ".." / "b" / "f" / "g" - path3 = tmpdir / "a" / "h" / ".." / "b" / "i" - result = get_common_root([path1, path2, path3]) - assert result == tmpdir / "a" / "b" - - -def test_get_common_root_of_directory(tmpdir): - """``get_common_root()`` returns a single directory itself""" - tmpdir = Path(tmpdir) - result = get_common_root([tmpdir]) - assert result == tmpdir - - -def test_get_path_ancestry_for_directory(tmpdir): - """``get_path_ancestry()`` includes a directory itself as the last item""" - tmpdir = Path(tmpdir) - result = list(get_path_ancestry(tmpdir)) - assert result[-1] == tmpdir - assert result[-2] == tmpdir.parent - - -def test_get_path_ancestry_for_file(tmpdir): - """``get_path_ancestry()`` includes a file's parent directory as the last item""" - tmpdir = Path(tmpdir) - dummy = tmpdir / "dummy" - dummy.write_text("dummy") - result = list(get_path_ancestry(dummy)) - assert result[-1] == tmpdir - assert result[-2] == tmpdir.parent - - -@pytest.mark.kwparametrize( - dict(textdocument=TextDocument(), expect="utf-8"), - dict(textdocument=TextDocument(encoding="utf-8"), expect="utf-8"), - dict(textdocument=TextDocument(encoding="utf-16"), expect="utf-16"), - dict(textdocument=TextDocument.from_str(""), expect="utf-8"), - dict(textdocument=TextDocument.from_str("", encoding="utf-8"), expect="utf-8"), - dict(textdocument=TextDocument.from_str("", encoding="utf-16"), expect="utf-16"), - dict(textdocument=TextDocument.from_lines([]), expect="utf-8"), - dict(textdocument=TextDocument.from_lines([], encoding="utf-8"), expect="utf-8"), - dict(textdocument=TextDocument.from_lines([], encoding="utf-16"), expect="utf-16"), -) -def test_textdocument_set_encoding(textdocument, expect): - """TextDocument.encoding is correct from each constructor""" - assert textdocument.encoding == expect - - -@pytest.mark.kwparametrize( - dict(doc=TextDocument(), expect=""), - dict(doc=TextDocument(lines=["zéro", "un"]), expect="zéro\nun\n"), - dict(doc=TextDocument(lines=["zéro", "un"], newline="\n"), expect="zéro\nun\n"), - dict( - doc=TextDocument(lines=["zéro", "un"], newline="\r\n"), expect="zéro\r\nun\r\n" - ), -) -def test_textdocument_string(doc, expect): - """TextDocument.string respects the newline setting""" - assert doc.string == expect - - -@pytest.mark.parametrize("newline", ["\n", "\r\n"]) -@pytest.mark.kwparametrize( - dict(textdocument=TextDocument(), expect=""), - dict(textdocument=TextDocument(lines=["zéro", "un"])), - dict(textdocument=TextDocument(string="zéro\nun\n")), - dict(textdocument=TextDocument(lines=["zéro", "un"], newline="\n")), - dict(textdocument=TextDocument(string="zéro\nun\n", newline="\n")), - dict(textdocument=TextDocument(lines=["zéro", "un"], newline="\r\n")), - dict(textdocument=TextDocument(string="zéro\r\nun\r\n", newline="\r\n")), - expect="zéro{newline}un{newline}", -) -def test_textdocument_string_with_newline(textdocument, newline, expect): - """TextDocument.string respects the newline setting""" - result = textdocument.string_with_newline(newline) - - expected = expect.format(newline=newline) - assert result == expected - - -@pytest.mark.kwparametrize( - dict(encoding="utf-8", newline="\n", expect=b"z\xc3\xa9ro\nun\n"), - dict(encoding="iso-8859-1", newline="\n", expect=b"z\xe9ro\nun\n"), - dict(encoding="utf-8", newline="\r\n", expect=b"z\xc3\xa9ro\r\nun\r\n"), - dict(encoding="iso-8859-1", newline="\r\n", expect=b"z\xe9ro\r\nun\r\n"), -) -def test_textdocument_encoded_string(encoding, newline, expect): - """TextDocument.encoded_string uses correct encoding and newline""" - textdocument = TextDocument( - lines=["zéro", "un"], encoding=encoding, newline=newline - ) - - assert textdocument.encoded_string == expect - - -@pytest.mark.kwparametrize( - dict(doc=TextDocument(), expect=()), - dict(doc=TextDocument(string="zéro\nun\n"), expect=("zéro", "un")), - dict(doc=TextDocument(string="zéro\nun\n", newline="\n"), expect=("zéro", "un")), - dict( - doc=TextDocument(string="zéro\r\nun\r\n", newline="\r\n"), expect=("zéro", "un") - ), -) -def test_textdocument_lines(doc, expect): - """TextDocument.lines is correct after parsing a string with different newlines""" - assert doc.lines == expect - - -@pytest.mark.kwparametrize( - dict( - textdocument=TextDocument.from_str(""), - expect_lines=(), - expect_encoding="utf-8", - expect_newline="\n", - expect_mtime="", - ), - dict( - textdocument=TextDocument.from_str("", encoding="utf-8"), - expect_lines=(), - expect_encoding="utf-8", - expect_newline="\n", - expect_mtime="", - ), - dict( - textdocument=TextDocument.from_str("", encoding="iso-8859-1"), - expect_lines=(), - expect_encoding="iso-8859-1", - expect_newline="\n", - expect_mtime="", - ), - dict( - textdocument=TextDocument.from_str("a\nb\n"), - expect_lines=("a", "b"), - expect_encoding="utf-8", - expect_newline="\n", - expect_mtime="", - ), - dict( - textdocument=TextDocument.from_str("a\r\nb\r\n"), - expect_lines=("a", "b"), - expect_encoding="utf-8", - expect_newline="\r\n", - expect_mtime="", - ), - dict( - textdocument=TextDocument.from_str("", mtime="my mtime"), - expect_lines=(), - expect_encoding="utf-8", - expect_newline="\n", - expect_mtime="my mtime", - ), -) -def test_textdocument_from_str( - textdocument, expect_lines, expect_encoding, expect_newline, expect_mtime -): - """TextDocument.from_str() gets correct content, encoding, newlines and mtime""" - assert textdocument.lines == expect_lines - assert textdocument.encoding == expect_encoding - assert textdocument.newline == expect_newline - assert textdocument.mtime == expect_mtime - - -@pytest.mark.kwparametrize( - dict(textdocument=b'print("touch\xc3\xa9")\n', expect="utf-8"), - dict(textdocument=b'\xef\xbb\xbfprint("touch\xc3\xa9")\n', expect="utf-8-sig"), - dict(textdocument=b'# coding: iso-8859-1\n"touch\xe9"\n', expect="iso-8859-1"), - indirect=["textdocument"], -) -def test_textdocument_detect_encoding(textdocument, expect): - """TextDocument.from_file/bytes() detects the file encoding correctly""" - assert textdocument.encoding == expect - - -@pytest.mark.kwparametrize( - dict(textdocument=b'print("unix")\n', expect="\n"), - dict(textdocument=b'print("windows")\r\n', expect="\r\n"), - indirect=["textdocument"], -) -def test_textdocument_detect_newline(textdocument, expect): - """TextDocument.from_file/bytes() detects the newline sequence correctly""" - assert textdocument.newline == expect - - -@pytest.mark.kwparametrize( - dict(doc1=TextDocument(lines=["foo"]), doc2=TextDocument(lines=[]), expect=False), - dict(doc1=TextDocument(lines=[]), doc2=TextDocument(lines=["foo"]), expect=False), - dict( - doc1=TextDocument(lines=["foo"]), doc2=TextDocument(lines=["bar"]), expect=False - ), - dict( - doc1=TextDocument(lines=["line1", "line2"]), - doc2=TextDocument(lines=["line1", "line2"]), - expect=True, - ), - dict( - doc1=TextDocument(lines=["line1", "line2"], encoding="utf-16", newline="\r\n"), - doc2=TextDocument(lines=["line1", "line2"]), - expect=True, - ), - dict(doc1=TextDocument(lines=["foo"]), doc2=TextDocument(""), expect=False), - dict(doc1=TextDocument(lines=[]), doc2=TextDocument("foo\n"), expect=False), - dict(doc1=TextDocument(lines=["foo"]), doc2=TextDocument("bar\n"), expect=False), - dict( - doc1=TextDocument(lines=["line1", "line2"]), - doc2=TextDocument("line1\nline2\n"), - expect=True, - ), - dict(doc1=TextDocument("foo\n"), doc2=TextDocument(lines=[]), expect=False), - dict(doc1=TextDocument(""), doc2=TextDocument(lines=["foo"]), expect=False), - dict(doc1=TextDocument("foo\n"), doc2=TextDocument(lines=["bar"]), expect=False), - dict( - doc1=TextDocument("line1\nline2\n"), - doc2=TextDocument(lines=["line1", "line2"]), - expect=True, - ), - dict(doc1=TextDocument("foo\n"), doc2=TextDocument(""), expect=False), - dict(doc1=TextDocument(""), doc2=TextDocument("foo\n"), expect=False), - dict(doc1=TextDocument("foo\n"), doc2=TextDocument("bar\n"), expect=False), - dict( - doc1=TextDocument("line1\nline2\n"), - doc2=TextDocument("line1\nline2\n"), - expect=True, - ), - dict( - doc1=TextDocument("line1\r\nline2\r\n"), - doc2=TextDocument("line1\nline2\n"), - expect=True, - ), - dict(doc1=TextDocument("foo"), doc2="line1\nline2\n", expect=NotImplemented), -) -def test_textdocument_eq(doc1, doc2, expect): - """TextDocument.__eq__()""" - result = doc1.__eq__(doc2) # pylint: disable=unnecessary-dunder-call - - assert result == expect - - -@pytest.mark.kwparametrize( - dict(document=TextDocument(""), expect="TextDocument([0 lines])"), - dict(document=TextDocument(lines=[]), expect="TextDocument([0 lines])"), - dict(document=TextDocument("One line\n"), expect="TextDocument([1 lines])"), - dict(document=TextDocument(lines=["One line"]), expect="TextDocument([1 lines])"), - dict(document=TextDocument("Two\nlines\n"), expect="TextDocument([2 lines])"), - dict( - document=TextDocument(lines=["Two", "lines"]), expect="TextDocument([2 lines])" - ), - dict( - document=TextDocument(mtime="some mtime"), - expect="TextDocument([0 lines], mtime='some mtime')", - ), - dict( - document=TextDocument(encoding="utf-8"), - expect="TextDocument([0 lines])", - ), - dict( - document=TextDocument(encoding="a non-default encoding"), - expect="TextDocument([0 lines], encoding='a non-default encoding')", - ), - dict( - document=TextDocument(newline="\n"), - expect="TextDocument([0 lines])", - ), - dict( - document=TextDocument(newline="a non-default newline"), - expect="TextDocument([0 lines], newline='a non-default newline')", - ), -) -def test_textdocument_repr(document, expect): - """TextDocument.__repr__()""" - result = repr(document) - - assert result == expect - - -@pytest.mark.kwparametrize( - dict(document=TextDocument(), expect=""), - dict(document=TextDocument(mtime=""), expect=""), - dict(document=TextDocument(mtime="dummy mtime"), expect="dummy mtime"), -) -def test_textdocument_mtime(document, expect): - """TextDocument.mtime""" - assert document.mtime == expect - - -def test_textdocument_from_file(tmp_path): - """TextDocument.from_file()""" - dummy_txt = tmp_path / "dummy.txt" - dummy_txt.write_bytes(b"# coding: iso-8859-1\r\ndummy\r\ncontent\r\n") - os.utime(dummy_txt, (1_000_000_000, 1_000_000_000)) - - document = TextDocument.from_file(dummy_txt) - - assert document.string == "# coding: iso-8859-1\r\ndummy\r\ncontent\r\n" - assert document.lines == ("# coding: iso-8859-1", "dummy", "content") - assert document.encoding == "iso-8859-1" - assert document.newline == "\r\n" - assert document.mtime == "2001-09-09 01:46:40.000000 +0000" diff --git a/src/darker/tests/test_verification.py b/src/darker/tests/test_verification.py index 70e209bf3..4cad7f42a 100644 --- a/src/darker/tests/test_verification.py +++ b/src/darker/tests/test_verification.py @@ -6,14 +6,13 @@ import pytest -from darker.utils import DiffChunk from darker.verification import ( ASTVerifier, BinarySearch, NotEquivalentError, verify_ast_unchanged, ) -from darkgraylib.utils import TextDocument +from darkgraylib.utils import DiffChunk, TextDocument @pytest.mark.kwparametrize( diff --git a/src/darker/utils.py b/src/darker/utils.py index 41e097017..acff93f36 100644 --- a/src/darker/utils.py +++ b/src/darker/utils.py @@ -1,29 +1,12 @@ """Miscellaneous utility functions""" import logging -import sys -from itertools import chain from pathlib import Path -from typing import Collection, Iterable, List, Tuple - -logger = logging.getLogger(__name__) - -TextLines = Tuple[str, ...] - - -WINDOWS = sys.platform.startswith("win") -GIT_DATEFORMAT = "%Y-%m-%d %H:%M:%S.%f +0000" +from typing import Collection, List +from darkgraylib.utils import DiffChunk -def detect_newline(string: str) -> str: - """Detect LF or CRLF newlines in a string by looking at the end of the first line""" - first_lf_pos = string.find("\n") - if first_lf_pos > 0 and string[first_lf_pos - 1] == "\r": - return "\r\n" - return "\n" - - -DiffChunk = Tuple[int, TextLines, TextLines] +logger = logging.getLogger(__name__) def debug_dump(black_chunks: List[DiffChunk], edited_linenums: List[int]) -> None: @@ -41,39 +24,6 @@ def debug_dump(black_chunks: List[DiffChunk], edited_linenums: List[int]) -> Non print(80 * "-") -def joinlines(lines: Iterable[str], newline: str = "\n") -> str: - """Join a list of lines back, adding a linefeed after each line - - This is the reverse of ``str.splitlines()``. - - """ - return "".join(f"{line}{newline}" for line in lines) - - -def get_path_ancestry(path: Path) -> Iterable[Path]: - """Return paths to directories leading to the given path - - :param path: The directory or file to get ancestor directories for - :return: A list of paths, starting from filesystem root and ending in the given - path (if it's a directory) or the parent of the given path (if it's a file) - - """ - reverse_parents = reversed(path.parents) - if path.is_dir(): - return chain(reverse_parents, [path]) - return reverse_parents - - -def get_common_root(paths: Iterable[Path]) -> Path: - """Find the deepest common parent directory of given paths""" - resolved_paths = [path.resolve() for path in paths] - parents = reversed(list(zip(*(get_path_ancestry(path) for path in resolved_paths)))) - for first_path, *other_paths in parents: - if all(path == first_path for path in other_paths): - return first_path - raise ValueError(f"Paths have no common parent Git root: {resolved_paths}") - - def glob_any(path: Path, patterns: Collection[str]) -> bool: """Return `True` if path matches any of the patterns diff --git a/src/darker/verification.py b/src/darker/verification.py index ebbc4ce61..1a89bac3b 100644 --- a/src/darker/verification.py +++ b/src/darker/verification.py @@ -4,8 +4,8 @@ from black import assert_equivalent, parse_ast, stringify_ast -from darker.utils import DiffChunk, debug_dump -from darkgraylib.utils import TextDocument +from darker.utils import debug_dump +from darkgraylib.utils import DiffChunk, TextDocument class NotEquivalentError(Exception):