Skip to content

Commit 3623032

Browse files
committed
[tooling] Add parity to app utilities
1 parent 4ac6a7d commit 3623032

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

exercise_utils/cli.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,67 @@
11
"""General utility functions for running CLI commands."""
22

3+
import os
34
import subprocess
5+
from dataclasses import dataclass
6+
from subprocess import CompletedProcess
47
from sys import exit
5-
from typing import List, Optional
8+
from typing import Dict, List, Optional
9+
10+
11+
@dataclass
12+
class CommandResult:
13+
result: CompletedProcess[str]
14+
15+
def is_success(self) -> bool:
16+
return self.result.returncode == 0
17+
18+
@property
19+
def stdout(self) -> str:
20+
return self.result.stdout.strip()
21+
22+
@property
23+
def returncode(self) -> int:
24+
return self.result.returncode
25+
26+
27+
def run(
28+
command: List[str],
29+
verbose: bool,
30+
env: Dict[str, str] = {},
31+
exit_on_error: bool = False,
32+
) -> CommandResult:
33+
"""Runs the given command, logging the output if verbose is True."""
34+
try:
35+
result = subprocess.run(
36+
command,
37+
capture_output=True,
38+
text=True,
39+
env=dict(os.environ, **env),
40+
encoding="utf-8",
41+
)
42+
except FileNotFoundError:
43+
if exit_on_error:
44+
exit(1)
45+
error_msg = f"Command not found: {command[0]}"
46+
result = CompletedProcess(command, returncode=127, stdout="", stderr=error_msg)
47+
except PermissionError:
48+
if exit_on_error:
49+
exit(1)
50+
error_msg = f"Permission denied: {command[0]}"
51+
result = CompletedProcess(command, returncode=126, stdout="", stderr=error_msg)
52+
except OSError as e:
53+
if exit_on_error:
54+
exit(1)
55+
error_msg = f"OS error when running command {command}: {e}"
56+
result = CompletedProcess(command, returncode=1, stdout="", stderr=error_msg)
57+
58+
if verbose:
59+
if result.returncode == 0:
60+
print("\t" + result.stdout)
61+
else:
62+
print("\t" + result.stderr)
63+
64+
return CommandResult(result=result)
665

766

867
def run_command(command: List[str], verbose: bool) -> Optional[str]:

exercise_utils/git.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,8 @@ def track_remote_branch(remote: str, branch: str, verbose: bool) -> None:
7878
def remove_remote(remote: str, verbose: bool) -> None:
7979
"""Removes a given remote."""
8080
run_command(["git", "remote", "rm", remote], verbose)
81+
82+
83+
def add_remote(remote: str, remote_url: str, verbose: bool) -> None:
84+
"""Adds a remote with the given name and URL."""
85+
run_command(["git", "remote", "add", remote, remote_url], verbose)

exercise_utils/github_cli.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Wrapper for Github CLI commands."""
2+
# TODO: The following should be built using the builder pattern
3+
4+
from typing import Optional
5+
6+
from exercise_utils.cli import run
7+
8+
9+
def fork_repo(repository_name: str, fork_name: str, verbose: bool) -> None:
10+
"""Creates a fork of a repository."""
11+
run(
12+
[
13+
"gh",
14+
"repo",
15+
"fork",
16+
repository_name,
17+
"--default-branch-only",
18+
"--fork-name",
19+
fork_name,
20+
],
21+
verbose,
22+
)
23+
24+
25+
def clone_repo(repository_name: str, verbose: bool, name: Optional[str] = None) -> None:
26+
"""Creates a clone of a repository."""
27+
if name is not None:
28+
run(["gh", "repo", "clone", repository_name, name], verbose)
29+
else:
30+
run(["gh", "repo", "clone", repository_name], verbose)
31+
32+
33+
def delete_repo(repository_name: str, verbose: bool) -> None:
34+
"""Deletes a repository."""
35+
run(["gh", "repo", "delete", repository_name, "--yes"], verbose)
36+
37+
38+
def create_repo(repository_name: str, verbose: bool) -> None:
39+
"""Creates a Github repository on the current user's account."""
40+
run(["gh", "repo", "create", repository_name, "--public"], verbose)
41+
42+
43+
def get_github_username(verbose: bool) -> str:
44+
"""Returns the currently authenticated Github user's username."""
45+
result = run(["gh", "api", "user", "-q", ".login"], verbose)
46+
47+
if result.is_success():
48+
username = result.stdout.splitlines()[0]
49+
return username
50+
return ""
51+
52+
53+
def has_repo(repo_name: str, is_fork: bool, verbose: bool) -> bool:
54+
"""Returns if the given repository exists under the current user's repositories."""
55+
command = ["gh", "repo", "view", repo_name]
56+
if is_fork:
57+
command.extend(["--json", "isFork", "--jq", ".isFork"])
58+
result = run(
59+
command,
60+
verbose,
61+
env={"GH_PAGER": "cat"},
62+
)
63+
return result.is_success() and result.stdout == "true"

0 commit comments

Comments
 (0)