This commit is contained in:
Sleepy Monax 2023-06-12 19:09:17 +02:00
parent 515893de5f
commit aae3ccd06e
5 changed files with 973 additions and 186 deletions

View file

@ -3,7 +3,7 @@ import os
import logging import logging
from cutekit import const, project, vt100, plugins, cmds from cutekit import const, project, vt100, plugins, cmds
from cutekit.args import parse
def setupLogger(verbose: bool): def setupLogger(verbose: bool):
if verbose: if verbose:
@ -31,12 +31,12 @@ def setupLogger(verbose: bool):
datefmt="%Y-%m-%d %H:%M:%S", datefmt="%Y-%m-%d %H:%M:%S",
) )
def main() -> int: def main() -> int:
try: try:
a = parse(sys.argv[1:]) setupLogger(False)
setupLogger(a.consumeOpt("verbose", False) == True)
plugins.loadAll() plugins.loadAll()
cmds.exec(a) cmds.exec(sys.argv)
print() print()
return 0 return 0
except RuntimeError as e: except RuntimeError as e:
@ -47,4 +47,4 @@ def main() -> int:
return 1 return 1
except KeyboardInterrupt: except KeyboardInterrupt:
print() print()
return 1 return 1

651
cutekit/args2.py Normal file
View file

@ -0,0 +1,651 @@
from typing import TypeVar, Generic, Optional, Callable, Any
import sys
from cutekit import vt100
T = TypeVar('T')
# --- Base ------------------------------------------------------------------- #
class Scan:
_argv: list[str]
_off: int = 0
def __init__(self, argv: list[str]):
self._argv = argv
def any(self) -> bool:
return self._off < len(self._argv)
def peek(self) -> str:
return self._argv[self._off]
def tryPeek(self, default: str = "") -> str:
if self.any():
return self.peek()
return default
def isHelp(self) -> bool:
return self.tryPeek() == "-h" or self.tryPeek() == "--help"
def next(self) -> str:
result = self._argv[self._off]
self._off += 1
return result
def consumed(self) -> list[str]:
return self._argv[:self._off]
class Emit:
_buf: str = ""
_ident: int = 0
_newline: int = 0
def __init__(self) -> None:
pass
def ident(self) -> None:
self._ident += 1
def unident(self) -> None:
self._ident -= 1
def emit(self, text: str) -> None:
for c in text:
if c == "\n":
self._newline += 1
continue
if c != "\n" and self._newline:
self.flushNewline()
self._buf += " " * self._ident
self._buf += c
def newline(self) -> None:
self._newline += 1
def flushNewline(self) -> None:
if self._newline:
self._buf += "\n" * min(2, self._newline)
self._newline = 0
def finish(self) -> str:
self._newline = min(1, self._newline)
self.flushNewline()
if self._ident:
raise RuntimeError("Unbalanced ident")
return self._buf
class Node:
def eval(self, s: Scan, args: Any) -> bool:
raise NotImplementedError()
def usage(self, e: Emit) -> None:
raise NotImplementedError()
def help(self, e: Emit) -> None:
raise NotImplementedError()
# --- Quatifiers ------------------------------------------------------------ #
class AllOf(Node):
parts: list[Node]
def __init__(self, *parts: Node):
self.parts = list(parts)
def eval(self, s: Scan, args: Any) -> bool:
if s.isHelp():
e = Emit()
self.help(e)
print(e.finish())
raise SystemExit(0)
for part in self.parts:
part.eval(s, args)
return True
def usage(self, e: Emit) -> None:
if len(self.parts) == 1:
self.parts[0].usage(e)
return
e.emit("(")
first = True
for part in self.parts:
if not first:
e.emit(" ")
first = False
part.usage(e)
e.emit(")")
def help(self, e: Emit) -> None:
for part in self.parts:
part.help(e)
class OneOf(Node):
parts: list[Node]
def __init__(self, *parts: Node, title: Optional[str] = None):
self.parts = list(parts)
def eval(self, s: Scan, args: Any) -> bool:
if s.isHelp():
e = Emit()
self.help(e)
print(e.finish())
raise SystemExit(0)
for part in self.parts:
if part.eval(s, args):
return True
e = Emit()
self.usage(e)
if not s.any():
raise RuntimeError(
f"Unexpected end of input, expected one of " + e.finish())
raise RuntimeError(
f"Unexpected {s.peek()}, expected one of " + e.finish())
def usage(self, e: Emit) -> None:
e.emit("(")
first = True
for part in self.parts:
if not first:
e.emit(" | ")
first = False
part.usage(e)
e.emit(")")
def help(self, e: Emit) -> None:
for part in self.parts:
part.help(e)
class ZeroOrMoreOf(Node):
operands: list[Node]
def __init__(self, *operands: Node):
self.operands = list(operands)
def eval(self, s: Scan, args: Any) -> bool:
if s.isHelp():
e = Emit()
self.help(e)
print(e.finish())
raise SystemExit(0)
any = True
while s.any() and any:
any = False
for part in self.operands:
if not part.eval(s, args):
break
return True
def usage(self, e: Emit) -> None:
if len(self.operands) == 1:
e.emit("[")
self.operands[0].usage(e)
e.emit("...]")
return
e.emit("[")
first = True
for part in self.operands:
if not first:
e.emit(" | ")
first = False
part.usage(e)
e.emit("]+")
def help(self, e: Emit) -> None:
for part in self.operands:
part.help(e)
class OneOrMoreOf(Node):
operands: list[Node]
def __init__(self, *operands: Node):
self.operands = list(operands)
def eval(self, s: Scan, args: Any) -> bool:
if s.isHelp():
e = Emit()
self.help(e)
print(e.finish())
raise SystemExit(0)
any = False
while s.any():
for operand in self.operands:
if operand.eval(s, args):
any = True
break
if not any:
break
if not any:
e = Emit()
self.usage(e)
if not s.any():
raise RuntimeError(
f"Unexpected end of input, expected one or more {vt100.BOLD + vt100.GREEN}{e.finish()}{vt100.RESET}")
raise RuntimeError(
f"Unexpected {vt100.BOLD + vt100.BROWN}{s.peek()}{vt100.RESET}, expected one or more {vt100.BOLD + vt100.GREEN}{e.finish()}{vt100.RESET}")
return True
def usage(self, e: Emit) -> None:
if len(self.operands) == 1:
e.emit("")
self.operands[0].usage(e)
e.emit("+")
return
e.emit("[")
first = True
for operand in self.operands:
if not first:
e.emit(" | ")
first = False
e.emit("[")
operand.usage(e)
e.emit("]")
e.emit("]+")
def help(self, e: Emit) -> None:
for operand in self.operands:
operand.help(e)
# --- Keywords, Options and Operands ---------------------------------------- #
class Keyword(Node):
name: str
shorthand: str
description: str
syntax: Optional[Node]
run: Optional[Callable[[Any], None]]
def __init__(self, name: str, shorthand: str = "", description: str = "", syntax: Optional[Node] = None, run: Optional[Callable[[Any], None]] = None):
self.name = name
self.shorthand = shorthand
self.description = description
self.syntax = syntax
self.run = run
def eval(self, s: Scan, args: Any) -> bool:
if s.any() and (s.peek() == self.name or s.peek() == self.shorthand):
s.next()
if self.syntax:
try:
self.syntax.eval(s, args)
except RuntimeError as e:
print(f"{vt100.RED}Error:{vt100.RESET} {e}")
print("Try: --help for more information")
raise SystemExit(1)
if self.run:
self.run(args)
return True
return False
def usage(self, e: Emit) -> None:
if self.shorthand:
e.emit(f'({self.shorthand} | ')
e.emit(self.name)
if self.shorthand:
e.emit(")")
if self.syntax:
e.emit(" ")
self.syntax.usage(e)
def help(self, e: Emit) -> None:
if self.shorthand:
e.emit(f" {vt100.BOLD + vt100.GREEN}{self.shorthand}{vt100.RESET} ")
else:
e.emit(" ")
e.emit(f"{self.name} -")
e.emit(f" {self.description}\n")
e.ident()
e.newline()
e.emit(f"{vt100.BOLD}Usage:{vt100.RESET}")
e.newline()
self.usage(e)
e.newline()
if self.syntax:
self.syntax.help(e)
e.unident()
e.newline()
class Option(Node, Generic[T]):
name: str
key: str
shorthand: str
description: str
default: Optional[T]
def __init__(self, name: str, key: Optional[str] = None, shorthand: str = "", description: str = "", default: Optional[T] = None):
self.name = name
self.key = key or name
self.shorthand = shorthand
self.description = description
self.default = default
def eval(self, s: Scan, args: Any) -> bool:
if not s.any():
return False
opt = s.peek()
if s.peek().startswith('--' + self.name) or s.peek().startswith('-' + self.shorthand):
s.next()
if '=' in opt:
value = opt.split('=')[1]
else:
if not s.any():
raise RuntimeError(
f"Missing value for {vt100.BOLD + vt100.BROWN}{opt}{vt100.RESET}")
value = s.next()
args[self.key] = value
return True
return False
def usage(self, e: Emit) -> None:
e.emit("(")
if self.shorthand:
e.emit(f'(-{self.shorthand} | ')
e.emit(f'--{self.name}')
if self.shorthand:
e.emit(")")
if self.default:
e.emit(f'=<{self.key}>')
e.emit(")")
def help(self, e: Emit) -> None:
if self.shorthand:
e.emit(f"-{self.shorthand}, ")
else:
e.emit(" ")
e.emit(f"--{self.name}")
e.emit(f" {self.description}")
if self.default:
e.emit(f" (default: {self.default})")
e.emit("\n")
class Operand(Node, Generic[T]):
name: str
key: str
description: str
default: Optional[T]
def __init__(self, name: str, key: Optional[str] = None, description: str = "", default: Optional[T] = None):
self.name = name
self.key = key or name
self.description = description
self.default = default
def eval(self, s: Scan, args: Any) -> bool:
if s.any() and not s.peek().startswith('-'):
args[self.key] = s.next()
return True
return False
def usage(self, e: Emit) -> None:
e.emit(f"<{self.key}>")
def help(self, e: Emit) -> None:
e.emit(f"{vt100.WHITE}<{self.name}>{vt100.RESET}")
e.emit(f" {self.description}\n")
if self.default:
e.emit(f" (default: {self.default})")
class Operands(Node, Generic[T]):
name: str
key: str
description: str
default: list[T]
def __init__(self, name: str, key: Optional[str] = None, description: str = "", default: list[T] = []):
self.name = name
self.key = key or name
self.description = description
self.default = default
def eval(self, s: Scan, args: Any) -> bool:
if self.key not in args:
args[self.key] = []
any = False
while s.any() and not s.peek().startswith('-'):
args[self.key] += [s.next()]
any = True
if not any and len(args[self.key]) == 0:
args[self.key] += self.default
return True
def usage(self, e: Emit) -> None:
e.emit(f"<{self.key}...>")
def help(self, e: Emit) -> None:
e.emit(f"{vt100.WHITE}<{self.name}>{vt100.RESET}")
e.emit(f" {self.description}\n")
if self.default:
e.emit(f" (default: {self.default})")
class Sink(Node):
name: str
key: str
description: str
def __init__(self, name: str, key: Optional[str] = None, description: str = ""):
self.name = name
self.key = key or name
self.description = description
def eval(self, s: Scan, args: Any) -> bool:
if self.key not in args:
args[self.key] = []
while s.any():
args[self.key].append(s.next())
return True
def usage(self, e: Emit) -> None:
e.emit(f"<{self.name}>...")
def help(self, e: Emit) -> None:
e.emit(f"{vt100.WHITE}<{self.name}...>{vt100.RESET}")
e.emit(f" {self.description}\n")
e.emit("\n")
# --- Help ------------------------------------------------------------------- #
class Title(Node):
title: str
def __init__(self, title: str):
self.title = title
def eval(self, s: Scan, args: Any) -> bool:
return False
def usage(self, e: Emit) -> None:
pass
def help(self, e: Emit) -> None:
e.emit(f"{vt100.BOLD}{self.title}{vt100.RESET}\n")
class Heading(Node):
title: str
def __init__(self, title: str):
self.title = title
def eval(self, s: Scan, args: Any) -> bool:
return False
def usage(self, e: Emit) -> None:
pass
def help(self, e: Emit) -> None:
e.emit(f"{vt100.BOLD}{self.title}:{vt100.RESET}\n")
class Paragraph(Node):
_text: str
def __init__(self, text: str):
self._text = text
def eval(self, s: Scan, args: Any) -> bool:
return False
def usage(self, e: Emit) -> None:
pass
def help(self, e: Emit) -> None:
e.emit(f"{self._text}\n")
class Section(Node):
_inner: Node
def __init__(self, inner: Node):
self._inner = inner
def eval(self, s: Scan, args: Any) -> bool:
return self._inner.eval(s, args)
def usage(self, e: Emit) -> None:
self._inner.usage(e)
def help(self, e: Emit) -> None:
e.newline()
self._inner.help(e)
e.newline()
def Options(*nodes: Node) -> Node:
return Section(ZeroOrMoreOf(
Heading("Options"),
Paragraph("Options can be specified in any order."),
ZeroOrMoreOf(
*nodes,
),
))
"""
cmds = None
def helpCmd(args):
global cmds
if cmds:
e = Emit()
cmds.help(e)
print(e.finish())
cmds = Section(OneOf(
Title("CuteKit"),
Paragraph(
"A build system and package manager for low-level software development"),
Section(
ZeroOrMoreOf(
Heading("Usage"),
Paragraph("ck <command> [args...]")
)
),
Section(
OneOf(
Heading("Commands"),
Keyword(
"help",
shorthand="h",
description="Show help",
syntax=AllOf(
Operand("command", description="Command to show help for"),
),
run=helpCmd,
),
Keyword(
"build",
shorthand="b",
description="Build the project",
syntax=AllOf(
Options(
Option[str](
"target",
shorthand="t",
description="Build target",
default="default",
),
),
Operands(
"components",
description="Components to build",
),
),
run=lambda args: print("build", args),
),
Keyword(
"run",
shorthand="r",
description="Run the project",
syntax=AllOf(
Options(
Option[str](
"target",
shorthand="t",
description="Build target",
default="default",
),
),
Operand("component", description="Component to run"),
Sink("args", description="Arguments to pass to the component")
),
run=lambda args: print("run", args),
),
)
)
))
try:
cmds.eval(Scan(sys.argv[1:]), {})
except RuntimeError as e:
print(f"{vt100.RED}Error:{vt100.RESET} {e}")
print("Try: --help for more information")
raise SystemExit(1)
"""

View file

@ -93,7 +93,7 @@ def gen(out: TextIO, context: Context):
writer.default("all") writer.default("all")
def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentInstance: def buildMany(componentSpec: list[str], targetSpec: str, props: Props = {}) -> list[ComponentInstance]:
context = contextFor(targetSpec, props) context = contextFor(targetSpec, props)
shell.mkdir(context.builddir()) shell.mkdir(context.builddir())
@ -102,18 +102,21 @@ def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentIn
with open(ninjaPath, "w") as f: with open(ninjaPath, "w") as f:
gen(f, context) gen(f, context)
instance = context.componentByName(componentSpec) instances = map(lambda i: context.componentByName(i), componentSpec)
if instance is None: for instance in instances:
raise RuntimeError(f"Component {componentSpec} not found") if not instance.enabled:
raise RuntimeError(
f"Component {componentSpec} is disabled: {instance.disableReason}")
if not instance.enabled: shell.exec(f"ninja", "-v", "-f", ninjaPath,
raise RuntimeError( *map(lambda i: i.outfile(), instances))
f"Component {componentSpec} is disabled: {instance.disableReason}")
shell.exec(f"ninja", "-v", "-f", ninjaPath, instance.outfile()) return [instance]
return instance
def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentInstance:
return buildMany([componentSpec], targetSpec, props)[0]
class Paths: class Paths:

