mirror of
https://github.com/bellingcat/octosuite.git
synced 2026-06-08 03:18:35 +03:00
Add lib.py, and minor cleanups in menus.py
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "octosuite"
|
||||
version = "4.0.0"
|
||||
version = "4.0.0-beta"
|
||||
description = "TUI-based toolkit for GitHub-data analysis."
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
@@ -33,4 +33,4 @@ dev = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
octosuite = "octosuite.main:start"
|
||||
octosuite = "octosuite.app:start"
|
||||
2
src/octosuite/__init__.py
Normal file
2
src/octosuite/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__pkg__ = "octosuite"
|
||||
__version__ = "4.0.0"
|
||||
@@ -1,6 +1,6 @@
|
||||
import sys
|
||||
|
||||
from . import console, __pkg__, __version__
|
||||
from .lib import console, __pkg__, __version__
|
||||
from .tui.menus import Menus
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import typing as t
|
||||
import requests
|
||||
from requests import Response
|
||||
|
||||
from .. import __version__
|
||||
from ..lib import __version__
|
||||
|
||||
BASE_URL = "https://api.github.com"
|
||||
|
||||
__all__ = ["BASE_URL", "GitHub"]
|
||||
|
||||
|
||||
class GitHub:
|
||||
def __init__(
|
||||
@@ -13,20 +13,17 @@ from rich.text import Text
|
||||
from rich.tree import Tree
|
||||
from update_checker import UpdateChecker
|
||||
|
||||
__pkg__ = "octosuite"
|
||||
__version__ = "4.0.0"
|
||||
__author__ = "Ritchie Mwewa"
|
||||
from . import __pkg__, __version__
|
||||
|
||||
__all__ = [
|
||||
"console",
|
||||
"__pkg__",
|
||||
"__author__",
|
||||
"__version__",
|
||||
"console",
|
||||
"preview_response",
|
||||
"export_response",
|
||||
"check_updates",
|
||||
"clear_screen",
|
||||
"text_banner",
|
||||
"ascii_banner",
|
||||
"set_menu_title",
|
||||
]
|
||||
|
||||
@@ -70,7 +67,13 @@ def preview_response(data: t.Union[dict, list], source: str, _type: str):
|
||||
console.print(data)
|
||||
|
||||
|
||||
def export_response(data, data_type, source, file_formats, output_dir="../exports"):
|
||||
def export_response(
|
||||
data: t.Union[dict, list],
|
||||
data_type: str,
|
||||
source: str,
|
||||
file_formats: list,
|
||||
output_dir: str = "../exports",
|
||||
):
|
||||
"""Export data to selected formats using built-in libraries."""
|
||||
# Create output directory if it doesn't exist
|
||||
output_dir = Path(output_dir)
|
||||
@@ -184,15 +187,18 @@ def fill_tree(tree: Tree, data: t.Union[dict, list]) -> Tree:
|
||||
|
||||
|
||||
def check_updates():
|
||||
checker = UpdateChecker()
|
||||
result = checker.check(__pkg__, __version__)
|
||||
if result:
|
||||
message_dialog(title="Update Available", text=result).run()
|
||||
else:
|
||||
message_dialog(
|
||||
title="Up to Date",
|
||||
text=f"You're running the current version, {__version__}",
|
||||
).run()
|
||||
with console.status("[dim]Checking for updates...[/dim]") as status:
|
||||
checker = UpdateChecker()
|
||||
result = checker.check(__pkg__, __version__)
|
||||
if result:
|
||||
status.stop()
|
||||
message_dialog(title="Update Available", text=result).run()
|
||||
else:
|
||||
status.stop()
|
||||
message_dialog(
|
||||
title="Up to Date",
|
||||
text=f"You're running the current version, {__version__}",
|
||||
).run()
|
||||
|
||||
|
||||
def clear_screen():
|
||||
@@ -200,7 +206,7 @@ def clear_screen():
|
||||
subprocess.run(["cls" if os.name == "nt" else "clear"])
|
||||
|
||||
|
||||
def text_banner(text: str):
|
||||
def ascii_banner(text: str):
|
||||
clear_screen()
|
||||
|
||||
ascii_text = pyfiglet.figlet_format(text=text, font="chunky")
|
||||
@@ -22,10 +22,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE."""
|
||||
|
||||
__all__ = ["Dialogs"]
|
||||
|
||||
|
||||
class Dialogs:
|
||||
def __init__(self):
|
||||
...
|
||||
def __init__(self): ...
|
||||
|
||||
@staticmethod
|
||||
def quit() -> bool:
|
||||
@@ -7,9 +7,9 @@ from rich.status import Status
|
||||
|
||||
from .dialogs import Dialogs
|
||||
from .prompts import Prompts
|
||||
from .. import check_updates, preview_response, export_response, set_menu_title
|
||||
from .. import console, clear_screen, text_banner
|
||||
from ..core.models import User, Repo, Org, Search
|
||||
from ..lib import check_updates, preview_response, export_response, set_menu_title
|
||||
from ..lib import console, clear_screen, ascii_banner
|
||||
|
||||
CUSTOM_STYLE = Style(
|
||||
[
|
||||
@@ -22,12 +22,11 @@ INSTRUCTIONS = "↑↓ [move] • ⮠ [select]"
|
||||
EXPORT_INSTRUCTIONS = "↑↓ [move] • ⮠ [confirm] • spacebar [check]"
|
||||
POINTER: str = "🖝 "
|
||||
|
||||
__all__ = ["Menus"]
|
||||
|
||||
|
||||
dialogs = Dialogs()
|
||||
prompts = Prompts()
|
||||
|
||||
__all__ = ["Menus"]
|
||||
|
||||
|
||||
class Menus:
|
||||
def __init__(self):
|
||||
@@ -88,7 +87,7 @@ class Menus:
|
||||
status.update(f"[dim]Getting {method_name} from {source}...[/dim]")
|
||||
return method(**params)
|
||||
|
||||
def _navigation(self, option, callback, *callback_args):
|
||||
def _navigation(self, option: str, callback: t.Callable, *callback_args):
|
||||
"""Handle navigation options (back, quit, change settings)."""
|
||||
navigation_handlers = {
|
||||
"back": lambda: self.main(),
|
||||
@@ -183,7 +182,7 @@ class Menus:
|
||||
set_menu_title(menu_type="home")
|
||||
clear_screen()
|
||||
try:
|
||||
text_banner(text="octosuite")
|
||||
ascii_banner(text="octosuite")
|
||||
|
||||
action = q.select(
|
||||
"What action would you like to perform?",
|
||||
@@ -256,7 +255,7 @@ class Menus:
|
||||
set_menu_title(menu_type="search")
|
||||
clear_screen()
|
||||
if query is None:
|
||||
text_banner(text="Search")
|
||||
ascii_banner(text="Search")
|
||||
query = prompts.prompt(
|
||||
message="Search Query",
|
||||
instruction="e.g., machine learning",
|
||||
@@ -264,7 +263,7 @@ class Menus:
|
||||
)
|
||||
|
||||
clear_screen()
|
||||
text_banner(text=query)
|
||||
ascii_banner(text=query)
|
||||
|
||||
option = q.select(
|
||||
"What would you like to do/search?",
|
||||
@@ -334,7 +333,7 @@ class Menus:
|
||||
# Execute search if it's a valid method
|
||||
if option in self.search_methods:
|
||||
with console.status(
|
||||
status=f"[dim]Initialising {option} search[/dim]..."
|
||||
status=f"[dim]Initialising {option} search...[/dim]"
|
||||
) as status:
|
||||
# Get pagination params
|
||||
|
||||
@@ -350,7 +349,7 @@ class Menus:
|
||||
)
|
||||
|
||||
method = getattr(search, option)
|
||||
status.update(f"[dim]Searching {option} for {query}[/dim]...")
|
||||
status.update(f"[dim]Searching {option} for {query}...[/dim]")
|
||||
data = method()
|
||||
|
||||
if data:
|
||||
@@ -369,7 +368,7 @@ class Menus:
|
||||
set_menu_title(menu_type="user")
|
||||
clear_screen()
|
||||
if username is None:
|
||||
text_banner(text="User")
|
||||
ascii_banner(text="User")
|
||||
username = prompts.prompt(
|
||||
message="GitHub Username",
|
||||
instruction="e.g., octocat",
|
||||
@@ -378,7 +377,7 @@ class Menus:
|
||||
)
|
||||
|
||||
clear_screen()
|
||||
text_banner(text=username)
|
||||
ascii_banner(text=username)
|
||||
|
||||
option = q.select(
|
||||
"What would you like to do/get?",
|
||||
@@ -473,12 +472,12 @@ class Menus:
|
||||
valid_methods = self.paginated_methods | self.non_paginated_methods
|
||||
if option in valid_methods:
|
||||
with Status(
|
||||
status=f"[dim]Initialising user {option}[/dim]...",
|
||||
status=f"[dim]Initialising user {option}...[/dim]",
|
||||
console=console,
|
||||
) as status:
|
||||
user = User(name=username)
|
||||
|
||||
status.update(f"[dim]Validating user's ({username}) existence[/dim]...")
|
||||
status.update(f"[dim]Validating user's ({username}) existence...[/dim]")
|
||||
if user.exists():
|
||||
console.print(
|
||||
f"[bold][green]✔[/green] User ({username}) exists on GitHub[/bold]"
|
||||
@@ -507,7 +506,7 @@ class Menus:
|
||||
set_menu_title(menu_type="repo")
|
||||
clear_screen()
|
||||
if name is None or owner is None:
|
||||
text_banner(text="Repo")
|
||||
ascii_banner(text="Repo")
|
||||
if name is None:
|
||||
name = prompts.prompt(
|
||||
message="GitHub Repo Name",
|
||||
@@ -523,7 +522,7 @@ class Menus:
|
||||
)
|
||||
|
||||
clear_screen()
|
||||
text_banner(text=f"{owner}/{name}")
|
||||
ascii_banner(text=f"{owner}/{name}")
|
||||
|
||||
option = q.select(
|
||||
"What would you like to do/get?",
|
||||
@@ -665,13 +664,13 @@ class Menus:
|
||||
if option in valid_methods:
|
||||
source = f"{owner}/{name}"
|
||||
with Status(
|
||||
status=f"[dim]Initialising repository {option}[/dim]...",
|
||||
status=f"[dim]Initialising repository {option}...[/dim]",
|
||||
console=console,
|
||||
) as status:
|
||||
repo = Repo(name=name, owner=owner)
|
||||
|
||||
status.update(
|
||||
f"[dim]Validating repository's ({source}) existence[/dim]..."
|
||||
f"[dim]Validating repository's ({source}) existence...[/dim]"
|
||||
)
|
||||
if repo.exists():
|
||||
console.print(
|
||||
@@ -698,7 +697,7 @@ class Menus:
|
||||
set_menu_title(menu_type="org")
|
||||
clear_screen()
|
||||
if name is None:
|
||||
text_banner(text="Org")
|
||||
ascii_banner(text="Org")
|
||||
name = prompts.prompt(
|
||||
message="GitHub Organisation Name",
|
||||
instruction="e.g, github",
|
||||
@@ -707,7 +706,7 @@ class Menus:
|
||||
)
|
||||
|
||||
clear_screen()
|
||||
text_banner(text=name)
|
||||
ascii_banner(text=name)
|
||||
|
||||
option = q.select(
|
||||
"What would you like to do?",
|
||||
@@ -781,13 +780,13 @@ class Menus:
|
||||
valid_methods = self.paginated_methods | self.non_paginated_methods
|
||||
if option in valid_methods:
|
||||
with Status(
|
||||
status=f"[dim]Initialising organisation {option}[/dim]...",
|
||||
status=f"[dim]Initialising organisation {option}...[/dim]",
|
||||
console=console,
|
||||
) as status:
|
||||
org = Org(name=name)
|
||||
|
||||
status.update(
|
||||
f"[dim]Validating organisation's ({name}) existence[/dim]..."
|
||||
f"[dim]Validating organisation's ({name}) existence...[/dim]"
|
||||
)
|
||||
if org.exists():
|
||||
console.print(
|
||||
@@ -3,6 +3,8 @@ import typing as t
|
||||
import questionary as q
|
||||
from questionary import Style
|
||||
|
||||
__all__ = ["Prompts"]
|
||||
|
||||
|
||||
class Prompts:
|
||||
def __init__(self):
|
||||
Reference in New Issue
Block a user