Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ jobs:
toxenv: "py312"
- version: "3.11"
toxenv: "py311"
- version: "3.10"
toxenv: "py310"
steps:
- uses: actions/checkout@v6

Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ formats:
- htmlzip

build:
os: ubuntu-20.04
os: ubuntu-26.04
tools:
python: "3.14"

Expand Down
11 changes: 11 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [0.2.0] - 2026-06-22

### Fixed

- Fix independent use of Click, which is now vendored in Typer
- Fix deprecated Typer `isolated_filesystem` usage in tests

### Removed

- Remove support for Python 3.10

### Added

- Add support for Python 3.14
Expand Down
28 changes: 12 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
requires = ["uv_build>=0.11.23,<0.12.0"]
build-backend = "uv_build"

[project]
requires-python = ">=3.10"
requires-python = ">=3.11"
name = "repo-man"
version = "0.1.2"
version = "0.2.0"
description = "Manage repositories of a variety of different types."
readme = "README.md"
authors = [
{ name = "Dane Hillard", email = "github@danehillard.com" }
]
Expand All @@ -15,7 +16,6 @@ classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand All @@ -24,19 +24,15 @@ classifiers = [
"Programming Language :: Python :: 3 :: Only",
]
dependencies = [
"typer>=0.12.5"
"click>=8.4.1",
"typer>=0.26.7",
]
dynamic = ["readme"]

[project.urls]
Documentation = "https://repo-man.readthedocs.org"
Repository = "https://github.com/easy-as-python/repo-man"
Issues = "https://github.com/easy-as-python/repo-man/issues"

[tool.setuptools.packages.find]
where = ["src"]
exclude = ["test*"]

[project.scripts]
repo-man = "repo_man.cli:main"

Expand All @@ -48,8 +44,8 @@ docs = [
"sphinx-autobuild",
]

[tool.setuptools.dynamic]
readme = { file = "README.md", content-type = "text/markdown" }
[tool.uv.build-backend]
module-name = "repo_man"

######################
# Tool configuration #
Expand All @@ -76,7 +72,7 @@ select = [

[tool.mypy]
strict = true
python_version = "3.10"
python_version = "3.11"
warn_unused_configs = true
show_error_context = true
pretty = true
Expand All @@ -93,7 +89,7 @@ source = ["repo_man"]
branch = true

[tool.coverage.report]
fail_under = 90.00
fail_under = 95.00
show_missing = true
skip_covered = true

Expand All @@ -104,7 +100,7 @@ source = [
]

[tool.tox]
envlist = ["py310", "py311", "py312", "py313", "py314"]
env_list = ["py311", "py312", "py313", "py314"]
isolated_build = true

[tool.tox.env_run_base]
Expand Down
4 changes: 2 additions & 2 deletions src/repo_man/commands/edit.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import typer
import click

from repo_man.consts import REPO_TYPES_CFG
from repo_man.utils import ensure_config_file_exists
Expand All @@ -9,4 +9,4 @@ def edit() -> None:

ensure_config_file_exists()

typer.edit(filename=REPO_TYPES_CFG)
click.edit(filename=REPO_TYPES_CFG)
185 changes: 104 additions & 81 deletions test/test_add.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import configparser
import os
from collections.abc import Callable
from pathlib import Path

Expand All @@ -7,129 +8,151 @@
from repo_man.cli import cli


def test_add_clean_confirm(runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]) -> None:
with runner.isolated_filesystem():
(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="Y\nY\n", obj=config)
assert result.exit_code == 0
assert (
result.output
== """No repo-man.cfg file found. Do you want to continue? [y/N]: Y
def test_add_clean_confirm(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="Y\nY\n", obj=config)
assert result.exit_code == 0
assert (
result.output
== """No repo-man.cfg file found. Do you want to continue? [y/N]: Y
The following types are unknown and will be added:

some-type

Continue? [y/N]: Y
"""
)
)

with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n some-repo\n\n"""
with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n some-repo\n\n"""


def test_add_clean_no_confirm_new_file(runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]) -> None:
with runner.isolated_filesystem():
(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="\n", obj=config)
assert result.exit_code == 1
assert result.output == """No repo-man.cfg file found. Do you want to continue? [y/N]: \nAborted.\n"""
def test_add_clean_no_confirm_new_file(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="\n", obj=config)
assert result.exit_code == 1
assert result.output == """No repo-man.cfg file found. Do you want to continue? [y/N]: \nAborted.\n"""


def test_add_with_existing_file(runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]) -> None:
with runner.isolated_filesystem():
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[foo]
def test_add_with_existing_file(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[foo]
known =
bar
"""
)

