Skip to content

Commit 51e613c

Browse files
authored
Organize package and start testing (#15)
* Organize package Split commands into their own modules for better management. - Add new init command - Add new edit command - Add new implode command - Add new sniff command - Add some unit tests - Update validations/confirmations * Reformat file
1 parent 1ae17a6 commit 51e613c

15 files changed

Lines changed: 517 additions & 155 deletions

File tree

docs/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- `init` command to create a new config file
13+
- `edit` command to edit a config file manually
14+
- `implode` command to remove configuration
15+
- `sniff` command to inspect configuration and issues
16+
- More confirmations for exceptional cases
17+
- A start to unit tests
18+
19+
### Changed
20+
21+
- Move root options to new `sniff` command
22+
- Move subcommands and utilities to individual modules
23+
- Updated error and confirmation messaging
24+
- Open long repo lists in pager
25+
1026
## [0.0.5] - 2023-12-09
1127

1228
### Changed

src/repo_man/cli.py

Lines changed: 21 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,34 @@
11
import configparser
2-
import sys
3-
from pathlib import Path
4-
from typing import NoReturn, Union
52

63
import click
74

5+
from repo_man.commands.add import add
6+
from repo_man.commands.edit import edit
7+
from repo_man.commands.flavors import flavors
8+
from repo_man.commands.implode import implode
9+
from repo_man.commands.init import init
10+
from repo_man.commands.list_repos import list_repos
11+
from repo_man.commands.sniff import sniff
12+
from repo_man.consts import REPO_TYPES_CFG
813

9-
FAIL = "\033[91m"
10-
ENDC = "\033[0m"
11-
REPO_TYPES_CFG = "repo-man.cfg"
1214

13-
14-
def check_if_allowed(path: Path) -> Union[bool, NoReturn]:
15-
if REPO_TYPES_CFG not in (str(item) for item in path.iterdir()):
16-
print(f"{FAIL}The current directory is not configured for repository management{ENDC}")
17-
sys.exit(1)
18-
19-
return True
20-
21-
22-
def parse_repo_types(config: configparser.ConfigParser) -> dict[str, set[str]]:
23-
repo_types: dict[str, set[str]] = {"all": set()}
24-
for section in config.sections():
25-
repos = {repo for repo in config[section]["known"].split("\n") if repo}
26-
repo_types[section] = repos
27-
if section != "ignore":
28-
repo_types["all"].update(repos)
29-
30-
return repo_types
31-
32-
33-
def check_missing_repos(path: Path, repo_types: dict[str, set[str]]) -> None:
34-
missing = set()
35-
directories = {str(directory) for directory in path.iterdir()}
36-
37-
for repo in sorted(repo_types["all"]):
38-
if repo not in directories:
39-
missing.add(repo)
40-
41-
if missing:
42-
print(f"{FAIL}The following repositories are configured but do not exist:")
43-
for repo in missing:
44-
print(f"\t{repo}")
45-
sys.exit(1)
46-
47-
return None
48-
49-
50-
def get_valid_repo_types():
51-
config = configparser.ConfigParser()
52-
config.read(REPO_TYPES_CFG)
53-
valid_repo_types = parse_repo_types(config)
54-
return sorted(set(valid_repo_types.keys()))
55-
56-
57-
def main():
58-
path = Path(".")
59-
check_if_allowed(path)
60-
61-
config = configparser.ConfigParser()
62-
config.read(REPO_TYPES_CFG)
63-
valid_repo_types = parse_repo_types(config)
64-
check_missing_repos(path, valid_repo_types)
65-
66-
cli()
67-
68-
69-
@click.group(invoke_without_command=True, context_settings={"help_option_names": ["-h", "--help"]})
15+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
7016
@click.version_option(package_name="repo-man")
71-
@click.option("-k", "--known", is_flag=True, help="List known repository types")
72-
@click.option("-u", "--unconfigured", is_flag=True, help="List repositories without a configured type")
73-
@click.option("-d", "--duplicates", is_flag=True, help="List repositories with more than one configured type")
74-
# @click.option("-v", "--verbose", "verbosity", count=True)
75-
def cli(known: bool, unconfigured: bool, duplicates: bool):
17+
@click.pass_context
18+
def cli(context):
7619
"""Manage repositories of different types"""
7720

78-
path = Path(".")
79-
config = configparser.ConfigParser()
80-
config.read(REPO_TYPES_CFG)
81-
valid_repo_types = parse_repo_types(config)
82-
83-
if known:
84-
known_repo_types = sorted(
85-
[repo_type for repo_type in valid_repo_types if repo_type != "all" and repo_type != "ignore"]
86-
)
87-
for repo_type in known_repo_types:
88-
print(repo_type)
89-
90-
if unconfigured:
91-
for directory in sorted(path.iterdir()):
92-
if (
93-
directory.is_dir()
94-
and str(directory) not in valid_repo_types["all"]
95-
and str(directory) not in valid_repo_types.get("ignore", [])
96-
):
97-
print(directory)
98-
99-
if duplicates:
100-
seen = set()
101-
for repo_type in valid_repo_types:
102-
if repo_type != "all" and repo_type != "ignore":
103-
for repo in valid_repo_types[repo_type]:
104-
if repo in seen:
105-
print(repo)
106-
seen.add(repo)
107-
108-
109-
@cli.command(name="list", help="The type of repository to manage")
110-
@click.option("-t", "--type", "repo_type", type=click.Choice(get_valid_repo_types()), show_choices=False, required=True)
111-
def list_repos(repo_type: str):
112-
"""List matching repositories"""
113-
11421
config = configparser.ConfigParser()
11522
config.read(REPO_TYPES_CFG)
116-
valid_repo_types = parse_repo_types(config)
23+
context.obj = config
11724

118-
for repo in valid_repo_types[repo_type]:
119-
print(repo)
120-
121-
return None
122-
123-
124-
@cli.command
125-
@click.argument("repo", type=click.Path(exists=True, file_okay=False))
126-
def flavors(repo: str):
127-
"""List the configured types for a repository"""
128-
129-
config = configparser.ConfigParser()
130-
config.read(REPO_TYPES_CFG)
13125

132-
found = set()
133-
134-
for section in config.sections():
135-
if section == "ignore":
136-
continue
137-
if repo in config[section]["known"].split("\n"):
138-
found.add(section)
139-
140-
for repository in sorted(found):
141-
print(repository)
142-
143-
144-
@cli.command
145-
@click.option("-t", "--type", "repo_types", multiple=True, help="The type of the repository", required=True)
146-
@click.argument("repo", type=click.Path(exists=True, file_okay=False))
147-
def add(repo: str, repo_types: list):
148-
"""Add a new repository"""
149-
150-
config = configparser.ConfigParser()
151-
config.read(REPO_TYPES_CFG)
152-
153-
for repo_type in repo_types:
154-
if repo_type in config:
155-
original_config = config[repo_type]["known"]
156-
else:
157-
original_config = ""
158-
config.add_section(repo_type)
159-
160-
if "known" not in config[repo_type] or repo not in config[repo_type]["known"].split("\n"):
161-
config.set(repo_type, "known", f"{original_config}\n{repo}")
162-
163-
with open(REPO_TYPES_CFG, "w") as config_file:
164-
config.write(config_file)
26+
def main():
27+
cli.add_command(add)
28+
cli.add_command(edit)
29+
cli.add_command(flavors)
30+
cli.add_command(implode)
31+
cli.add_command(init)
32+
cli.add_command(list_repos)
33+
cli.add_command(sniff)
34+
cli()

src/repo_man/commands/__init__.py

Whitespace-only changes.

src/repo_man/commands/add.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import configparser
2+
from pathlib import Path
3+
4+
import click
5+
6+
from repo_man.consts import REPO_TYPES_CFG
7+
from repo_man.utils import pass_config
8+
9+
10+
@click.command
11+
@click.option("-t", "--type", "repo_types", multiple=True, help="The type of the repository", required=True)
12+
@click.argument("repo", type=click.Path(exists=True, file_okay=False))
13+
@pass_config
14+
def add(config: configparser.ConfigParser, repo: str, repo_types: list[str]):
15+
"""Add a new repository"""
16+
17+
if not Path(REPO_TYPES_CFG).exists():
18+
click.confirm(click.style(f"No {REPO_TYPES_CFG} file found. Do you want to continue?", fg="yellow"), abort=True)
19+
20+
new_types = [repo_type for repo_type in repo_types if repo_type not in config]
21+
if new_types:
22+
message = "\n\t".join(new_types)
23+
click.confirm(f"The following types are unknown and will be added:\n\n\t{message}\n\nContinue?", abort=True)
24+
25+
for repo_type in repo_types:
26+
if repo_type in config:
27+
original_config = config[repo_type]["known"]
28+
else:
29+
original_config = ""
30+
config.add_section(repo_type)
31+
32+
if "known" not in config[repo_type] or repo not in config[repo_type]["known"].split("\n"):
33+
config.set(repo_type, "known", f"{original_config}\n{repo}")
34+
35+
with open(REPO_TYPES_CFG, "w") as config_file:
36+
config.write(config_file)

src/repo_man/commands/edit.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from pathlib import Path
2+
3+
import click
4+
5+
from repo_man.consts import REPO_TYPES_CFG
6+
7+
8+
@click.command
9+
def edit():
10+
"""Edit the repo-man configuration manually"""
11+
12+
if not Path(REPO_TYPES_CFG).exists():
13+
click.echo(click.style(f"No {REPO_TYPES_CFG} file found.", fg="red"))
14+
return
15+
16+
click.edit(filename=REPO_TYPES_CFG)

src/repo_man/commands/flavors.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import configparser
2+
from pathlib import Path
3+
4+
import click
5+
6+
from repo_man.consts import REPO_TYPES_CFG
7+
from repo_man.utils import pass_config
8+
9+
10+
@click.command
11+
@click.argument("repo", type=click.Path(exists=True, file_okay=False))
12+
@pass_config
13+
def flavors(config: configparser.ConfigParser, repo: str):
14+
"""List the configured types for a repository"""
15+
16+
if not Path(REPO_TYPES_CFG).exists():
17+
click.echo(click.style(f"No {REPO_TYPES_CFG} file found.", fg="red"))
18+
return
19+
20+
found = set()
21+
22+
for section in config.sections():
23+
if section == "ignore":
24+
continue
25+
if repo in config[section]["known"].split("\n"):
26+
found.add(section)
27+
28+
for repository in sorted(found):
29+
click.echo(repository)

src/repo_man/commands/implode.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from pathlib import Path
2+
3+
import click
4+
5+
from repo_man.consts import REPO_TYPES_CFG
6+
7+
8+
@click.command
9+
@click.argument("path", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path))
10+
def implode(path: Path):
11+
"""Remove repo-man configuration for the specified directory"""
12+
13+
click.confirm(click.style("Are you sure you want to do this?", fg="yellow"), abort=True)
14+
(path / REPO_TYPES_CFG).unlink(missing_ok=True)

