wip
This commit is contained in:
parent
515893de5f
commit
aae3ccd06e
|
@ -3,7 +3,7 @@ import os
|
|||
import logging
|
||||
|
||||
from cutekit import const, project, vt100, plugins, cmds
|
||||
from cutekit.args import parse
|
||||
|
||||
|
||||
def setupLogger(verbose: bool):
|
||||
if verbose:
|
||||
|
@ -31,12 +31,12 @@ def setupLogger(verbose: bool):
|
|||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
a = parse(sys.argv[1:])
|
||||
setupLogger(a.consumeOpt("verbose", False) == True)
|
||||
setupLogger(False)
|
||||
plugins.loadAll()
|
||||
cmds.exec(a)
|
||||
cmds.exec(sys.argv)
|
||||
print()
|
||||
return 0
|
||||
except RuntimeError as e:
|
||||
|
@ -47,4 +47,4 @@ def main() -> int:
|
|||
return 1
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
return 1
|
||||
return 1
|
||||
|
|
651
cutekit/args2.py
Normal file
651
cutekit/args2.py
Normal 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)
|
||||
"""
|
|
@ -93,7 +93,7 @@ def gen(out: TextIO, context: Context):
|
|||
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)
|
||||
|
||||
shell.mkdir(context.builddir())
|
||||
|
@ -102,18 +102,21 @@ def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentIn
|
|||
with open(ninjaPath, "w") as f:
|
||||
gen(f, context)
|
||||
|
||||
instance = context.componentByName(componentSpec)
|
||||
instances = map(lambda i: context.componentByName(i), componentSpec)
|
||||
|
||||
if instance is None:
|
||||
raise RuntimeError(f"Component {componentSpec} not found")
|
||||
for instance in instances:
|
||||
if not instance.enabled:
|
||||
raise RuntimeError(
|
||||
f"Component {componentSpec} is disabled: {instance.disableReason}")
|
||||
|
||||
if not instance.enabled:
|
||||
raise RuntimeError(
|
||||
f"Component {componentSpec} is disabled: {instance.disableReason}")
|
||||
shell.exec(f"ninja", "-v", "-f", ninjaPath,
|
||||
*map(lambda i: i.outfile(), instances))
|
||||
|
||||
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:
|
||||
|
|
473
cutekit/cmds.py
473
cutekit/cmds.py
|
@ -2,212 +2,328 @@ import os
|
|||
import logging
|
||||
import requests
|
||||
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.args import Args
|
||||
from cutekit.jexpr import Json
|
||||
from cutekit import context, shell, const, vt100, builder, graph, project, args2
|
||||
from cutekit.model import Extern
|
||||
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__)
|
||||
|
||||
|
||||
class Cmd:
|
||||
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
|
||||
root: Optional[args2.Node] = None
|
||||
cmds: list[args2.Node] = []
|
||||
|
||||
|
||||
cmds: list[Cmd] = []
|
||||
|
||||
|
||||
def append(cmd: Cmd):
|
||||
cmd.isPlugin = True
|
||||
def append(cmd: args2.Node):
|
||||
global cmds
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
component = builder.build(args['component'], args['target'])
|
||||
os.environ["CK_TARGET"] = component.context.target.id
|
||||
os.environ["CK_COMPONENT"] = component.id()
|
||||
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()
|
||||
|
||||
targetSpec = cast(str, args.consumeOpt(
|
||||
"target", "host-" + shell.uname().machine))
|
||||
builder.testAll(targetSpec)
|
||||
builder.testAll(args['target'])
|
||||
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
component = builder.build(args['component'], args['target'])
|
||||
os.environ["CK_TARGET"] = component.context.target.id
|
||||
os.environ["CK_COMPONENT"] = component.id()
|
||||
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()
|
||||
|
||||
targetSpec = cast(str, args.consumeOpt(
|
||||
"target", "host-" + shell.uname().machine))
|
||||
|
||||
componentSpec = args.consumeArg()
|
||||
|
||||
if componentSpec is None:
|
||||
builder.buildAll(targetSpec)
|
||||
if len(args['components']) == 0:
|
||||
builder.buildAll(args['target'])
|
||||
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):
|
||||
project.chdir()
|
||||
# --- List Command ----------------------------------------------------------- #
|
||||
|
||||
components = context.loadAllComponents()
|
||||
targets = context.loadAllTargets()
|
||||
|
||||
vt100.title("Components")
|
||||
if len(components) == 0:
|
||||
print(f" (No components available)")
|
||||
def listWithTitle(title: str, elements: list[str]):
|
||||
vt100.title(title + " (" + str(len(elements)) + ")")
|
||||
if len(elements) == 0:
|
||||
print(f" (No {title.lower()} available)")
|
||||
else:
|
||||
print(vt100.indent(vt100.wordwrap(
|
||||
", ".join(map(lambda m: m.id, components)))))
|
||||
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)))))
|
||||
|
||||
", ".join(elements))))
|
||||
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()
|
||||
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()
|
||||
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):
|
||||
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}")
|
||||
# --- Version Command ---------------------------------------------------------- #
|
||||
|
||||
|
||||
cmds += [Cmd("h", "help", "Show this help message", helpCmd)]
|
||||
|
||||
|
||||
def versionCmd(args: Args):
|
||||
def versionCmd(args: Any):
|
||||
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()
|
||||
|
||||
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) == True
|
||||
showDisabled: bool = args.consumeOpt("show-disabled", False) == True
|
||||
|
||||
context = contextFor(targetSpec)
|
||||
|
||||
graph.view(context, scope=scope, showExe=not onlyLibs,
|
||||
showDisabled=showDisabled)
|
||||
context = contextFor(args["target"])
|
||||
graph.view(
|
||||
context,
|
||||
scope=args["scope"],
|
||||
showExe=not args["only-libs"],
|
||||
showDisabled=args["show-disabled"])
|
||||
|
||||
|
||||
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]):
|
||||
|
@ -225,22 +341,30 @@ def grabExtern(extern: dict[str, Extern]):
|
|||
grabExtern(context.loadProject(extPath).extern)
|
||||
|
||||
|
||||
def installCmd(args: Args):
|
||||
def installCmd(args: Any):
|
||||
project.chdir()
|
||||
|
||||
pj = context.loadProject(".")
|
||||
grabExtern(pj.extern)
|
||||
p = context.loadProject(".")
|
||||
grabExtern(p.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):
|
||||
repo = args.consumeOpt('repo', const.DEFAULT_REPO_TEMPLATES)
|
||||
list = args.consumeOpt('list')
|
||||
def initCmd(args: Any):
|
||||
repo = args['repo']
|
||||
list = args['list']
|
||||
|
||||
template = args.consumeArg()
|
||||
name = args.consumeArg()
|
||||
template = args['template']
|
||||
name = args['name']
|
||||
|
||||
logger.info("Fetching registry...")
|
||||
r = requests.get(
|
||||
|
@ -261,7 +385,7 @@ def initCmd(args: Args):
|
|||
raise RuntimeError('Failed to fetch registry')
|
||||
|
||||
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
|
||||
|
||||
if not template:
|
||||
|
@ -284,26 +408,35 @@ def initCmd(args: Args):
|
|||
f" {vt100.GREEN}cutekit build{vt100.BRIGHT_BLACK} # Build the project{vt100.RESET}")
|
||||
|
||||
|
||||
cmds += [Cmd("I", "init", "Initialize a new project", initCmd)]
|
||||
|
||||
|
||||
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: Args):
|
||||
cmd = args.consumeArg()
|
||||
|
||||
if cmd is None:
|
||||
raise RuntimeError("No command specified")
|
||||
|
||||
for c in cmds:
|
||||
if c.shortName == cmd or c.longName == cmd:
|
||||
c.callback(args)
|
||||
return
|
||||
|
||||
raise RuntimeError(f"Unknown command {cmd}")
|
||||
append(
|
||||
args2.Keyword(
|
||||
"init",
|
||||
shorthand="I",
|
||||
description="Initialize a new project",
|
||||
run=installCmd,
|
||||
syntax=args2.AllOf(
|
||||
args2.Options(
|
||||
args2.Option[str](
|
||||
"repo",
|
||||
shorthand="r",
|
||||
description="Repository to fetch templates from",
|
||||
default=const.DEFAULT_REPO_TEMPLATES,
|
||||
),
|
||||
args2.Option[bool](
|
||||
"list",
|
||||
shorthand="l",
|
||||
description="List available templates",
|
||||
default=False,
|
||||
),
|
||||
),
|
||||
args2.Operand[str](
|
||||
"template",
|
||||
description="Template to use",
|
||||
),
|
||||
args2.Operand[str](
|
||||
"name",
|
||||
description="Name of the project",
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
@ -95,10 +95,10 @@ class Context(IContext):
|
|||
self.instances = instances
|
||||
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))
|
||||
if len(result) == 0:
|
||||
return None
|
||||
raise RuntimeError(f"Component '{name}' not found")
|
||||
return result[0]
|
||||
|
||||
def cincls(self) -> list[str]:
|
||||
|
|
Loading…
Reference in a new issue