diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml index 25a146d1..c5d4db43 100644 --- a/.github/actions/install_requirements/action.yml +++ b/.github/actions/install_requirements/action.yml @@ -32,7 +32,7 @@ runs: mkdir -p lockfiles pip freeze --exclude-editable > lockfiles/${{ inputs.requirements_file }} # delete the self referencing line and make sure it isn't blank - sed -i '/file:/d' lockfiles/${{ inputs.requirements_file }} + sed -i'' -e '/file:/d' lockfiles/${{ inputs.requirements_file }} shell: bash - name: Upload lockfiles diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py index 39c12772..d70367ae 100755 --- a/.github/pages/make_switcher.py +++ b/.github/pages/make_switcher.py @@ -64,7 +64,7 @@ def write_json(path: Path, repository: str, versions: str): ] text = json.dumps(struct, indent=2) print(f"JSON switcher:\n{text}") - path.write_text(text) + path.write_text(text, encoding="utf-8") def main(args=None): diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index c728ed8b..e57f238f 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - os: ["ubuntu-latest"] # can add windows-latest, macos-latest + os: ["ubuntu-latest", "windows-latest", "macos-latest"] python: ["3.9", "3.10", "3.11"] install: ["-e .[dev]"] # Make one version be non-editable to test both paths of version code diff --git a/pyproject.toml b/pyproject.toml index 5359c2d1..c679db98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] -description = "One line description of your module" +description = "The CLI for adopting the python3-pip-skeleton framework" dependencies = ["tomli"] # Add project dependencies here, e.g. ["click", "numpy"] dynamic = ["version"] license.file = "LICENSE" diff --git a/src/python3_pip_skeleton/__main__.py b/src/python3_pip_skeleton/__main__.py index d2ca4f5e..1688b76b 100644 --- a/src/python3_pip_skeleton/__main__.py +++ b/src/python3_pip_skeleton/__main__.py @@ -127,8 +127,10 @@ def replace_text(text: str) -> str: return text def replace_in_file(file_path: Path, text_from: str, text_to: str): - file_contents = file_path.read_text() - file_path.write_text(file_contents.replace(text_from, text_to)) + file_contents = file_path.read_text(encoding="utf-8") + file_path.write_text( + file_contents.replace(text_from, text_to), encoding="utf-8" + ) branches = list_branches(path) assert MERGE_BRANCH not in branches, ( @@ -155,22 +157,23 @@ def replace_in_file(file_path: Path, text_from: str, text_to: str): for relative_child in git_tmp("ls-files").splitlines(): child = Path(git_tmp.name) / relative_child if child.suffix in CHANGE_SUFFIXES and child.name not in IGNORE_FILES: - text = replace_text(child.read_text()) - child.write_text(text) + text = replace_text(child.read_text(encoding="utf-8")) + child.write_text(text, encoding="utf-8") # Replace the file, ignoring text between specified substrings elif ( child.suffix in CHANGE_SUFFIXES and child.name in IGNORE_FILES and IGNORE_FILES[child.name] ): - original_text = child.read_text() + original_text = child.read_text(encoding="utf-8") ignore_sections = find_ignore_sections( child.name, original_text, IGNORE_FILES[child.name] ) child.write_text( replace_text_ignoring_sections( original_text, ignore_sections, replace_text - ) + ), + encoding="utf-8", ) # Change instructions in the docs to reflect which pip skeleton is in use diff --git a/tests/test_adopt.py b/tests/test_adopt.py index 17aaf273..71252284 100644 --- a/tests/test_adopt.py +++ b/tests/test_adopt.py @@ -1,6 +1,6 @@ import subprocess import sys -from os import chdir, makedirs +from os import chdir, makedirs, name from pathlib import Path from unittest.mock import patch @@ -80,11 +80,20 @@ def test_new_module(extra_args, tmp_path: Path): ) assert (module / "src" / "my_module").is_dir() assert check_output("git", "branch", cwd=module).strip() == "* main" + check_output("python", "-m", "venv", "venv", cwd=module) - check_output("venv/bin/pip", "install", "--upgrade", "pip", cwd=module) - check_output("venv/bin/pip", "install", ".[dev]", cwd=module) + + if name == "nt": + python_exec = (module / "venv" / "Scripts" / "python.exe").absolute() + else: + python_exec = Path("venv") / "bin" / "python" + check_output( - "venv/bin/python", + str(python_exec), "-m", "pip", "install", "--upgrade", "pip", cwd=module + ) + check_output(str(python_exec), "-m", "pip", "install", ".[dev]", cwd=module) + check_output( + str(python_exec), "-m", "sphinx", "-EWT", @@ -94,7 +103,7 @@ def test_new_module(extra_args, tmp_path: Path): cwd=module, ) with pytest.raises(ValueError) as ctx: - check_output("venv/bin/python", "-m", "pytest", module / "tests", cwd=module) + check_output(str(python_exec), "-m", "pytest", module / "tests", cwd=module) out = ctx.value.args[0] print(out) assert "4 failed, 1 passed" in out @@ -138,7 +147,12 @@ def test_new_module_merge_from_valid_branch(tmp_path: Path): # Test basic functionality assert (module / "src" / "my_module").is_dir() check_output("python", "-m", "venv", "venv", cwd=module) - check_output("venv/bin/pip", "install", ".[dev]", cwd=module) + + python_exec = Path("venv") / "bin" / "python" + if name == "nt": + python_exec = (module / "venv" / "Scripts" / "python.exe").absolute() + + check_output(str(python_exec), "-m", "pip", "install", ".[dev]", cwd=module) def test_new_module_merge_from_invalid_branch(tmp_path: Path): diff --git a/tests/test_boilerplate_removed.py b/tests/test_boilerplate_removed.py new file mode 100644 index 00000000..4dcf9459 --- /dev/null +++ b/tests/test_boilerplate_removed.py @@ -0,0 +1,66 @@ +""" +This file checks that all the example boilerplate text has been removed. +It can be deleted when all the contained tests pass. + +We include it in the skeleton-cli to ensure it passes for windows and macOS too. +""" +import sys +from pathlib import Path + +if sys.version_info < (3, 8): + from importlib_metadata import metadata # noqa +else: + from importlib.metadata import metadata # noqa + +ROOT = Path(__file__).parent.parent + + +def skeleton_check(check: bool, text: str): + if ROOT.name == "python3-pip-skeleton" or str(ROOT) == "/project": + # In the skeleton module the check should fail + check = not check + text = f"Skeleton didn't raise: {text}" + if check: + raise AssertionError(text) + + +def assert_not_contains_text(path: str, text: str, explanation: str): + full_path = ROOT / path + if full_path.exists(): + contents = full_path.read_text().replace("\n", " ") + skeleton_check(text in contents, f"Please change ./{path} {explanation}") + + +# pyproject.toml +def test_module_summary(): + summary = metadata("python3-pip-skeleton")["summary"] + skeleton_check( + "One line description of your module" in summary, + "Please change project.description in ./pyproject.toml " + "to be a one line description of your module", + ) + + +# README +def test_changed_README_intro(): + assert_not_contains_text( + "README.rst", + "This is where you should write a short paragraph", + "to include an intro on what your module does", + ) + + +def test_removed_adopt_skeleton(): + assert_not_contains_text( + "README.rst", + "This project contains template code only", + "remove the note at the start", + ) + + +def test_changed_README_body(): + assert_not_contains_text( + "README.rst", + "This is where you should put some images or code snippets", + "to include some features and why people should use it", + )