src/repo_man/commands/init.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pathlib import Path
2+
3+
import click
4+
5+
from repo_man.consts import REPO_TYPES_CFG
6+
7+
8+
@click.command
9+
@click.argument("path", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path))
10+
def init(path: Path):
11+
"""Initialize repo-man to track repositories located at the specified path"""
12+
13+
if (path / REPO_TYPES_CFG).exists():
14+
click.confirm(
15+
click.style(f"{REPO_TYPES_CFG} file already exists. Overwrite with empty configuration?", fg="yellow"),
16+
abort=True,
17+
)
18+
19+
with open(path / REPO_TYPES_CFG, "w"):
20+
pass
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import configparser
2+
from pathlib import Path
3+
4+
import click
5+
6+
from repo_man.consts import REPO_TYPES_CFG
7+
from repo_man.utils import get_valid_repo_types, parse_repo_types, pass_config
8+
9+
10+
@click.command(name="list", help="The type of repository to manage")
11+
@click.option("-t", "--type", "repo_type", type=click.Choice(get_valid_repo_types()), show_choices=False, required=True)
12+
@pass_config
13+
def list_repos(config: configparser.ConfigParser, repo_type: str):
14+
"""List matching repositories"""
15+
16+
if not Path(REPO_TYPES_CFG).exists():
17+
click.echo(click.style(f"No {REPO_TYPES_CFG} file found.", fg="red"))
18+
return
19+
20+
valid_repo_types = parse_repo_types(config)
21+
22+
repos = sorted(valid_repo_types[repo_type])
23+
24+
if len(repos) > 25:
25+
click.echo_via_pager("\n".join(repos))
26+
else:
27+
click.echo("\n".join(repos))

0 commit comments

Comments
 (0)