From 1c1aa7e02db79c6779360e7976932c4afa67c23b Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 1/6] blacken Python scripts --- libvimcat/src/make-version.py | 186 +++++++------ test/tests.py | 497 ++++++++++++++++++---------------- 2 files changed, 363 insertions(+), 320 deletions(-) diff --git a/libvimcat/src/make-version.py b/libvimcat/src/make-version.py index f9cb898..90e1a46 100755 --- a/libvimcat/src/make-version.py +++ b/libvimcat/src/make-version.py @@ -12,118 +12,125 @@ import sys from typing import Iterator, Optional + def all_versions() -> Iterator[str]: - """ - All known versions in the CHANGELOG.rst. - """ - with open(Path(__file__).parent / "../../CHANGELOG.rst", "rt") as f: - for line in f: - m = re.match(r"(v\d{4}\.\d{2}\.\d{2})$", line) - if m is not None: - yield m.group(1) + """ + All known versions in the CHANGELOG.rst. + """ + with open(Path(__file__).parent / "../../CHANGELOG.rst", "rt") as f: + for line in f: + m = re.match(r"(v\d{4}\.\d{2}\.\d{2})$", line) + if m is not None: + yield m.group(1) + def last_release() -> str: - """ - The version of the last release. This will be used as the version number if no - Git information is available. - """ - for version in all_versions(): - return version + """ + The version of the last release. This will be used as the version number if no + Git information is available. + """ + for version in all_versions(): + return version + + return "" - return "" def has_git() -> bool: - """ - Return True if we are in a Git repository and have Git. - """ + """ + Return True if we are in a Git repository and have Git. + """ - # return False if we don't have Git - if shutil.which("git") is None: - return False + # return False if we don't have Git + if shutil.which("git") is None: + return False - # return False if we have no Git repository information - if not (Path(__file__).parent / "../../.git").exists(): - return False + # return False if we have no Git repository information + if not (Path(__file__).parent / "../../.git").exists(): + return False + + return True - return True def get_tag() -> Optional[str]: - """ - Find the version tag of the current Git commit, e.g. v2020.05.03, if it - exists. - """ - try: - tag = sp.check_output(["git", "describe", "--tags"], stderr=sp.DEVNULL) - except sp.CalledProcessError: - tag = None - - if tag is not None: - tag = tag.decode("utf-8", "replace").strip() - if re.match(r"v[\d\.]+$", tag) is None: - # not a version tag - tag = None - - return tag + """ + Find the version tag of the current Git commit, e.g. v2020.05.03, if it + exists. + """ + try: + tag = sp.check_output(["git", "describe", "--tags"], stderr=sp.DEVNULL) + except sp.CalledProcessError: + tag = None + + if tag is not None: + tag = tag.decode("utf-8", "replace").strip() + if re.match(r"v[\d\.]+$", tag) is None: + # not a version tag + tag = None + + return tag + def get_sha() -> str: - """ - Find the hash of the current Git commit. - """ - rev = sp.check_output(["git", "rev-parse", "--verify", "HEAD"]) - rev = rev.decode("utf-8", "replace").strip() + """ + Find the hash of the current Git commit. + """ + rev = sp.check_output(["git", "rev-parse", "--verify", "HEAD"]) + rev = rev.decode("utf-8", "replace").strip() + + return rev - return rev def is_dirty() -> bool: - """ - Determine whether the current working directory has uncommitted changes. - """ - dirty = False + """ + Determine whether the current working directory has uncommitted changes. + """ + dirty = False - p = sp.run(["git", "diff", "--exit-code"], stdout=sp.DEVNULL, - stderr=sp.DEVNULL) - dirty |= p.returncode != 0 + p = sp.run(["git", "diff", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL) + dirty |= p.returncode != 0 - p = sp.run(["git", "diff", "--cached", "--exit-code"], stdout=sp.DEVNULL, - stderr=sp.DEVNULL) - dirty |= p.returncode != 0 + p = sp.run( + ["git", "diff", "--cached", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL + ) + dirty |= p.returncode != 0 + + return dirty - return dirty def main(args: [str]) -> int: - if len(args) != 2 or args[1] == "--help": - sys.stderr.write( - f"usage: {args[0]} file\n" - " write version information as a C source file\n") - return -1 + if len(args) != 2 or args[1] == "--help": + sys.stderr.write( + f"usage: {args[0]} file\n" " write version information as a C source file\n" + ) + return -1 - # get the contents of the old version file if it exists - old = None - if os.path.exists(args[1]): - old = Path(args[1]).read_text() + # get the contents of the old version file if it exists + old = None + if os.path.exists(args[1]): + old = Path(args[1]).read_text() - version = None + version = None - # look for a version tag on the current commit - if version is None and has_git(): - tag = get_tag() - if tag is not None: - version = f'{tag}{" (dirty)" if is_dirty() else ""}' + # look for a version tag on the current commit + if version is None and has_git(): + tag = get_tag() + if tag is not None: + version = f'{tag}{" (dirty)" if is_dirty() else ""}' - # look for the commit hash as the version - if version is None and has_git(): - rev = get_sha() - assert rev is not None - version = f'Git commit {rev}{" (dirty)" if is_dirty() else ""}' + # look for the commit hash as the version + if version is None and has_git(): + rev = get_sha() + assert rev is not None + version = f'Git commit {rev}{" (dirty)" if is_dirty() else ""}' - # fall back to our known release version - if version is None: - version = last_release() + # fall back to our known release version + if version is None: + version = last_release() - known_versions = ", ".join(f'"{v}"' for v in reversed(list(all_versions()))) + known_versions = ", ".join(f'"{v}"' for v in reversed(list(all_versions()))) - new = f"""\ + new = f"""\ #include #include @@ -145,12 +152,13 @@ def main(args: [str]) -> int: sizeof(KNOWN_VERSIONS) / sizeof(KNOWN_VERSIONS[0]); """ - # If the version has changed, update the output. Otherwise we leave the old - # contents – and more importantly, the timestamp – intact. - if old != new: - Path(args[1]).write_text(new) + # If the version has changed, update the output. Otherwise we leave the old + # contents – and more importantly, the timestamp – intact. + if old != new: + Path(args[1]).write_text(new) + + return 0 - return 0 if __name__ == "__main__": - sys.exit(main(sys.argv)) + sys.exit(main(sys.argv)) diff --git a/test/tests.py b/test/tests.py index 1433914..8766ca5 100644 --- a/test/tests.py +++ b/test/tests.py @@ -10,295 +10,330 @@ from pathlib import Path from typing import Dict, Optional + def make_vimcatrc(home: Path): - """ - create an empty ~/.vimcatrc to pass the consent check - """ - (home / ".vimcatrc").write_text("") + """ + create an empty ~/.vimcatrc to pass the consent check + """ + (home / ".vimcatrc").write_text("") + def set_home(home: Path) -> Dict[str, str]: - """ - setup an environment using the given path as ${HOME} - """ - env = os.environ.copy() - env["HOME"] = str(home) - make_vimcatrc(home) - return env + """ + setup an environment using the given path as ${HOME} + """ + env = os.environ.copy() + env["HOME"] = str(home) + make_vimcatrc(home) + return env + @pytest.mark.parametrize("colour", (None, "always", "auto", "never")) @pytest.mark.parametrize("no_color", (False, True)) @pytest.mark.parametrize("t_Co", (2, 8, 16, 88, 256, 16777216)) @pytest.mark.parametrize("termguicolors", (False, True)) @pytest.mark.parametrize("title", (False, True)) -def test_colour(tmp_path: Path, colour: Optional[str], no_color: bool, - t_Co: int, termguicolors: bool, title: bool): - """ - `vimcat` should obey the user’s colour preferences - """ - - if termguicolors and "256color" not in os.environ.get("TERM", ""): - pytest.skip("no 256-color support in this terminal") - - env = set_home(tmp_path) - if no_color: - env["NO_COLOR"] = "1" - elif "NO_COLOR" in env: - del env["NO_COLOR"] - - # write a vimrc to force syntax highlighting and 8-bit colour - with open(tmp_path / ".vimrc", "wt") as f: - f.write(f"syntax on\nset t_Co={t_Co}\n") - if termguicolors: - f.write("set termguicolors\n" - # we also need to teach Vim how to colourise - 'let &t_8f = "\\[38;2;%lu;%lu;%lum"\n' - 'let &t_8b = "\\[48;2;%lu;%lu;%lum"\n') - if title: - f.write("set title\n") - - args = ["vimcat", "--debug"] - if colour is not None: - args += [f"--colour={colour}"] - - # highlight a C file - source = Path(__file__).parent / "test_version_le.c" - output = subprocess.check_output(args + ["--", source], env=env) - - # was there a Control Sequence Identifier in the output? - contains_csi = b"\033[" in output - - # allow no colour in monochrome mode, as it may be unused/unsupported - if t_Co == 2 and not contains_csi: - return - - if colour == "auto" or colour is None: - assert contains_csi != no_color, "incorrect NO_COLOR handling" - elif colour == "always": - assert contains_csi, "incorrect --colour=always behaviour" - else: - assert colour == "never" - assert not contains_csi, "incorrect --colour=never behaviour" +def test_colour( + tmp_path: Path, + colour: Optional[str], + no_color: bool, + t_Co: int, + termguicolors: bool, + title: bool, +): + """ + `vimcat` should obey the user’s colour preferences + """ + + if termguicolors and "256color" not in os.environ.get("TERM", ""): + pytest.skip("no 256-color support in this terminal") + + env = set_home(tmp_path) + if no_color: + env["NO_COLOR"] = "1" + elif "NO_COLOR" in env: + del env["NO_COLOR"] + + # write a vimrc to force syntax highlighting and 8-bit colour + with open(tmp_path / ".vimrc", "wt") as f: + f.write(f"syntax on\nset t_Co={t_Co}\n") + if termguicolors: + f.write( + "set termguicolors\n" + # we also need to teach Vim how to colourise + 'let &t_8f = "\\[38;2;%lu;%lu;%lum"\n' + 'let &t_8b = "\\[48;2;%lu;%lu;%lum"\n' + ) + if title: + f.write("set title\n") + + args = ["vimcat", "--debug"] + if colour is not None: + args += [f"--colour={colour}"] + + # highlight a C file + source = Path(__file__).parent / "test_version_le.c" + output = subprocess.check_output(args + ["--", source], env=env) + + # was there a Control Sequence Identifier in the output? + contains_csi = b"\033[" in output + + # allow no colour in monochrome mode, as it may be unused/unsupported + if t_Co == 2 and not contains_csi: + return + + if colour == "auto" or colour is None: + assert contains_csi != no_color, "incorrect NO_COLOR handling" + elif colour == "always": + assert contains_csi, "incorrect --colour=always behaviour" + else: + assert colour == "never" + assert not contains_csi, "incorrect --colour=never behaviour" + VIM_COLUMN_LIMIT = 10000 """ maximum number of terminal columns Vim will render """ + @pytest.mark.xfail(strict=True) def test_combining_characters(tmp_path: Path): - """ - UTF-8 combining characters should be rendered in the correct terminal cell - """ + """ + UTF-8 combining characters should be rendered in the correct terminal cell + """ + + # We cannot directly read the virtual terminal interface through `vimcat` and + # we cannot distinguish which column a given character we receive came from. + # So we use a trick where we stick the combining character just across the + # border of the maximum column Vim will render to. So if we get the combining + # right, this should be visible, and if not it will be invisible. - # We cannot directly read the virtual terminal interface through `vimcat` and - # we cannot distinguish which column a given character we receive came from. - # So we use a trick where we stick the combining character just across the - # border of the maximum column Vim will render to. So if we get the combining - # right, this should be visible, and if not it will be invisible. + sample = tmp_path / "input.txt" - sample = tmp_path / "input.txt" + # write a file containing a trailing combining character + with open(sample, "wb") as f: + for _ in range(VIM_COLUMN_LIMIT - 1): + f.write(b" ") + f.write(b"e") + f.write(b"\xcc\x81") - # write a file containing a trailing combining character - with open(sample, "wb") as f: - for _ in range(VIM_COLUMN_LIMIT - 1): - f.write(b" ") - f.write(b"e") - f.write(b"\xcc\x81") + env = set_home(tmp_path) - env = set_home(tmp_path) + # ask `vimcat` to render it + output = subprocess.check_output(["vimcat", "--debug", sample], env=env) - # ask `vimcat` to render it - output = subprocess.check_output(["vimcat", "--debug", sample], env=env) + prefix = b" " * (VIM_COLUMN_LIMIT - 1) + assert output.startswith(prefix), "incorrect leading space" - prefix = b" " * (VIM_COLUMN_LIMIT - 1) - assert output.startswith(prefix), "incorrect leading space" + assert output[len(prefix) :] == b"e\xcc\x81\n", "truncated combining character" - assert output[len(prefix):] == b"e\xcc\x81\n", "truncated combining character" @pytest.mark.parametrize("debug", (False, True)) def test_consent(tmp_path: Path, debug: bool): - """ - Vimcat should refuse to run without ~/.vimcatrc - """ - - # like `set_home` but exclude creating a ~/.vimcatrc - env = os.environ.copy() - env["HOME"] = str(tmp_path) - - # pick an arbitrary file to cat - subject = Path(__file__).resolve() - - # run vimcat - args = ["vimcat"] - if debug: - args += ["--debug"] - args += [subject] - ret = subprocess.call(args, env=env) - - assert ret != 0, "vimcat ran successfully without ~/.vimcatrc" - -@pytest.mark.parametrize("case", ( - "newline1.txt", - "newline2.txt", - "newline3.txt", - "newline4.txt", - "newline5.txt", - "newline6.txt", - "newline7.txt", -)) + """ + Vimcat should refuse to run without ~/.vimcatrc + """ + + # like `set_home` but exclude creating a ~/.vimcatrc + env = os.environ.copy() + env["HOME"] = str(tmp_path) + + # pick an arbitrary file to cat + subject = Path(__file__).resolve() + + # run vimcat + args = ["vimcat"] + if debug: + args += ["--debug"] + args += [subject] + ret = subprocess.call(args, env=env) + + assert ret != 0, "vimcat ran successfully without ~/.vimcatrc" + + +@pytest.mark.parametrize( + "case", + ( + "newline1.txt", + "newline2.txt", + "newline3.txt", + "newline4.txt", + "newline5.txt", + "newline6.txt", + "newline7.txt", + ), +) def test_newline(tmp_path: Path, case: str): - """ - check `vimcat` deals with various newline ending/not-ending correctly - """ + """ + check `vimcat` deals with various newline ending/not-ending correctly + """ + + env = set_home(tmp_path) - env = set_home(tmp_path) + # run `vimcat` on some sample input + input = Path(__file__).parent / case + assert input.exists(), "missing test case input" + output = subprocess.check_output( + ["vimcat", "--debug", input], universal_newlines=True, env=env + ) - # run `vimcat` on some sample input - input = Path(__file__).parent / case - assert input.exists(), "missing test case input" - output = subprocess.check_output(["vimcat", "--debug", input], - universal_newlines=True, env=env) + # read the sample in Python + reference = input.read_text() - # read the sample in Python - reference = input.read_text() + # if it was non-empty and did not end in a newline, `vimcat` should have added + # one + if len(reference) == 0 or reference[-1] != "\n": + reference += "\n" - # if it was non-empty and did not end in a newline, `vimcat` should have added - # one - if len(reference) == 0 or reference[-1] != "\n": - reference += "\n" + assert output == reference, "incorrect newline handling" - assert output == reference, "incorrect newline handling" def test_no_file(tmp_path: Path): - """ - passing a non-existent file should produce no output and an error message - """ + """ + passing a non-existent file should produce no output and an error message + """ - input = tmp_path / "no-file.txt" - env = set_home(tmp_path) + input = tmp_path / "no-file.txt" + env = set_home(tmp_path) - p = subprocess.run(["vimcat", input], capture_output=True, env=env) + p = subprocess.run(["vimcat", input], capture_output=True, env=env) + + assert p.returncode != 0, "EXIT_SUCCESS status with non-existent file" + assert p.stdout == b"", "output for non-existent file" + assert p.stderr != b"", "no error message for non-existent file" - assert p.returncode != 0, "EXIT_SUCCESS status with non-existent file" - assert p.stdout == b"", "output for non-existent file" - assert p.stderr != b"", "no error message for non-existent file" def test_no_vim(tmp_path: Path): - """ - if `vim` is not installed, we should get a reasonable error message - """ - env = set_home(tmp_path) + """ + if `vim` is not installed, we should get a reasonable error message + """ + env = set_home(tmp_path) + + # construct an absolute path to vimcat + vimcat = shutil.which("vimcat") + assert vimcat is not None, "vimcat not found" + vimcat = Path(vimcat).resolve() - # construct an absolute path to vimcat - vimcat = shutil.which("vimcat") - assert vimcat is not None, "vimcat not found" - vimcat = Path(vimcat).resolve() + # blank `$PATH` so `vim` cannot be found + env["PATH"] = "" - # blank `$PATH` so `vim` cannot be found - env["PATH"] = "" + # run `vimcat` on an arbitrary file + src = Path(__file__).resolve() + p = subprocess.run( + [vimcat, src], capture_output=True, universal_newlines=True, env=env + ) - # run `vimcat` on an arbitrary file - src = Path(__file__).resolve() - p = subprocess.run([vimcat, src], capture_output=True, - universal_newlines=True, env=env) + assert p.returncode != 0, "vimcat exited with success even without vim available" + assert ( + re.search(r"\bvim\b", p.stderr) is not None + ), "error message did not mention vim" - assert p.returncode != 0, \ - "vimcat exited with success even without vim available" - assert re.search(r"\bvim\b", p.stderr) is not None, \ - "error message did not mention vim" VIM_LINE_LIMIT = 1000 """ maximum number of terminal lines Vim will render """ -@pytest.mark.parametrize("height", - list(range(VIM_LINE_LIMIT - 2, VIM_LINE_LIMIT + 3)) + - list(range(2 * VIM_LINE_LIMIT - 2, 2 * VIM_LINE_LIMIT + 3)) + +@pytest.mark.parametrize( + "height", + list(range(VIM_LINE_LIMIT - 2, VIM_LINE_LIMIT + 3)) + + list(range(2 * VIM_LINE_LIMIT - 2, 2 * VIM_LINE_LIMIT + 3)), ) def test_tall(tmp_path: Path, height: int): - """ - check displaying a file near the boundaries of Vim’s line limit - """ - - sample = tmp_path / "input.txt" - env = set_home(tmp_path) - - # setup a file with many lines - with open(sample, "wt") as f: - for i in range(height): - f.write(f"line {i}\n") - - # ask `vimcat` to display it - output = subprocess.check_output(["vimcat", "--debug", sample], - universal_newlines=True, env=env) - - # confirm we got what we expected - i = 0 - for line in output.splitlines(): - assert line == f"line {i}", f"incorrect output at line {i + 1}" - i += 1 - assert i == height, "incorrect total number of lines" - -@pytest.mark.parametrize("case", ( - "utf-8.txt", - "utf-8_1.txt", - "utf-8_2.txt", - "utf-8_3.txt", - "utf-8_4.txt", - "utf-8_5.txt", - "utf-8_6.txt", -)) + """ + check displaying a file near the boundaries of Vim’s line limit + """ + + sample = tmp_path / "input.txt" + env = set_home(tmp_path) + + # setup a file with many lines + with open(sample, "wt") as f: + for i in range(height): + f.write(f"line {i}\n") + + # ask `vimcat` to display it + output = subprocess.check_output( + ["vimcat", "--debug", sample], universal_newlines=True, env=env + ) + + # confirm we got what we expected + i = 0 + for line in output.splitlines(): + assert line == f"line {i}", f"incorrect output at line {i + 1}" + i += 1 + assert i == height, "incorrect total number of lines" + + +@pytest.mark.parametrize( + "case", + ( + "utf-8.txt", + "utf-8_1.txt", + "utf-8_2.txt", + "utf-8_3.txt", + "utf-8_4.txt", + "utf-8_5.txt", + "utf-8_6.txt", + ), +) def test_utf8(tmp_path: Path, case: str): - """ - check `vimcat` can deal with UTF-8 characters of any length - """ + """ + check `vimcat` can deal with UTF-8 characters of any length + """ - env = set_home(tmp_path) + env = set_home(tmp_path) - # run `vimcat` on a sample containing characters of various lengths - input = Path(__file__).parent / case - assert input.exists(), "missing test case input" - output = subprocess.check_output(["vimcat", "--debug", input], - universal_newlines=True, env=env) + # run `vimcat` on a sample containing characters of various lengths + input = Path(__file__).parent / case + assert input.exists(), "missing test case input" + output = subprocess.check_output( + ["vimcat", "--debug", input], universal_newlines=True, env=env + ) - # read the sample with Python, which we know understands UTF-8 correctly - reference = input.read_text(encoding="utf-8").strip() + # read the sample with Python, which we know understands UTF-8 correctly + reference = input.read_text(encoding="utf-8").strip() + + # `vimcat` should have given us the same, under the assumption no syntax + # highlighting of basic text files is enabled + assert output.strip() == reference, "incorrect UTF-8 decoding" - # `vimcat` should have given us the same, under the assumption no syntax - # highlighting of basic text files is enabled - assert output.strip() == reference, "incorrect UTF-8 decoding" def test_version_le(): - """ - version comparison API should behave as expected - """ - subprocess.check_call(["test_version_le"]) + """ + version comparison API should behave as expected + """ + subprocess.check_call(["test_version_le"]) + -@pytest.mark.parametrize("width", - list(range(VIM_COLUMN_LIMIT - 2, VIM_COLUMN_LIMIT + 3))) +@pytest.mark.parametrize( + "width", list(range(VIM_COLUMN_LIMIT - 2, VIM_COLUMN_LIMIT + 3)) +) def test_wide(tmp_path: Path, width: int): - """ - at least as many columns as the Vim render limit should be displayed - """ - - sample = tmp_path / "input.txt" - env = set_home(tmp_path) - - # setup a file with a wide line: - with open(sample, "wt") as f: - for _ in range(width): - f.write("a") - f.write("\n") - - # ask `vimcat` to display it - output = subprocess.check_output(["vimcat", "--debug", sample], - universal_newlines=True, env=env) - - # confirm we got at least as many columns as expected - if width <= VIM_COLUMN_LIMIT: - reference = "a" * width + "\n" - assert output == reference, "incorrect wide line rendering" - else: - reference = "a" * VIM_COLUMN_LIMIT - assert output.startswith(reference), "incorrect wide line rendering" + """ + at least as many columns as the Vim render limit should be displayed + """ + + sample = tmp_path / "input.txt" + env = set_home(tmp_path) + + # setup a file with a wide line: + with open(sample, "wt") as f: + for _ in range(width): + f.write("a") + f.write("\n") + + # ask `vimcat` to display it + output = subprocess.check_output( + ["vimcat", "--debug", sample], universal_newlines=True, env=env + ) + + # confirm we got at least as many columns as expected + if width <= VIM_COLUMN_LIMIT: + reference = "a" * width + "\n" + assert output == reference, "incorrect wide line rendering" + else: + reference = "a" * VIM_COLUMN_LIMIT + assert output.startswith(reference), "incorrect wide line rendering" From a07644c9e660764316320354abf7cc8369a87b91 Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 2/6] CI: enforce Python black formatting --- .github/workflows/ci.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d681488..20788c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,8 +112,8 @@ jobs: - run: cmake --build build --target check - run: sudo cmake --install build - clang_format: - name: clang-format + c_format: + name: C format runs-on: ubuntu-22.04 steps: - run: uname -rms @@ -121,3 +121,18 @@ jobs: - run: git clone --no-checkout -- ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} wd - run: cd wd && git fetch -- origin ${{ github.event.pull_request.head.sha }} && git checkout FETCH_HEAD - run: cd wd && git ls-files -z '**/*.c' '**/*.h' | xargs -0 -- clang-format-15 --dry-run --style=file --Werror + + py_format: + name: Python format + runs-on: ubuntu-22.04 + env: + DEBIAN_FRONTEND: noninteractive + steps: + - run: uname -rms + - run: python3 --version + - run: sudo apt-get update + - run: sudo apt-get install --no-install-recommends -y black + - run: echo "cloning ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" + - run: git clone --no-checkout -- ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} wd + - run: cd wd && git fetch -- origin ${{ github.event.pull_request.head.sha }} && git checkout FETCH_HEAD + - run: cd wd && git ls-files -z '**/*.py' | xargs -0 -- python3 -m black --check -- From 550d7c14e369fcab704c8240463a1329472de642 Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 3/6] isort Python scripts --- libvimcat/src/make-version.py | 2 +- test/tests.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libvimcat/src/make-version.py b/libvimcat/src/make-version.py index 90e1a46..2bdcf20 100755 --- a/libvimcat/src/make-version.py +++ b/libvimcat/src/make-version.py @@ -5,11 +5,11 @@ """ import os -from pathlib import Path import re import shutil import subprocess as sp import sys +from pathlib import Path from typing import Iterator, Optional diff --git a/test/tests.py b/test/tests.py index 8766ca5..26ceec5 100644 --- a/test/tests.py +++ b/test/tests.py @@ -3,13 +3,14 @@ """ import os -import pytest import re import shutil import subprocess from pathlib import Path from typing import Dict, Optional +import pytest + def make_vimcatrc(home: Path): """ From 0ae04b0cfea12a40f29967f10c95a9b68b19677f Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 4/6] CI: enforce Python isort formatting --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20788c4..11a22fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,8 +131,9 @@ jobs: - run: uname -rms - run: python3 --version - run: sudo apt-get update - - run: sudo apt-get install --no-install-recommends -y black + - run: sudo apt-get install --no-install-recommends -y black python3-isort - run: echo "cloning ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" - run: git clone --no-checkout -- ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} wd - run: cd wd && git fetch -- origin ${{ github.event.pull_request.head.sha }} && git checkout FETCH_HEAD - run: cd wd && git ls-files -z '**/*.py' | xargs -0 -- python3 -m black --check -- + - run: cd wd && git ls-files -z '**/*.py' | xargs -0 -- python3 -m isort --profile=black --check -- From 1562203a049fc3b66df78be108d663026b5cb71e Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 5/6] fix Pylint 'unspecified-encoding' warnings --- libvimcat/src/make-version.py | 8 +++++--- test/tests.py | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libvimcat/src/make-version.py b/libvimcat/src/make-version.py index 2bdcf20..23d6016 100755 --- a/libvimcat/src/make-version.py +++ b/libvimcat/src/make-version.py @@ -17,7 +17,9 @@ def all_versions() -> Iterator[str]: """ All known versions in the CHANGELOG.rst. """ - with open(Path(__file__).parent / "../../CHANGELOG.rst", "rt") as f: + with open( + Path(__file__).parent / "../../CHANGELOG.rst", "rt", encoding="utf-8" + ) as f: for line in f: m = re.match(r"(v\d{4}\.\d{2}\.\d{2})$", line) if m is not None: @@ -108,7 +110,7 @@ def main(args: [str]) -> int: # get the contents of the old version file if it exists old = None if os.path.exists(args[1]): - old = Path(args[1]).read_text() + old = Path(args[1]).read_text(encoding="utf-8") version = None @@ -155,7 +157,7 @@ def main(args: [str]) -> int: # If the version has changed, update the output. Otherwise we leave the old # contents – and more importantly, the timestamp – intact. if old != new: - Path(args[1]).write_text(new) + Path(args[1]).write_text(new, encoding="utf-8") return 0 diff --git a/test/tests.py b/test/tests.py index 26ceec5..1c185b7 100644 --- a/test/tests.py +++ b/test/tests.py @@ -56,7 +56,7 @@ def test_colour( del env["NO_COLOR"] # write a vimrc to force syntax highlighting and 8-bit colour - with open(tmp_path / ".vimrc", "wt") as f: + with open(tmp_path / ".vimrc", "wt", encoding="utf-8") as f: f.write(f"syntax on\nset t_Co={t_Co}\n") if termguicolors: f.write( @@ -251,7 +251,7 @@ def test_tall(tmp_path: Path, height: int): env = set_home(tmp_path) # setup a file with many lines - with open(sample, "wt") as f: + with open(sample, "wt", encoding="utf-8") as f: for i in range(height): f.write(f"line {i}\n") @@ -321,7 +321,7 @@ def test_wide(tmp_path: Path, width: int): env = set_home(tmp_path) # setup a file with a wide line: - with open(sample, "wt") as f: + with open(sample, "wt", encoding="utf-8") as f: for _ in range(width): f.write("a") f.write("\n") From 8b90c42d3f1ad60b926ba80f39ff1c39929629ac Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 May 2026 17:12:52 -0700 Subject: [PATCH 6/6] fix Pylint 'subprocess-run-check' warnings --- libvimcat/src/make-version.py | 12 ++++++++++-- test/tests.py | 8 ++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/libvimcat/src/make-version.py b/libvimcat/src/make-version.py index 23d6016..a6ea01d 100755 --- a/libvimcat/src/make-version.py +++ b/libvimcat/src/make-version.py @@ -88,11 +88,19 @@ def is_dirty() -> bool: """ dirty = False - p = sp.run(["git", "diff", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL) + p = sp.run( + ["git", "diff", "--exit-code"], + stdout=sp.DEVNULL, + stderr=sp.DEVNULL, + check=False, + ) dirty |= p.returncode != 0 p = sp.run( - ["git", "diff", "--cached", "--exit-code"], stdout=sp.DEVNULL, stderr=sp.DEVNULL + ["git", "diff", "--cached", "--exit-code"], + stdout=sp.DEVNULL, + stderr=sp.DEVNULL, + check=False, ) dirty |= p.returncode != 0 diff --git a/test/tests.py b/test/tests.py index 1c185b7..78357e3 100644 --- a/test/tests.py +++ b/test/tests.py @@ -198,7 +198,7 @@ def test_no_file(tmp_path: Path): input = tmp_path / "no-file.txt" env = set_home(tmp_path) - p = subprocess.run(["vimcat", input], capture_output=True, env=env) + p = subprocess.run(["vimcat", input], capture_output=True, check=False, env=env) assert p.returncode != 0, "EXIT_SUCCESS status with non-existent file" assert p.stdout == b"", "output for non-existent file" @@ -222,7 +222,11 @@ def test_no_vim(tmp_path: Path): # run `vimcat` on an arbitrary file src = Path(__file__).resolve() p = subprocess.run( - [vimcat, src], capture_output=True, universal_newlines=True, env=env + [vimcat, src], + capture_output=True, + universal_newlines=True, + check=False, + env=env, ) assert p.returncode != 0, "vimcat exited with success even without vim available"