///|
/// Shared CLI exit helpers used by `src/cmd/ts2mbt` and `src/cmd/mbt2ts`.
/// Each cmd binary owns its own `print_usage` callback and binary name; the
/// library only owns the exit + error-print pieces so the dispatchers stay
/// thin.
///|
#cfg(target="native")
extern "C" fn cli_exit_native(code : Int) = "exit"
///|
#cfg(not(target="native"))
fn cli_exit_native(code : Int) -> Unit {
abort("tsmbt cli exit \{code}")
}
///|
/// Exit the CLI with the given status code. Use `0` for success and a
/// non-zero value for any failure path.
pub fn cli_exit(code : Int) -> Unit {
cli_exit_native(code)
}
///|
/// Print a one-line " command failed" banner and exit with status 1.
/// Cmd dispatchers should call this from every error path so CI / scripts
/// can detect failure.
pub fn cli_fail(binary_name : String) -> Unit {
println("\{binary_name} command failed")
cli_exit(1)
}
///|
/// Bail out of the CLI: print `Error: ` then exit with status 1.
/// Suitable for missing-argument, unknown-subcommand, and other usage
/// errors that should not be silently ignored.
pub fn cli_bail(message : String) -> Unit {
println("Error: \{message}")
cli_exit(1)
}
///|
/// `mizchi/ts` package version reported by `--version` / `-V` from each
/// cmd binary. Sourced from `moon.mod.json` and bumped together with it
/// at release time.
pub const CLI_VERSION : String = "0.3.0"
///|
/// Print the version banner for the given binary name and exit with
/// status 0. Cmd dispatchers should intercept `--version` / `-V` and
/// route here.
pub fn cli_print_version(binary_name : String) -> Unit {
println("\{binary_name} \{CLI_VERSION} (mizchi/ts)")
}
///|
/// Treat the given top-level token as a version request — `--version`
/// or `-V`. Cmd dispatchers use this to short-circuit the help/dispatch
/// pipeline and just print the version banner.
pub fn cli_token_is_version(token : String) -> Bool {
token == "--version" || token == "-V"
}
///|
/// Sanitize a raw bridge / IO error message so the printed CLI text
/// doesn't leak the MoonBit `OSError("@fs.open(): ...")` debug shape.
/// Specifically: when the message contains `OSError("...")`, replace
/// that fragment with the inner reason (after the first `: ` delimiter)
/// so users see a single-quoted file path + plain reason.
pub fn cli_clean_error(message : String) -> String {
let marker = "OSError(\""
match message.find(marker) {
None => message
Some(start_idx) => {
let prefix = message[:start_idx].to_string()
let body_start = start_idx + marker.length()
let after = message[body_start:message.length()].to_string()
// The body looks like:
// : \"\": ")
// Strip the trailing `")` and pull the trailing reason after the
// last `\": `.
let trimmed = if after.has_suffix("\")") {
after[:after.length() - 2].to_string()
} else {
after
}
let reason = match trimmed.rev_find("\\\": ") {
Some(idx) => trimmed[idx + 4:trimmed.length()].to_string()
None => trimmed
}
prefix + reason
}
}
}
///|
/// Treat the given subcommand token as a help request — `--help`, `-h`, or
/// `help`. Cmd dispatchers use this to short-circuit per-subcommand help
/// before treating the next positional as a path.
pub fn cli_subcommand_is_help(token : String) -> Bool {
token == "--help" || token == "-h" || token == "help"
}
///|
/// Outcome of a CLI subcommand argument scan.
///
/// - `Args(positionals)`: the parse succeeded; `positionals` are the
/// subcommand-specific args (`args[start:]`).
/// - `HelpRequested`: the next token after the subcommand was a help
/// marker; the caller should print usage and return without erroring.
pub enum CliArgScan {
Args(Array[String])
HelpRequested
}
///|
/// Parse the positional arguments for a subcommand. Returns `HelpRequested`
/// when the user passed `--help` / `-h` / `help`. Calls `cli_bail` (which
/// exits with status 1) when fewer than `min_args` positionals are present
/// — the bail message includes a hint pointing at the per-subcommand
/// `--help` so users know how to recover.
///
/// `args` is the full process argv slice (`@env.args()`), `start` is the
/// index of the first positional after the subcommand verb, and
/// `usage` is the bail message printed when the arity check fails. The
/// optional `help_hint` (e.g. `"ts2mbt decl --help"`) is appended to the
/// bail message; pass `""` to suppress it.
pub fn cli_parse_subcommand_args(
args : Array[String],
start : Int,
min_args : Int,
usage : String,
help_hint? : String = "",
) -> CliArgScan {
if start < args.length() && cli_subcommand_is_help(args[start]) {
return HelpRequested
}
let provided = args.length() - start
if provided < min_args {
let suffix = if help_hint == "" {
""
} else {
" (run `\{help_hint}` for usage)"
}
cli_bail("\{usage} — got \{provided} arg(s), need \{min_args}\{suffix}")
}
let positionals : Array[String] = []
for i in start..