Update cutekit CLI to use new cli module

This commit is contained in:
Sleepy Monax 2023-11-11 16:17:33 +01:00
parent 2c9a7c5fc6
commit 9a962e825e
4 changed files with 140 additions and 111 deletions

View file

@ -2,8 +2,7 @@ import sys
import os import os
import logging import logging
from cutekit import const, project, vt100, plugins, cmds from cutekit import const, project, vt100, plugins, cmds, cli
from cutekit.args import parse
def setupLogger(verbose: bool): def setupLogger(verbose: bool):
@ -35,7 +34,7 @@ def setupLogger(verbose: bool):
def main() -> int: def main() -> int:
try: try:
a = parse(sys.argv[1:]) a = cli.parse(sys.argv[1:])
setupLogger(a.consumeOpt("verbose", False) is True) setupLogger(a.consumeOpt("verbose", False) is True)
plugins.loadAll() plugins.loadAll()
cmds.exec(a) cmds.exec(a)

View file

@ -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] Value = Union[str, bool, int]
@ -58,3 +61,37 @@ def parse(args: list[str]) -> Args:
result.args.append(arg) result.args.append(arg)
return result 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

View file

@ -1,58 +1,29 @@
import inspect
import logging import logging
import os import os
import sys import sys
from dataclasses import dataclass
from typing import Callable, cast, Optional
from cutekit import context, shell, const, vt100, builder, graph, project from cutekit import (
from cutekit.args import Args context,
from cutekit.context import contextFor shell,
from cutekit.jexpr import Json const,
from cutekit.model import Extern vt100,
builder,
project,
cli,
model,
jexpr,
)
Callback = Callable[[Args], None]
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@dataclass @cli.command("p", "project", "Show project information")
class Cmd: def runCmd(args: cli.Args):
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):
project.chdir() 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:") props = args.consumePrefix("prop:")
componentSpec = args.consumeArg() componentSpec = args.consumeArg()
@ -69,19 +40,19 @@ def runCmd(args: Args):
shell.exec(component.outfile(), *args.args) shell.exec(component.outfile(), *args.args)
@cmd("t", "test", "Run all test targets") @cli.command("t", "test", "Run all test targets")
def testCmd(args: Args): def testCmd(args: cli.Args):
project.chdir() project.chdir()
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine)) targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine))
builder.testAll(targetSpec) builder.testAll(targetSpec)
@cmd("d", "debug", "Debug a component") @cli.command("d", "debug", "Debug a component")
def debugCmd(args: Args): def debugCmd(args: cli.Args):
project.chdir() 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:") props = args.consumePrefix("prop:")
componentSpec = args.consumeArg() componentSpec = args.consumeArg()
@ -98,11 +69,11 @@ def debugCmd(args: Args):
shell.exec("lldb", "-o", "run", component.outfile(), *args.args) shell.exec("lldb", "-o", "run", component.outfile(), *args.args)
@cmd("b", "build", "Build a component or all components") @cli.command("b", "build", "Build a component or all components")
def buildCmd(args: Args): def buildCmd(args: cli.Args):
project.chdir() 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:") props = args.consumePrefix("prop:")
componentSpec = args.consumeArg() componentSpec = args.consumeArg()
@ -112,8 +83,8 @@ def buildCmd(args: Args):
builder.build(componentSpec, targetSpec, props) builder.build(componentSpec, targetSpec, props)
@cmd("l", "list", "List all components and targets") @cli.command("l", "list", "List all components and targets")
def listCmd(args: Args): def listCmd(args: cli.Args):
project.chdir() project.chdir()
components = context.loadAllComponents() components = context.loadAllComponents()
@ -136,20 +107,20 @@ def listCmd(args: Args):
print() print()
@cmd("c", "clean", "Clean build files") @cli.command("c", "clean", "Clean build files")
def cleanCmd(args: Args): def cleanCmd(args: cli.Args):
project.chdir() project.chdir()
shell.rmrf(const.BUILD_DIR) shell.rmrf(const.BUILD_DIR)
@cmd("n", "nuke", "Clean all build files and caches") @cli.command("n", "nuke", "Clean all build files and caches")
def nukeCmd(args: Args): def nukeCmd(args: cli.Args):
project.chdir() project.chdir()
shell.rmrf(const.PROJECT_CK_DIR) shell.rmrf(const.PROJECT_CK_DIR)
@cmd("h", "help", "Show this help message") @cli.command("h", "help", "Show this help message")
def helpCmd(args: Args): def helpCmd(args: cli.Args):
usage() usage()
print() print()
@ -159,7 +130,7 @@ def helpCmd(args: Args):
print() print()
vt100.title("Commands") vt100.title("Commands")
for cmd in cmds: for cmd in cli.commands:
pluginText = "" pluginText = ""
if cmd.isPlugin: if cmd.isPlugin:
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}" pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
@ -176,26 +147,11 @@ def helpCmd(args: Args):
@cmd("v", "version", "Show current version") @cmd("v", "version", "Show current version")
def versionCmd(args: Args): def versionCmd(args: cli.Args):
print(f"CuteKit v{const.VERSION_STR}") print(f"CuteKit v{const.VERSION_STR}")
@cmd("g", "graph", "Show the dependency graph") def grabExtern(extern: dict[str, model.Extern]):
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]):
for extSpec, ext in extern.items(): for extSpec, ext in extern.items():
extPath = os.path.join(const.EXTERN_DIR, extSpec) extPath = os.path.join(const.EXTERN_DIR, extSpec)
@ -212,16 +168,16 @@ def grabExtern(extern: dict[str, Extern]):
grabExtern(context.loadProject(extPath).extern) grabExtern(context.loadProject(extPath).extern)
@cmd("i", "install", "Install required external packages") @cli.command("i", "install", "Install required external packages")
def installCmd(args: Args): def installCmd(args: cli.Args):
project.chdir() project.chdir()
pj = context.loadProject(".") pj = context.loadProject(".")
grabExtern(pj.extern) grabExtern(pj.extern)
@cmd("I", "init", "Initialize a new project") @cli.command("I", "init", "Initialize a new project")
def initCmd(args: Args): def initCmd(args: cli.Args):
import requests import requests
repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES) repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES)
@ -248,7 +204,7 @@ def initCmd(args: Args):
if not template: if not template:
raise RuntimeError("Template not specified") raise RuntimeError("Template not specified")
def template_match(t: Json) -> str: def template_match(t: jexpr.Json) -> str:
return t["id"] == template return t["id"] == template
if not any(filter(template_match, registry)): 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) print(f"{vt100.RED}Error:{vt100.RESET} {msg}\n", file=sys.stderr)
def exec(args: Args): def exec(args: cli.Args):
cmd = args.consumeArg() cmd = args.consumeArg()
if cmd is None: if cmd is None:
raise RuntimeError("No command specified") raise RuntimeError("No command specified")
for c in cmds: for c in cli.commands:
if c.shortName == cmd or c.longName == cmd: if c.shortName == cmd or c.longName == cmd:
c.callback(args) c.callback(args)
return return

