Update cutekit CLI to use new cli module

This commit is contained in:
Sleepy Monax 2023-11-11 16:17:33 +01:00
parent 31ca0b19e8
commit e77e787547
4 changed files with 141 additions and 112 deletions

View file

@ -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)

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]
@ -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

View file

@ -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}"
@ -175,27 +146,12 @@ def helpCmd(args: Args):
print(f" - {const.GLOBAL_LOG_FILE}")
@cmd("v", "version", "Show current version")
def versionCmd(args: Args):
@cli.command("v", "version", "Show current version")
def versionCmd(args: cli.Args):
print(f"CuteKit v{const.VERSION_STR}\n")
@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

View file

@ -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"<<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
@ -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"<<B>{instance.manifest.id}</B><BR/>{vt100.wordwrap(instance.manifest.decription, 40,newline='<BR/>')}>",
shape=shape, style="filled", fillcolor=fillcolor)
g.node(
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:
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"<<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")
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>>",
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)