(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="Y\n", obj=config)
assert result.exit_code == 0
assert (
result.output
== """The following types are unknown and will be added:\n\n some-type\n\nContinue? [y/N]: Y\n"""
)

with open("repo-man.cfg") as config_file:
assert config_file.read() == """[foo]\nknown = \n bar\n\n[some-type]\nknown = \n some-repo\n\n"""
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], input="Y\n", obj=config)
assert result.exit_code == 0
assert (
result.output
== """The following types are unknown and will be added:\n\n some-type\n\nContinue? [y/N]: Y\n"""
)

with open("repo-man.cfg") as config_file:
assert config_file.read() == """[foo]\nknown = \n bar\n\n[some-type]\nknown = \n some-repo\n\n"""


def test_add_with_existing_file_and_type(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
with runner.isolated_filesystem():
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
known =
bar
"""
)
)

config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], obj=config)
assert result.exit_code == 0
assert result.output == ""

(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], obj=config)
assert result.exit_code == 0
assert result.output == ""
with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n bar\n some-repo\n\n"""

with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n bar\n some-repo\n\n"""

def test_add_multiple_types(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

def test_add_multiple_types(runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]) -> None:
with runner.isolated_filesystem():
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
known =
bar
"""
)

(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(
cli, ["add", "some-repo", "-t", "some-type", "-t", "some-other-type"], input="Y\n", obj=config
)
assert result.exit_code == 0
assert (
result.output
== """The following types are unknown and will be added:

config = get_config()
result = runner.invoke(
cli, ["add", "some-repo", "-t", "some-type", "-t", "some-other-type"], input="Y\n", obj=config
)
assert result.exit_code == 0
assert (
result.output
== """The following types are unknown and will be added:

some-other-type

Continue? [y/N]: Y
"""
)

with open("repo-man.cfg") as config_file:
assert (
config_file.read()
== """[some-type]\nknown = \n bar\n some-repo\n\n[some-other-type]\nknown = \n some-repo\n\n"""
)

with open("repo-man.cfg") as config_file:
assert (
config_file.read()
== """[some-type]\nknown = \n bar\n some-repo\n\n[some-other-type]\nknown = \n some-repo\n\n"""
)

def test_add_no_action_needed(
runner: CliRunner, get_config: Callable[[], configparser.ConfigParser], tmp_path: Path
) -> None:
repo = tmp_path / "some-repo"
repo.mkdir()
os.chdir(tmp_path)

def test_add_no_action_needed(runner: CliRunner, get_config: Callable[[], configparser.ConfigParser]) -> None:
with runner.isolated_filesystem():
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
with open("repo-man.cfg", "w") as config_file:
config_file.write(
"""[some-type]
known =
some-repo
"""
)
)

(Path(".") / "some-repo").mkdir()
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], obj=config)
assert result.exit_code == 0
assert result.output == ""
config = get_config()
result = runner.invoke(cli, ["add", "some-repo", "-t", "some-type"], obj=config)
assert result.exit_code == 0
assert result.output == ""

with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n some-repo\n\n"""
with open("repo-man.cfg") as config_file:
assert config_file.read() == """[some-type]\nknown = \n some-repo\n\n"""
Loading