View file

@ -1,19 +1,26 @@
import os import os
from typing import Optional from typing import cast
from cutekit.context import Context from . import vt100, context, project, cli, shell
from cutekit import vt100
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 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("graph", splines="ortho", rankdir="BT", ranksep="1.5")
g.attr('node', shape='ellipse') g.attr("node", shape="ellipse")
g.attr( g.attr(
'graph', label=f"<<B>{scope or 'Full Dependency Graph'}</B><BR/>{context.target.id}>", labelloc='t') "graph",
label=f"<<B>{scope or 'Full Dependency Graph'}</B><BR/>{context.target.id}>",
labelloc="t",
)
scopeInstance = None 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: if not instance.isLib() and not showExe:
continue continue
if scopeInstance is not None and \ if (
instance.manifest.id != scope and \ scopeInstance is not None
instance.manifest.id not in scopeInstance.resolved: and instance.manifest.id != scope
and instance.manifest.id not in scopeInstance.resolved
):
continue continue
if instance.enabled: if instance.enabled:
fillcolor = "lightgrey" if instance.isLib() else "lightblue" 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"<<B>{instance.manifest.id}</B><BR/>{vt100.wordwrap(instance.manifest.decription, 40,newline='<BR/>')}>", g.node(
shape=shape, style="filled", fillcolor=fillcolor) instance.manifest.id,
f"<<B>{instance.manifest.id}</B><BR/>{vt100.wordwrap(instance.manifest.decription, 40,newline='<BR/>')}>",
shape=shape,
style="filled",
fillcolor=fillcolor,
)
for req in instance.manifest.requires: for req in instance.manifest.requires:
g.edge(instance.manifest.id, req) g.edge(instance.manifest.id, req)
for req in instance.manifest.provides: for req in instance.manifest.provides:
isChosen = context.target.routing.get( isChosen = context.target.routing.get(req, None) == instance.manifest.id
req, None) == instance.manifest.id
g.edge(req, instance.manifest.id, arrowhead="none", color=( g.edge(
"blue" if isChosen else "black")) req,
instance.manifest.id,
arrowhead="none",
color=("blue" if isChosen else "black"),
)
elif showDisabled: elif showDisabled:
g.node(instance.manifest.id, f"<<B>{instance.manifest.id}</B><BR/>{vt100.wordwrap(instance.manifest.decription, 40,newline='<BR/>')}<BR/><BR/><I>{vt100.wordwrap(instance.disableReason, 40,newline='<BR/>')}</I>>", g.node(
shape="plaintext", style="filled", fontcolor="#999999", fillcolor="#eeeeee") instance.manifest.id,
f"<<B>{instance.manifest.id}</B><BR/>{vt100.wordwrap(instance.manifest.decription, 40,newline='<BR/>')}<BR/><BR/><I>{vt100.wordwrap(instance.disableReason, 40,newline='<BR/>')}</I>>",
shape="plaintext",
style="filled",
fontcolor="#999999",
fillcolor="#eeeeee",
)
for req in instance.manifest.requires: for req in instance.manifest.requires:
g.edge(instance.manifest.id, req, color="#aaaaaa") g.edge(instance.manifest.id, req, color="#aaaaaa")
for req in instance.manifest.provides: for req in instance.manifest.provides:
g.edge(req, instance.manifest.id, g.edge(req, instance.manifest.id, arrowhead="none", color="#aaaaaa")
arrowhead="none", color="#aaaaaa")
g.view(filename=os.path.join(context.builddir(), "graph.gv")) 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)