"""Visualisation options."""
import pathlib
import rich.box
import rich.console
import rich.table
import rich.tree
from gitconductor.gitlab import GitlabGroup, GitlabProject
CODE_TO_ACCESS = {
0: "No Access",
10: "Guest",
20: "Reporter",
30: "Developer",
40: "Maintainer",
50: "Owner",
}
CODE_TO_COLOUR = {
0: "white",
10: "red",
20: "bright_magenta",
30: "orange1",
40: "green",
50: "blue",
}
[docs]
def build_tree(group: GitlabGroup, tree: rich.tree.Tree) -> rich.tree.Tree:
"""Iteratively build the tree.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
tree (rich.tree.Tree): Initial Tree instance.
Returns:
rich.tree.Tree: Updated Tree instance.
"""
for project in group.projects:
branch = tree.add(project.name)
for grp in group.subgroups:
branch = tree.add(grp.name)
build_tree(grp, branch)
return tree
[docs]
def build_table(
group: GitlabGroup, rows: None | list[str] = None, depth: int = 0, maxdepth: int | None = None
) -> list[str]:
"""Iteratively build the table.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
rows (None, list): Previous table rows.
depth (int): Depth inside the tree.
maxdepth (int): Maximum recursion depth (0=PWD).
Returns:
list: New row to print to table.
"""
if rows is None:
rows = []
if maxdepth is None or depth <= maxdepth:
for project in group.projects:
rows.extend(project.rows)
for grp in group.subgroups:
rows = build_table(grp, rows, depth=depth + 1, maxdepth=maxdepth)
return rows
[docs]
def build_access(
group: GitlabGroup | GitlabProject,
rows: None | list[str] = None,
depth: int = 0,
unique_ids: list[str] | None = None,
explicit: bool = False,
root: pathlib.Path = pathlib.Path(),
maxdepth: int | None = None,
colour_only: bool = False,
) -> list[str]:
"""Iteratively build access lists.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
rows (None, list): Previous table rows.
depth (int): Depth inside the tree.
unique_ids (list): List of all unique IDs printed
explicit (bool): Explicitly show all members of all groups/projects?
root (pathlib.Path): Top level directory.
maxdepth (int): Maximum recursion depth (0=PWD).
colour_only (bool): Only return the colour code for the access level.
Returns:
list: New row to print to table.
"""
if rows is None:
rows = []
if unique_ids is None:
unique_ids = []
members = group.members
if members:
members = [member for member in members if member.id not in unique_ids or explicit]
for i, member in enumerate(members):
rows.append(
[
str(group.path.relative_to(root.parent)) if (not i or colour_only) else "",
member.name,
f"[{CODE_TO_COLOUR[member.access_level]}]{CODE_TO_ACCESS[member.access_level]}"
if not colour_only
else f"[{CODE_TO_COLOUR[member.access_level]}]",
member.public_email,
member.expires_at,
]
)
unique_ids.append(member.id)
if isinstance(group, GitlabGroup) and (maxdepth is None or depth < maxdepth):
for project in group.projects:
rows = build_access(
project,
rows,
depth=depth + 1,
unique_ids=unique_ids,
explicit=explicit,
maxdepth=maxdepth,
root=root,
colour_only=colour_only,
)
for grp in group.subgroups:
rows = build_access(
grp,
rows,
depth=depth + 1,
unique_ids=unique_ids,
explicit=explicit,
maxdepth=maxdepth,
root=root,
colour_only=colour_only,
)
return rows
[docs]
def tree(group: GitlabGroup) -> None:
"""Make a tree visualisation.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
Returns:
None
"""
tree = rich.tree.Tree(group.name)
tree = build_tree(group, tree)
console = rich.console.Console()
console.print(tree, crop=True)
[docs]
def table(group: GitlabGroup, maxdepth: int | None = None) -> None:
"""Make a table visualisation.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
maxdepth (int): Maximum recursion depth (0=PWD).
Returns:
None
"""
rows = build_table(group, maxdepth=maxdepth)
table = rich.table.Table()
[table.add_column(c, "bold cyan") for c in ["Name", "Tree", "Branch", "Path", "Remote"]]
for row in rows:
table.add_row(*row)
console = rich.console.Console()
console.print(table, crop=True)
[docs]
def access(group: GitlabGroup, explicit: bool = False, maxdepth: int | None = None) -> None:
"""Make an access visualisation.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
explicit (bool): Explicitly show all members of all groups/projects?
maxdepth (int): Maximum recursion depth (0=PWD).
Returns:
None
"""
rows = build_access(group, explicit=explicit, maxdepth=maxdepth, root=group.path)
table = rich.table.Table()
[table.add_column(c) for c in ["Group/Project", "User", "Access Level", "Public Email", "Expiry"]]
for row in rows:
table.add_row(*row)
console = rich.console.Console()
console.print(table, crop=True)
[docs]
def access_matrix(group: GitlabGroup, maxdepth: int | None = None) -> None:
"""Make an access matrix visualisation.
Args:
group (gitlab.GitlabGroup): GitLab group instance.
maxdepth (int): Maximum recursion depth (0=PWD).
Returns:
None
"""
rows = build_access(group, explicit=True, maxdepth=maxdepth, root=group.path, colour_only=True)
entries = {r[0] for r in rows if r[0]}
users = {r[1] for r in rows if r[1]}
table = rich.table.Table(show_lines=True, box=rich.box.SQUARE)
table.add_column("Group/Project", style="bold")
[table.add_column(c) for c in sorted(users)]
for entry in sorted(entries):
row = [
entry,
]
for user in sorted(users):
access = next((r[2] for r in rows if r[0] == entry and r[1] == user), "white")
block = f"[on {access[1:-1]}]{' ' * len(user)}"
row.append(block)
table.add_row(*row)
console = rich.console.Console()
console.print(table, crop=True)
table = rich.table.Table()
table.add_column("Access Level", style="bold")
for code, access in CODE_TO_ACCESS.items():
block = f"[on {CODE_TO_COLOUR[code]}]{' ' * 10}"
table.add_row(f"{access:<10} {block}")
console.print(table, crop=True)