diff --git a/pyproject.toml b/pyproject.toml index afa8564..e6608cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "requests>=2.31.0", "rich>=14.0.0", "dynaconf>=3.2.11", + "prettytable>=3.16.0", ] readme = "README.md" requires-python = ">= 3.11.5" diff --git a/src/github_rest_cli/api.py b/src/github_rest_cli/api.py index 893eaad..6849cc2 100644 --- a/src/github_rest_cli/api.py +++ b/src/github_rest_cli/api.py @@ -1,6 +1,6 @@ import requests from github_rest_cli.globals import GITHUB_URL, get_headers -from github_rest_cli.utils import rich_output, rprint +from github_rest_cli.utils import rich_output, CliOutput def request_with_handling( @@ -50,7 +50,7 @@ def fetch_user() -> str: return None -def get_repository(name: str, org: str = None): +def get_repository(name: str, org: str = None, output_format: str = "table"): owner = org if org else fetch_user() headers = get_headers() url = build_url("repos", owner, name) @@ -65,10 +65,40 @@ def get_repository(name: str, org: str = None): }, ) - if response: - data = response.json() - rprint(data) - return None + if not response: + return None + + output = CliOutput(response.json()) + + if output_format == "json": + return output.get_json_output() + + return output.get_json_output() + + +def list_repositories(page: int, property: str, role: str, output_format: str): + headers = get_headers() + url = build_url("user", "repos") + + params = {"per_page": page, "sort": property, "type": role} + + response = request_with_handling( + "GET", + url, + params=params, + headers=headers, + error_msg={401: "Unauthorized access. Please check your token or credentials."}, + ) + + if not response: + return None + + output = CliOutput(response.json()) + + if output_format == "json": + return output.json_format() + + return output.default_format() def create_repository(name: str, visibility: str, org: str = None, empty: bool = False): @@ -118,28 +148,6 @@ def delete_repository(name: str, org: str = None): ) -def list_repositories(page: int, property: str, role: str): - headers = get_headers() - url = build_url("user", "repos") - - params = {"per_page": page, "sort": property, "type": role} - - response = request_with_handling( - "GET", - url, - params=params, - headers=headers, - error_msg={401: "Unauthorized access. Please check your token or credentials."}, - ) - - if response: - data = response.json() - repo_full_name = [repo["full_name"] for repo in data] - for repos in repo_full_name: - rich_output(f"- {repos}") - rich_output(f"\nTotal repositories: {len(repo_full_name)}") - - def dependabot_security(name: str, enabled: bool, org: str = None): is_enabled = bool(enabled) diff --git a/src/github_rest_cli/main.py b/src/github_rest_cli/main.py index 4e5a4fb..bd08f15 100644 --- a/src/github_rest_cli/main.py +++ b/src/github_rest_cli/main.py @@ -29,7 +29,7 @@ def cli(): subparsers = parser.add_subparsers(help="GitHub REST API commands", dest="command") - # Subparser for "get-repository" function + # Subparser for "get-repo" function get_repo_parser = subparsers.add_parser( "get-repo", help="Get a repository's details" ) @@ -43,9 +43,17 @@ def cli(): get_repo_parser.add_argument( "-o", "--org", help="The organization name", required=False, dest="org" ) + get_repo_parser.add_argument( + "-f", + "--format", + required=False, + default="table", + dest="format", + help="Format to display the repository in", + ) get_repo_parser.set_defaults(func=get_repository) - # Subparser for "list-repository" function + # Subparser for "list-repo" function list_repo_parser = subparsers.add_parser( "list-repo", help="List your repositories", @@ -61,7 +69,7 @@ def cli(): "-p", "--page", required=False, - default=50, + default=20, type=int, dest="page", help="The number of results", @@ -74,6 +82,14 @@ def cli(): dest="sort", help="List repositories sorted by", ) + list_repo_parser.add_argument( + "-f", + "--format", + required=False, + default="table", + dest="format", + help="Format to display the list of repositories in", + ) list_repo_parser.set_defaults(func=list_repositories) # Subparser for "create-repository" function @@ -201,9 +217,11 @@ def cli(): if hasattr(args, "func"): if command == "get-repo": - args.func(args.name, args.org) + repo = args.func(args.name, args.org, args.format) + print(repo) # noqa: T201 elif command == "list-repo": - args.func(args.page, args.sort, args.role) + repos = args.func(args.page, args.sort, args.role, args.format) + print(repos) # noqa: T201 elif command == "create-repo": args.func(args.name, args.visibility, args.org, args.empty) elif command == "delete-repo": diff --git a/src/github_rest_cli/utils.py b/src/github_rest_cli/utils.py index fc23643..e37d392 100644 --- a/src/github_rest_cli/utils.py +++ b/src/github_rest_cli/utils.py @@ -1,5 +1,75 @@ from rich import print as rprint +import json + + +class CliFormatOutput: + def to_json(self, data): + return json.dumps(data, indent=2) + + def to_table(self, fields, data): + from prettytable import PrettyTable + + table = PrettyTable() + table.title = "GitHub Repositories" + table.header_style = "upper" + + if isinstance(fields, list): + table.field_names = fields + + # Align all columns to left. + table.align = "l" + + if isinstance(data, list): + for row in data: + table.add_row(row) + + return table + + +class CliOutput: + # What's the role of `data` here? + # `data` is a parameter of the __init__ method, provided when creating an instance. + # When you create an instance of the CliOutput class, + # `self.data` stores the value of the `data` parameter as an instance attribute. + def __init__(self, data): + self.data = data + self.formatter = CliFormatOutput() + + def json_format(self): + repos = { + "repositories": [ + { + "name": f.get("name"), + "owner": f.get("owner", {}).get("login"), + "url": f.get("html_url"), + "visibility": f.get("visibility"), + } + for f in self.data + ], + } + return self.formatter.to_json(repos) + + def table_format(self): + fields = ["name", "owner", "url", "visibility"] + values = [] + for repo in self.data: + values.append( + [ + repo.get("name"), + repo.get("owner", {}).get("login"), + repo.get("html_url"), + repo.get("visibility"), + ] + ) + + return self.formatter.to_table(fields, values) + + def get_json_output(self): + return self.formatter.to_json(self.data) + + def default_format(self): + return self.table_format() def rich_output(message: str, format_str: str = "bold green"): - rprint(f"[{format_str}]{message}[/{format_str}]") + return rprint(f"[{format_str}]{message}[/{format_str}]") diff --git a/uv.lock b/uv.lock index d01967a..79aab74 100644 --- a/uv.lock +++ b/uv.lock @@ -88,6 +88,7 @@ version = "1.0.3" source = { editable = "." } dependencies = [ { name = "dynaconf" }, + { name = "prettytable" }, { name = "requests" }, { name = "rich" }, ] @@ -102,6 +103,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "dynaconf", specifier = ">=3.2.11" }, + { name = "prettytable", specifier = ">=3.16.0" }, { name = "requests", specifier = ">=2.31.0" }, { name = "rich", specifier = ">=14.0.0" }, ] @@ -170,6 +172,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prettytable" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/85e18ac92afd08c533603e3393977b6bc1443043115a47bb094f3b98f94f/prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57", size = 66276, upload-time = "2025-03-24T19:39:04.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl", hash = "sha256:b5eccfabb82222f5aa46b798ff02a8452cf530a352c31bddfa29be41242863aa", size = 33863, upload-time = "2025-03-24T19:39:02.359Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -291,3 +305,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599 wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +]