Update cutekit CLI to use new cli module
This commit is contained in:
		
							parent
							
								
									31ca0b19e8
								
							
						
					
					
						commit
						e77e787547
					
				
					 4 changed files with 141 additions and 112 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										128
									
								
								cutekit/cmds.py
									
										
									
									
									
								
							
							
						
						
									
										128
									
								
								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}"
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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…
	
	Add table
		
		Reference in a new issue