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)