Skip to content

Commit

Permalink
feat: add force remove flag (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
seyLu authored Apr 6, 2024
1 parent 4dabf2a commit 0c02755
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 53 deletions.
66 changes: 35 additions & 31 deletions src/ghlabel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
from ghlabel.utils.dump_label import DumpLabel
from ghlabel.utils.github_api import GithubApi
from ghlabel.utils.github_api_types import GithubLabel
from ghlabel.utils.helpers import validate_env
from ghlabel.utils.helpers import clear_screen, validate_env
from ghlabel.utils.setup_github_label import SetupGithubLabel


def parse_remove_labels(labels: str | None) -> list[str] | None:
if not labels:
def parse_remove_labels(label_names: str | None) -> set[str] | None:
if not label_names:
return None
return list(map(str.strip, labels.split(",")))
return set(map(str.strip, label_names.split(",")))


def parse_add_labels(labels: str | None) -> list[GithubLabel] | None:
Expand Down Expand Up @@ -125,15 +125,15 @@ def setup_labels( # noqa: PLR0913
typer.Option(
"--add-labels",
"-a",
help="Add more labels.",
help="Add more Github labels.",
),
] = None,
remove_labels: Annotated[
Optional[str],
typer.Option(
"--remove-labels",
"-r",
help="Remove more labels.",
help="Remove more Github labels.",
),
] = None,
remove_all: Annotated[
Expand All @@ -144,6 +144,14 @@ def setup_labels( # noqa: PLR0913
help="Remove all Github labels.",
),
] = RemoveAllChoices.disable.value, # type: ignore[assignment]
force: Annotated[
bool,
typer.Option(
"--force-remove/--safe-remove",
"-f/-F",
help="Forcefully remove GitHub labels, even if they are currently in use on issues or pull requests.",
),
] = False,
) -> None:
if not token:
token = validate_env("GITHUB_TOKEN")
Expand All @@ -153,6 +161,7 @@ def setup_labels( # noqa: PLR0913
repo_name = validate_env("GITHUB_REPO_NAME")
gh_api: GithubApi = GithubApi(token, repo_owner, repo_name)

clear_screen()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
Expand All @@ -168,32 +177,19 @@ def setup_labels( # noqa: PLR0913
)
rich.print()

with Progress(
SpinnerColumn(style="[magenta]"),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description="[magenta]Removing...", total=None)

if remove_all.value == "enable":
gh_label.remove_all_labels(preview=preview)
elif remove_all.value == "silent":
gh_label.remove_all_labels(silent=True, preview=preview)
elif remove_all.value == "disable":
gh_label.remove_labels(
strict=strict,
labels=parse_remove_labels(remove_labels),
preview=preview,
)

with Progress(
SpinnerColumn(style="[cyan]"),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description="[cyan]Adding...", total=None)
if remove_all.value == "enable":
gh_label.remove_all_labels(preview=preview, force=force)
elif remove_all.value == "silent":
gh_label.remove_all_labels(silent=True, preview=preview, force=force)
elif remove_all.value == "disable":
gh_label.remove_labels(
strict=strict,
label_names=parse_remove_labels(remove_labels),
preview=preview,
force=force,
)

gh_label.add_labels(labels=parse_add_labels(add_labels), preview=preview)
gh_label.add_labels(labels=parse_add_labels(add_labels), preview=preview)

if gh_label.labels_unsafe_to_remove:
if not preview:
Expand All @@ -205,6 +201,11 @@ def setup_labels( # noqa: PLR0913
)
rich.print()

if not preview:
rich.print(
f"[green]Successfully[/green] setup github labels from config to repo `{repo_owner}/{repo_name}`."
)


@app.command("dump", help="Generate starter labels config files.") # type: ignore[misc]
def app_dump(
Expand Down Expand Up @@ -243,6 +244,7 @@ def app_dump(
),
] = AppChoices.app.value, # type: ignore[assignment]
) -> None:
clear_screen()
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
Expand All @@ -253,6 +255,8 @@ def app_dump(
DumpLabel.dump(labels_dir=labels_dir, new=new, ext=ext.value, app=app.value)
time.sleep(0.5)

rich.print(f"[green]Successfully[/green] dumped labels config to `{labels_dir}`.")


@app.callback() # type: ignore[misc]
def app_callback(
Expand Down
5 changes: 3 additions & 2 deletions src/ghlabel/utils/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def create_label(self, label: GithubLabel) -> tuple[GithubLabel, StatusCode]:
return res.json(), res.status_code

def update_label(self, label: GithubLabel) -> tuple[GithubLabel, StatusCode]:
url: str = f"{self.base_url}/{label['name']}"
url: str = f"{self.base_url}/labels/{label['name']}"
label["new_name"] = label.pop("name") # type: ignore[misc]
res: Response

Expand All @@ -140,7 +140,8 @@ def update_label(self, label: GithubLabel) -> tuple[GithubLabel, StatusCode]:
"The site can't be reached, `github.com` took to long to respond. Try checking the connection."
)
sys.exit()
except HTTPError:
except HTTPError as ex:
logging.exception(ex)
logging.error(
f"Failed to update label `{label['new_name']}`. Check the label format."
)
Expand Down
9 changes: 9 additions & 0 deletions src/ghlabel/utils/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import os
import platform
import subprocess
import sys
from logging.config import fileConfig
from pathlib import Path
Expand All @@ -19,3 +21,10 @@ def validate_env(env: str) -> str:
logging.error(f"{env} environment variable not set.")
sys.exit()
return _env


def clear_screen() -> None:
if platform.system() == "Windows":
subprocess.run("cls", shell=True, check=False) # noqa: S607, S602
else:
subprocess.run("clear", shell=True, check=False) # noqa: S607, S602
98 changes: 78 additions & 20 deletions src/ghlabel/utils/setup_github_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@
import rich
import yaml
from dotenv import load_dotenv
from rich.progress import Progress
from rich.prompt import Confirm

from ghlabel.utils.github_api import GithubApi
from ghlabel.utils.github_api_types import GithubIssue, GithubLabel
from ghlabel.utils.helpers import STATUS_OK, validate_env
from ghlabel.utils.helpers import (
STATUS_OK,
clear_screen,
validate_env,
)

load_dotenv()
Path("logs").mkdir(exist_ok=True)
Expand All @@ -50,6 +56,7 @@ def __init__(
self._labels: list[GithubLabel] = self._load_labels_from_config() or []
self._label_name_urls_map: dict[str, set[str]] = {}
self._labels_unsafe_to_remove: set[str] = set()
self._labels_force_remove: set[str] = set()

@property
def labels_dir(self) -> str:
Expand All @@ -61,6 +68,7 @@ def github_labels(self) -> list[GithubLabel]:

@property
def github_label_names(self) -> list[str]:
# requires index, so using list instead of set
return self._github_label_names

@property
Expand All @@ -75,10 +83,17 @@ def label_name_urls_map(self) -> dict[str, set[str]]:
def labels_unsafe_to_remove(self) -> set[str]:
return self._labels_unsafe_to_remove

@property
def labels_force_remove(self) -> set[str]:
return self._labels_force_remove

@property
def gh_api(self) -> GithubApi:
return self._gh_api

def set_labels_force_remove(self, label_names: set[str]) -> None:
return self._labels_force_remove.update(label_names)

def _fetch_formatted_github_labels(self) -> list[GithubLabel]:
github_labels, status_code = self.gh_api.list_labels()
if status_code != STATUS_OK:
Expand Down Expand Up @@ -232,40 +247,49 @@ def _load_labels_to_remove_from_config(self) -> set[str]:

return set(labels_to_remove)

def remove_all_labels(self, silent: bool = False, preview: bool = False) -> None:
def remove_all_labels(
self, silent: bool = False, preview: bool = False, force: bool = False
) -> None:
confirmation: bool = False

if silent is False and not preview:
confirmation = input(
"WARNING: This action will remove all labels in the repository.\n"
"Are you sure you want to continue? (yes/no): "
).strip().lower() in ("y", "yes")
rich.print(
"[[yellow]WARNING[/yellow]] This action will [red]remove[/red] all labels in the repository."
)
confirmation = Confirm.ask("Are you sure you want to continue?")
else:
confirmation = True

if confirmation:
self.remove_labels(labels=self.github_label_names, preview=preview)
self.remove_labels(
label_names=set(self.github_label_names), preview=preview, force=force
)

def remove_labels(
self,
labels: list[str] | None = None,
label_names: set[str] | None = None,
strict: bool = False,
preview: bool = False,
force: bool = False,
) -> None:
labels_to_remove: set[str] = set()
labels_safe_to_remove: set[str]

if strict:
labels_to_remove.update(
set(self.github_label_names)
- set([label["name"] for label in self.labels])
)

if labels:
labels_to_remove.update(labels)
if label_names:
labels_to_remove.update(label_names)

labels_safe_to_remove: set[str] = self._list_labels_safe_to_remove(
label_names=labels_to_remove
)
if not force:
labels_safe_to_remove = self._list_labels_safe_to_remove(
label_names=labels_to_remove
)
else:
labels_safe_to_remove = labels_to_remove

if preview:
rich.print(" will [red]remove[/red] the following labels:")
Expand All @@ -282,9 +306,20 @@ def remove_labels(
rich.print()
return

for label_name in labels_safe_to_remove:
if label_name in self.github_label_names:
self.gh_api.delete_label(label_name)
clear_screen()
with Progress(transient=True) as progress:
task_id = progress.add_task(
"[red]Removing...[/red]", total=len(labels_safe_to_remove)
)

for label_name in labels_safe_to_remove:
if label_name in self.github_label_names:
self.gh_api.delete_label(label_name)
progress.update(
task_id,
advance=1,
description=f"[red]Removed[/red] Label `{label_name}`",
)

def update_labels(self, labels: list[GithubLabel], preview: bool = False) -> None:
if preview and labels:
Expand All @@ -305,8 +340,20 @@ def update_labels(self, labels: list[GithubLabel], preview: bool = False) -> Non
rich.print()
return

for label in labels:
self.gh_api.update_label(label)
if preview and labels:
clear_screen()
with Progress(transient=True) as progress:
task_id = progress.add_task(
"[yellow]Updating...[/yellow]", total=len(labels)
)

for label in labels:
self.gh_api.update_label(label)
progress.update(
task_id,
advance=1,
description=f'[yellow]Updated[/yellow] Label `{label["new_name"]}`',
)

def add_labels(
self, labels: list[GithubLabel] | None = None, preview: bool = False
Expand Down Expand Up @@ -358,8 +405,19 @@ def add_labels(
self.update_labels(labels_to_update, preview=preview)
return

for label in labels_to_add:
self.gh_api.create_label(label)
clear_screen()
with Progress(transient=True) as progress:
task_id = progress.add_task(
"[cyan]Adding...[/cyan]", total=len(labels_to_add)
)

for label in labels_to_add:
self.gh_api.create_label(label)
progress.update(
task_id,
advance=1,
description=f'[cyan]Added[/cyan] Label `{label["name"]}`',
)

self.update_labels(labels_to_update, preview=preview)
logging.info("Label creation process completed.")
Expand Down

0 comments on commit 0c02755

Please sign in to comment.