View file

@ -2,212 +2,328 @@ import os
import logging import logging
import requests import requests
import sys import sys
import git
from typing import Callable, cast, Optional, NoReturn from typing import cast, Optional, Any
from cutekit import context, shell, const, vt100, builder, graph, project from cutekit import context, shell, const, vt100, builder, graph, project, args2
from cutekit.args import Args
from cutekit.jexpr import Json
from cutekit.model import Extern from cutekit.model import Extern
from cutekit.context import contextFor from cutekit.context import contextFor
Callback = Callable[[Args], None]
# === Commons ================================================================ #
TARGET_OPTION = args2.Option[str](
"target",
shorthand="t",
description="Build target",
default="host-" + shell.uname().machine
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
root: Optional[args2.Node] = None
class Cmd: cmds: list[args2.Node] = []
shortName: Optional[str]
longName: str
helpText: str
callback: Callable[[Args], NoReturn]
isPlugin: bool = False
def __init__(self, shortName: Optional[str], longName: str, helpText: str, callback: Callable[[Args], NoReturn]):
self.shortName = shortName
self.longName = longName
self.helpText = helpText
self.callback = callback
cmds: list[Cmd] = [] def append(cmd: args2.Node):
global cmds
def append(cmd: Cmd):
cmd.isPlugin = True
cmds.append(cmd) cmds.append(cmd)
cmds.sort(key=lambda c: c.shortName or c.longName)
def runCmd(args: Args): def usage():
print(f"Usage: {const.ARGV0} <command> [args...]")
def error(msg: str) -> None:
print(f"{vt100.RED}Error:{vt100.RESET} {msg}\n", file=sys.stderr)
def exec(args: list[str]):
global root, cmds
if root is None:
root = args2.Section(args2.OneOf(
args2.Title("CuteKit"),
args2.Paragraph(
"A build system and package manager for low-level software development"),
args2.Section(
args2.ZeroOrMoreOf(
args2.Heading("Usage"),
args2.Paragraph("ck <command> [args...]")
)
),
args2.Section(
args2.OneOf(
args2.Heading("Commands"),
*cmds
)
)
))
try:
root.eval(args2.Scan(args[1:]), {})
except RuntimeError as e:
print(f"{vt100.RED}Error:{vt100.RESET} {e}")
print("Try: --help for more information")
raise SystemExit(1)
# === Commands =============================================================== #
# --- Help Command ----------------------------------------------------------- #
def helpCmd(args: Any):
global root
if root:
e = args.Emit()
root.help(e)
print(e.finish())
append(
args2.Keyword(
"help",
shorthand="h",
description="Show help",
run=helpCmd,
)
)
# --- Run Command ------------------------------------------------------------ #
def runCmd(args: Any):
project.chdir() project.chdir()
component = builder.build(args['component'], args['target'])
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
componentSpec = args.consumeArg()
if componentSpec is None:
raise RuntimeError("Component not specified")
component = builder.build(componentSpec, targetSpec)
os.environ["CK_TARGET"] = component.context.target.id os.environ["CK_TARGET"] = component.context.target.id
os.environ["CK_COMPONENT"] = component.id() os.environ["CK_COMPONENT"] = component.id()
os.environ["CK_BUILDDIR"] = component.context.builddir() os.environ["CK_BUILDDIR"] = component.context.builddir()
shell.exec(component.outfile(), *args["args"])
shell.exec(component.outfile(), *args.args)
cmds += [Cmd("r", "run", "Run the target", runCmd)] append(
args2.Keyword(
"run",
shorthand="r",
description="Run the target",
run=runCmd,
syntax=args2.AllOf(
args2.Options(
TARGET_OPTION
),
args2.Operand("component", description="Component to run"),
args2.Sink("args", description="Arguments to pass to the component")
),
),
)
def testCmd(args: Args): # --- Test Command ----------------------------------------------------------- #
def testCmd(args: Any):
project.chdir() project.chdir()
builder.testAll(args['target'])
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
builder.testAll(targetSpec)
cmds += [Cmd("t", "test", "Run all test targets", testCmd)] append(
args2.Keyword(
"test",
shorthand="t",
description="Run all test",
run=testCmd,
syntax=args2.AllOf(
args2.Options(
TARGET_OPTION
)
),
),
)
def debugCmd(args: Args): # --- Debug Command ---------------------------------------------------------- #
def debugCmd(args: Any):
project.chdir() project.chdir()
component = builder.build(args['component'], args['target'])
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
componentSpec = args.consumeArg()
if componentSpec is None:
raise RuntimeError("Component not specified")
component = builder.build(componentSpec, targetSpec)
os.environ["CK_TARGET"] = component.context.target.id os.environ["CK_TARGET"] = component.context.target.id
os.environ["CK_COMPONENT"] = component.id() os.environ["CK_COMPONENT"] = component.id()
os.environ["CK_BUILDDIR"] = component.context.builddir() os.environ["CK_BUILDDIR"] = component.context.builddir()
shell.exec("lldb", "-o", "run", component.outfile(), *args["args"])
shell.exec("lldb", "-o", "run", component.outfile(), *args.args)
cmds += [Cmd("d", "debug", "Debug the target", debugCmd)] append(
args2.Keyword(
"debug",
shorthand="d",
description="Run a component in the debugger",
run=debugCmd,
syntax=args2.AllOf(
args2.Options(
TARGET_OPTION
),
args2.Operand("component", description="Component to debug"),
args2.Sink("args", description="Arguments to pass to the component")
),
),
)
def buildCmd(args: Args): # --- Build Command ---------------------------------------------------------- #
def buildCmd(args: Any):
project.chdir() project.chdir()
if len(args['components']) == 0:
targetSpec = cast(str, args.consumeOpt( builder.buildAll(args['target'])
"target", "host-" + shell.uname().machine))
componentSpec = args.consumeArg()
if componentSpec is None:
builder.buildAll(targetSpec)
else: else:
builder.build(componentSpec, targetSpec) builder.buildMany(args['components'], args['target'])
cmds += [Cmd("b", "build", "Build the target", buildCmd)] append(
args2.Keyword(
"build",
shorthand="b",
description="Build specified component or all components",
run=buildCmd,
syntax=args2.AllOf(
args2.Options(
TARGET_OPTION
),
args2.Operands(
"components", description="Components to build", default=[])
),
),
)
def listCmd(args: Args): # --- List Command ----------------------------------------------------------- #
project.chdir()
components = context.loadAllComponents() def listWithTitle(title: str, elements: list[str]):
targets = context.loadAllTargets() vt100.title(title + " (" + str(len(elements)) + ")")
if len(elements) == 0:
vt100.title("Components") print(f" (No {title.lower()} available)")
if len(components) == 0:
print(f" (No components available)")
else: else:
print(vt100.indent(vt100.wordwrap( print(vt100.indent(vt100.wordwrap(
", ".join(map(lambda m: m.id, components))))) ", ".join(elements))))
print()
vt100.title("Targets")
if len(targets) == 0:
print(f" (No targets available)")
else:
print(vt100.indent(vt100.wordwrap(", ".join(map(lambda m: m.id, targets)))))
print() print()
cmds += [Cmd("l", "list", "List the targets", listCmd)] def listCmd(args: Any):
project.chdir()
components = context.loadAllComponents()
targets = context.loadAllTargets()
listWithTitle("Components", list(map(lambda m: m.id, components)))
listWithTitle("Targets", list(map(lambda m: m.id, targets)))
def cleanCmd(args: Args): append(
args2.Keyword(
"list",
shorthand="l",
description="List all targets and components",
run=listCmd,
),
)
# --- Clean Command ---------------------------------------------------------- #
def cleanCmd(args: Any):
project.chdir() project.chdir()
shell.rmrf(const.BUILD_DIR) shell.rmrf(const.BUILD_DIR)
cmds += [Cmd("c", "clean", "Clean the build directory", cleanCmd)] append(
args2.Keyword(
"clean",
shorthand="c",
description="Clean the build directory",
run=cleanCmd,
),
)
# --- Nuke Command ---------------------------------------------------------- #
def nukeCmd(args: Args): def nukeCmd(args: Any):
project.chdir() project.chdir()
shell.rmrf(const.PROJECT_CK_DIR) shell.rmrf(const.PROJECT_CK_DIR)
cmds += [Cmd("n", "nuke", "Clean the build directory and cache", nukeCmd)] append(
args2.Keyword(
"nuke",
shorthand="n",
description="Clean the build and cache directories",
run=nukeCmd,
),
)
def helpCmd(args: Args): # --- Version Command ---------------------------------------------------------- #
usage()
print()
vt100.title("Description")
print(f" {const.DESCRIPTION}")
print()
vt100.title("Commands")
for cmd in cmds:
pluginText = ""
if cmd.isPlugin:
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
print(
f" {vt100.GREEN}{cmd.shortName or ' '}{vt100.RESET} {cmd.longName} - {cmd.helpText} {pluginText}")
print()
vt100.title("Logging")
print(f" Logs are stored in:")
print(f" - {const.PROJECT_LOG_FILE}")
print(f" - {const.GLOBAL_LOG_FILE}")
cmds += [Cmd("h", "help", "Show this help message", helpCmd)] def versionCmd(args: Any):
def versionCmd(args: Args):
print(f"CuteKit v{const.VERSION_STR}\n") print(f"CuteKit v{const.VERSION_STR}\n")
cmds += [Cmd("v", "version", "Show current version", versionCmd)] append(
args2.Keyword(
"version",
shorthand="v",
description="Show current version",
run=versionCmd,
),
)
# --- Version Command ---------------------------------------------------------- #
def graphCmd(args: Args): def graphCmd(args: Any):
project.chdir() project.chdir()
context = contextFor(args["target"])
targetSpec = cast(str, args.consumeOpt( graph.view(
"target", "host-" + shell.uname().machine)) context,
scope=args["scope"],
scope: Optional[str] = cast(Optional[str], args.tryConsumeOpt("scope")) showExe=not args["only-libs"],
onlyLibs: bool = args.consumeOpt("only-libs", False) == True showDisabled=args["show-disabled"])
showDisabled: bool = args.consumeOpt("show-disabled", False) == True
context = contextFor(targetSpec)
graph.view(context, scope=scope, showExe=not onlyLibs,
showDisabled=showDisabled)
cmds += [Cmd("g", "graph", "Show dependency graph", graphCmd)] append(
args2.Keyword(
"graph",
shorthand="g",
description="Show dependency graph",
run=graphCmd,
syntax=args2.AllOf(
args2.Options(
TARGET_OPTION,
args2.Option[str](
"scope",
shorthand="s",
description="Scope to show (default: all)",
default=None,
),
args2.Option[bool](
"only-libs",
shorthand="l",
description="Only show libraries",
default=False,
),
args2.Option[bool](
"show-disabled",
shorthand="d",
description="Show disabled components",
default=False,
)
)
),
),
)
# --- Install Command ---------------------------------------------------------- #
def grabExtern(extern: dict[str, Extern]): def grabExtern(extern: dict[str, Extern]):
@ -225,22 +341,30 @@ def grabExtern(extern: dict[str, Extern]):
grabExtern(context.loadProject(extPath).extern) grabExtern(context.loadProject(extPath).extern)
def installCmd(args: Args): def installCmd(args: Any):
project.chdir() project.chdir()
p = context.loadProject(".")
pj = context.loadProject(".") grabExtern(p.extern)
grabExtern(pj.extern)
cmds += [Cmd("i", "install", "Install all the external packages", installCmd)] append(
args2.Keyword(
"install",
shorthand="i",
description="Install external dependencies",
run=installCmd,
),
)
# --- Init Command ---------------------------------------------------------- #
def initCmd(args: Args): def initCmd(args: Any):
repo = args.consumeOpt('repo', const.DEFAULT_REPO_TEMPLATES) repo = args['repo']
list = args.consumeOpt('list') list = args['list']
template = args.consumeArg() template = args['template']
name = args.consumeArg() name = args['name']
logger.info("Fetching registry...") logger.info("Fetching registry...")
r = requests.get( r = requests.get(
@ -261,7 +385,7 @@ def initCmd(args: Args):
raise RuntimeError('Failed to fetch registry') raise RuntimeError('Failed to fetch registry')
print('\n'.join( print('\n'.join(
f"* {entry['id']} - {entry['description']}" for entry in json.loads(r.text))) f"* {entry['id']} - {entry['description']}" for entry in registry))
return return
if not template: if not template:
@ -284,26 +408,35 @@ def initCmd(args: Args):
f" {vt100.GREEN}cutekit build{vt100.BRIGHT_BLACK} # Build the project{vt100.RESET}") f" {vt100.GREEN}cutekit build{vt100.BRIGHT_BLACK} # Build the project{vt100.RESET}")
cmds += [Cmd("I", "init", "Initialize a new project", initCmd)] append(
args2.Keyword(
"init",
def usage(): shorthand="I",
print(f"Usage: {const.ARGV0} <command> [args...]") description="Initialize a new project",
run=installCmd,
syntax=args2.AllOf(
def error(msg: str) -> None: args2.Options(
print(f"{vt100.RED}Error:{vt100.RESET} {msg}\n", file=sys.stderr) args2.Option[str](
"repo",
shorthand="r",
def exec(args: Args): description="Repository to fetch templates from",
cmd = args.consumeArg() default=const.DEFAULT_REPO_TEMPLATES,
),
if cmd is None: args2.Option[bool](
raise RuntimeError("No command specified") "list",
shorthand="l",
for c in cmds: description="List available templates",
if c.shortName == cmd or c.longName == cmd: default=False,
c.callback(args) ),
return ),
args2.Operand[str](
raise RuntimeError(f"Unknown command {cmd}") "template",
description="Template to use",
),
args2.Operand[str](
"name",
description="Name of the project",
)
),
)
)

View file

@ -95,10 +95,10 @@ class Context(IContext):
self.instances = instances self.instances = instances
self.tools = tools self.tools = tools
def componentByName(self, name: str) -> Optional[ComponentInstance]: def componentByName(self, name: str) -> ComponentInstance:
result = list(filter(lambda x: x.manifest.id == name, self.instances)) result = list(filter(lambda x: x.manifest.id == name, self.instances))
if len(result) == 0: if len(result) == 0:
return None raise RuntimeError(f"Component '{name}' not found")
return result[0] return result[0]
def cincls(self) -> list[str]: def cincls(self) -> list[str]: