From 9a962e825e180dcaa5baf5193b30c56a1cbccbeb Mon Sep 17 00:00:00 2001 From: VAN BOSSUYT Nicolas Date: Sat, 11 Nov 2023 16:17:33 +0100 Subject: [PATCH] Update cutekit CLI to use new cli module --- cutekit/__init__.py | 5 +- cutekit/{args.py => cli.py} | 39 ++++++++++- cutekit/cmds.py | 126 ++++++++++++------------------------ cutekit/graph.py | 81 ++++++++++++++++------- 4 files changed, 140 insertions(+), 111 deletions(-) rename cutekit/{args.py => cli.py} (65%) diff --git a/cutekit/__init__.py b/cutekit/__init__.py index 535808a..e787119 100644 --- a/cutekit/__init__.py +++ b/cutekit/__init__.py @@ -2,8 +2,7 @@ import sys import os import logging -from cutekit import const, project, vt100, plugins, cmds -from cutekit.args import parse +from cutekit import const, project, vt100, plugins, cmds, cli def setupLogger(verbose: bool): @@ -35,7 +34,7 @@ def setupLogger(verbose: bool): def main() -> int: try: - a = parse(sys.argv[1:]) + a = cli.parse(sys.argv[1:]) setupLogger(a.consumeOpt("verbose", False) is True) plugins.loadAll() cmds.exec(a) diff --git a/cutekit/args.py b/cutekit/cli.py similarity index 65% rename from cutekit/args.py rename to cutekit/cli.py index 60176c1..17b6e79 100644 --- a/cutekit/args.py +++ b/cutekit/cli.py @@ -1,4 +1,7 @@ -from typing import Optional, Union +import inspect + +from typing import Optional, Union, Callable +from dataclasses import dataclass Value = Union[str, bool, int] @@ -58,3 +61,37 @@ def parse(args: list[str]) -> Args: result.args.append(arg) return result + + +Callback = Callable[[Args], None] + + +@dataclass +class Command: + shortName: str + longName: str + helpText: str + isPlugin: bool + callback: Callback + + +commands: list[Command] = [] + + +def append(command: Command): + command.isPlugin = True + commands.append(command) + commands.sort(key=lambda c: c.shortName or c.longName) + + +def command(shortName: str, longName: str, helpText: str): + curframe = inspect.currentframe() + calframe = inspect.getouterframes(curframe, 2) + + def wrap(fn: Callable[[Args], None]): + commands.append( + Command(shortName, longName, helpText, calframe[1].filename != __file__, fn) + ) + return fn + + return wrap diff --git a/cutekit/cmds.py b/cutekit/cmds.py index dca6511..9c09d2b 100644 --- a/cutekit/cmds.py +++ b/cutekit/cmds.py @@ -1,58 +1,29 @@ -import inspect import logging import os import sys -from dataclasses import dataclass -from typing import Callable, cast, Optional -from cutekit import context, shell, const, vt100, builder, graph, project -from cutekit.args import Args -from cutekit.context import contextFor -from cutekit.jexpr import Json -from cutekit.model import Extern +from cutekit import ( + context, + shell, + const, + vt100, + builder, + project, + cli, + model, + jexpr, +) -Callback = Callable[[Args], None] _logger = logging.getLogger(__name__) -@dataclass -class Cmd: - shortName: str - longName: str - helpText: str - isPlugin: bool - callback: Callback - - -cmds: list[Cmd] = [] - - -def append(cmd: Cmd): - cmd.isPlugin = True - cmds.append(cmd) - cmds.sort(key=lambda c: c.shortName or c.longName) - - -def cmd(shortName: str, longName: str, helpText: str): - curframe = inspect.currentframe() - calframe = inspect.getouterframes(curframe, 2) - - def wrap(fn: Callable[[Args], None]): - cmds.append( - Cmd(shortName, longName, helpText, calframe[1].filename != __file__, fn) - ) - return fn - - return wrap - - -@cmd("p", "project", "Show project information") -def runCmd(args: Args): +@cli.command("p", "project", "Show project information") +def runCmd(args: cli.Args): project.chdir() - targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) + targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) props = args.consumePrefix("prop:") componentSpec = args.consumeArg() @@ -69,19 +40,19 @@ def runCmd(args: Args): shell.exec(component.outfile(), *args.args) -@cmd("t", "test", "Run all test targets") -def testCmd(args: Args): +@cli.command("t", "test", "Run all test targets") +def testCmd(args: cli.Args): project.chdir() - targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) + targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) builder.testAll(targetSpec) -@cmd("d", "debug", "Debug a component") -def debugCmd(args: Args): +@cli.command("d", "debug", "Debug a component") +def debugCmd(args: cli.Args): project.chdir() - targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) + targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) props = args.consumePrefix("prop:") componentSpec = args.consumeArg() @@ -98,11 +69,11 @@ def debugCmd(args: Args): shell.exec("lldb", "-o", "run", component.outfile(), *args.args) -@cmd("b", "build", "Build a component or all components") -def buildCmd(args: Args): +@cli.command("b", "build", "Build a component or all components") +def buildCmd(args: cli.Args): project.chdir() - targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) + targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) props = args.consumePrefix("prop:") componentSpec = args.consumeArg() @@ -112,8 +83,8 @@ def buildCmd(args: Args): builder.build(componentSpec, targetSpec, props) -@cmd("l", "list", "List all components and targets") -def listCmd(args: Args): +@cli.command("l", "list", "List all components and targets") +def listCmd(args: cli.Args): project.chdir() components = context.loadAllComponents() @@ -136,20 +107,20 @@ def listCmd(args: Args): print() -@cmd("c", "clean", "Clean build files") -def cleanCmd(args: Args): +@cli.command("c", "clean", "Clean build files") +def cleanCmd(args: cli.Args): project.chdir() shell.rmrf(const.BUILD_DIR) -@cmd("n", "nuke", "Clean all build files and caches") -def nukeCmd(args: Args): +@cli.command("n", "nuke", "Clean all build files and caches") +def nukeCmd(args: cli.Args): project.chdir() shell.rmrf(const.PROJECT_CK_DIR) -@cmd("h", "help", "Show this help message") -def helpCmd(args: Args): +@cli.command("h", "help", "Show this help message") +def helpCmd(args: cli.Args): usage() print() @@ -159,7 +130,7 @@ def helpCmd(args: Args): print() vt100.title("Commands") - for cmd in cmds: + for cmd in cli.commands: pluginText = "" if cmd.isPlugin: pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}" @@ -176,26 +147,11 @@ def helpCmd(args: Args): @cmd("v", "version", "Show current version") -def versionCmd(args: Args): +def versionCmd(args: cli.Args): print(f"CuteKit v{const.VERSION_STR}") -@cmd("g", "graph", "Show the dependency graph") -def graphCmd(args: Args): - project.chdir() - - targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) - - scope: Optional[str] = cast(Optional[str], args.tryConsumeOpt("scope")) - onlyLibs: bool = args.consumeOpt("only-libs", False) is True - showDisabled: bool = args.consumeOpt("show-disabled", False) is True - - context = contextFor(targetSpec) - - graph.view(context, scope=scope, showExe=not onlyLibs, showDisabled=showDisabled) - - -def grabExtern(extern: dict[str, Extern]): +def grabExtern(extern: dict[str, model.Extern]): for extSpec, ext in extern.items(): extPath = os.path.join(const.EXTERN_DIR, extSpec) @@ -212,16 +168,16 @@ def grabExtern(extern: dict[str, Extern]): grabExtern(context.loadProject(extPath).extern) -@cmd("i", "install", "Install required external packages") -def installCmd(args: Args): +@cli.command("i", "install", "Install required external packages") +def installCmd(args: cli.Args): project.chdir() pj = context.loadProject(".") grabExtern(pj.extern) -@cmd("I", "init", "Initialize a new project") -def initCmd(args: Args): +@cli.command("I", "init", "Initialize a new project") +def initCmd(args: cli.Args): import requests repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES) @@ -248,7 +204,7 @@ def initCmd(args: Args): if not template: raise RuntimeError("Template not specified") - def template_match(t: Json) -> str: + def template_match(t: jexpr.Json) -> str: return t["id"] == template if not any(filter(template_match, registry)): @@ -283,13 +239,13 @@ def error(msg: str) -> None: print(f"{vt100.RED}Error:{vt100.RESET} {msg}\n", file=sys.stderr) -def exec(args: Args): +def exec(args: cli.Args): cmd = args.consumeArg() if cmd is None: raise RuntimeError("No command specified") - for c in cmds: + for c in cli.commands: if c.shortName == cmd or c.longName == cmd: c.callback(args) return diff --git a/cutekit/graph.py b/cutekit/graph.py index 6fc380d..6f64eec 100644 --- a/cutekit/graph.py +++ b/cutekit/graph.py @@ -1,19 +1,26 @@ import os -from typing import Optional -from cutekit.context import Context -from cutekit import vt100 +from typing import cast +from . import vt100, context, project, cli, shell -def view(context: Context, scope: Optional[str] = None, showExe: bool = True, showDisabled: bool = False): +def view( + context: context.Context, + scope: str | None = None, + showExe: bool = True, + showDisabled: bool = False, +): from graphviz import Digraph - g = Digraph(context.target.id, filename='graph.gv') + g = Digraph(context.target.id, filename="graph.gv") - g.attr('graph', splines='ortho', rankdir='BT', ranksep='1.5') - g.attr('node', shape='ellipse') + g.attr("graph", splines="ortho", rankdir="BT", ranksep="1.5") + g.attr("node", shape="ellipse") g.attr( - 'graph', label=f"<{scope or 'Full Dependency Graph'}
{context.target.id}>", labelloc='t') + "graph", + label=f"<{scope or 'Full Dependency Graph'}
{context.target.id}>", + labelloc="t", + ) scopeInstance = None @@ -24,36 +31,66 @@ def view(context: Context, scope: Optional[str] = None, showExe: bool = True, sh if not instance.isLib() and not showExe: continue - if scopeInstance is not None and \ - instance.manifest.id != scope and \ - instance.manifest.id not in scopeInstance.resolved: + if ( + scopeInstance is not None + and instance.manifest.id != scope + and instance.manifest.id not in scopeInstance.resolved + ): continue if instance.enabled: fillcolor = "lightgrey" if instance.isLib() else "lightblue" - shape = "plaintext" if not scope == instance.manifest.id else 'box' + shape = "plaintext" if not scope == instance.manifest.id else "box" - g.node(instance.manifest.id, f"<{instance.manifest.id}
{vt100.wordwrap(instance.manifest.decription, 40,newline='
')}>", - shape=shape, style="filled", fillcolor=fillcolor) + g.node( + instance.manifest.id, + f"<{instance.manifest.id}
{vt100.wordwrap(instance.manifest.decription, 40,newline='
')}>", + shape=shape, + style="filled", + fillcolor=fillcolor, + ) for req in instance.manifest.requires: g.edge(instance.manifest.id, req) for req in instance.manifest.provides: - isChosen = context.target.routing.get( - req, None) == instance.manifest.id + isChosen = context.target.routing.get(req, None) == instance.manifest.id - g.edge(req, instance.manifest.id, arrowhead="none", color=( - "blue" if isChosen else "black")) + g.edge( + req, + instance.manifest.id, + arrowhead="none", + color=("blue" if isChosen else "black"), + ) elif showDisabled: - g.node(instance.manifest.id, f"<{instance.manifest.id}
{vt100.wordwrap(instance.manifest.decription, 40,newline='
')}

{vt100.wordwrap(instance.disableReason, 40,newline='
')}
>", - shape="plaintext", style="filled", fontcolor="#999999", fillcolor="#eeeeee") + g.node( + instance.manifest.id, + f"<{instance.manifest.id}
{vt100.wordwrap(instance.manifest.decription, 40,newline='
')}

{vt100.wordwrap(instance.disableReason, 40,newline='
')}
>", + shape="plaintext", + style="filled", + fontcolor="#999999", + fillcolor="#eeeeee", + ) for req in instance.manifest.requires: g.edge(instance.manifest.id, req, color="#aaaaaa") for req in instance.manifest.provides: - g.edge(req, instance.manifest.id, - arrowhead="none", color="#aaaaaa") + g.edge(req, instance.manifest.id, arrowhead="none", color="#aaaaaa") g.view(filename=os.path.join(context.builddir(), "graph.gv")) + + +@cli.command("g", "graph", "Show the dependency graph") +def graphCmd(args: cli.Args): + project.chdir() + + targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) + + scope: str | None = cast(str | None, args.tryConsumeOpt("scope")) + onlyLibs: bool = args.consumeOpt("only-libs", False) is True + showDisabled: bool = args.consumeOpt("show-disabled", False) is True + + ctx = context.contextFor(targetSpec) + + view(ctx, scope=scope, showExe=not onlyLibs, showDisabled=showDisabled)