CLI integration
How a Sushi CLI takes its console, its help screen and its shared commands from SushiCore. The
CLI is a Typer application; install SushiCore with the typer extra.
The console
Build the console on first use, not at import. Finding the CLI’s cli/ directory walks up for a
root marker and fails outside a checkout, so a console built at import breaks --help there.
LazyConsole takes the resolver and calls it the first time something prints:
# <package>/console.py
from sushicore.cli_console import LazyConsole
from .config import config_dir # the CLI's own resolver for its cli/ directory
lazy = LazyConsole(config_dir)
def __getattr__(name: str):
return lazy.attribute(name)
Other modules of the CLI then write from . import console and call console.info(...),
console.error(...), console.table(...) and the rest of the facade. lazy.get() returns the
Console object itself. Outside a checkout the console is built with no config files and uses
the defaults.
LazyConsole hands the console the files config.toml and config.local.toml from that
directory. build_console reads only their [cli] table, so the same files can hold the
[tool] build configuration. Configuration lists the keys.
Set lazy.machine = True while parsing the command line to get the JSON event stream; see
JSON events.
The help screen
import typer
from sushicore.typer_help import help_group
from . import console
app = typer.Typer(cls=help_group(console.lazy.get), rich_markup_mode="rich")
Pass the same class to every add_typer sub-application. What the page shows and when the logo
appears is in sushicore/help/README.md.
The shared surfaces
Every Sushi CLI registers the same surfaces, each with one call.
| Call | Gives the CLI |
|---|---|
sushicore.root_options.register_root_options(app, distribution=...) |
--version and --describe, the command catalogue as JSON |
sushicore.diag_commands.register_diagnostic_commands(app, diagnostics, program=..., panel=...) |
config and env |
sushicore.provision.commands.register_provision_commands(app, module) |
setup, doctor, link, unlink |
sushicore.aliases.AliasTable(program, aliases) |
Old spellings that still run, hidden, and name their replacement |
sushicore.entry.run(app, report) |
An entry point that turns a SushiCoreError into one line and exit code 1 |
register_root_options installs the application’s callback, so the application must not have
one of its own. A CLI that needs a root option of its own builds its callback from
version_option and describe_option in the same module.
register_diagnostic_commands takes env=False from a CLI whose env command has flags of its
own and is declared there.
AliasTable prints one notice per process, to a terminal only. Setting
<PROGRAM>_NO_DEPRECATION_NOTICE=1 silences it, for example SR_NO_DEPRECATION_NOTICE=1.
The provisioning commands and the ModuleProvision value they are registered from are in
sushicore/provision/README.md.
The entry point
The console script names a main function that hands the application to entry.run:
from sushicore import entry
from . import console
from .cli import app
def main() -> None:
entry.run(app, lambda message: console.error(message))
The reporter looks the printer up when it is called. Passing console.error directly reads the
attribute before run starts, and a console built from a malformed config file fails on that
read. When the reporter itself raises a SushiCoreError, run prints the line to stderr.
A failure the user can act on derives from sushicore.errors.SushiCoreError. Anything else
stays a traceback.
Building and running
build, test, run and clean stay in each CLI. SushiCore supplies what they call:
proc.Runner to spawn a child, cmake_driver.CMakeDriver for the cmake and ctest command
lines, build_env for the environment they run under and discovery.ExecutableIndex to list
what a build produced.

