Update cutekit CLI to use new cli module
This commit is contained in:
parent
2c9a7c5fc6
commit
9a962e825e
|
@ -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)
|
||||
|
|
|
@ -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
|
126
cutekit/cmds.py
126
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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue