feat(lang): std.cli exit-code + --json contract helpers [F3.3]
Foundation milestone close — the minimal exit-code / --json contract `dist` relies on, in pure sx (no compiler change). - EX_OK (0) / EX_USAGE (64, sysexits.h) / EX_UNAVAILABLE (70) named constants in std.cli. - exit_ok() / exit_usage() terminators routing through the canonical process.exit(code: u8) — removes the hand-rolled cli_bail_exit `_exit` binding; the unsupported-platform path now uses proc.exit(EX_UNAVAILABLE). - --json read is parsed.json (already parsed by F3.2); documented as the detection point with a stdout-pure / stderr-human convention. - examples/0718-modules-cli-exit-json.sx exercises the contract: json true with --json / false without, EX_USAGE == 64, and a usage path that exits 64 via exit_usage() (expected .exit = 64). - readme.md gains a std.cli command-line-interface subsection.
This commit is contained in:
@@ -25,11 +25,13 @@
|
||||
//
|
||||
// Platform: macOS only for now, via the C runtime's `_NSGetArgv()`
|
||||
// (char***) and `_NSGetArgc()` (int*). On any other OS the accessors bail
|
||||
// loudly (message + non-zero exit) rather than returning a silent empty.
|
||||
// loudly (message + `EX_UNAVAILABLE` exit via `process.exit`) rather than
|
||||
// returning a silent empty.
|
||||
// =====================================================================
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
proc :: #import "modules/process.sx";
|
||||
|
||||
libc :: #library "c";
|
||||
|
||||
@@ -42,9 +44,39 @@ libc :: #library "c";
|
||||
ns_get_argv :: () -> *s64 #foreign libc "_NSGetArgv";
|
||||
ns_get_argc :: () -> *s32 #foreign libc "_NSGetArgc";
|
||||
|
||||
// Bound to POSIX `_exit(2)`. Used only on the unsupported-platform path to
|
||||
// terminate loudly instead of handing back a misleading empty slice.
|
||||
cli_bail_exit :: (code: s32) -> noreturn #foreign libc "_exit";
|
||||
// =====================================================================
|
||||
// EXIT-CODE & `--json` CONTRACT (F3.3) — the minimal surface `dist` (and
|
||||
// any sx CLI front-end) relies on. Two pieces:
|
||||
//
|
||||
// 1. NAMED EXIT CODES (sysexits.h subset). A front-end ends with the
|
||||
// code that matches the outcome: `EX_OK` on success, `EX_USAGE`
|
||||
// after a `parse` failure (a `CliError` — bad command / flag /
|
||||
// value), `EX_UNAVAILABLE` when the platform is unsupported.
|
||||
// 2. TERMINATORS. `exit_ok()` / `exit_usage()` end the process with the
|
||||
// matching code. They route through the canonical
|
||||
// `process.exit(code: u8)` (modules/process.sx) — there is NO second
|
||||
// hand-rolled `_exit` binding in this module; the unsupported-platform
|
||||
// path below goes through `proc.exit(EX_UNAVAILABLE)` too.
|
||||
//
|
||||
// `--json` MODE needs no new code here: the parser already surfaces it as
|
||||
// `parsed.json` (true iff `--json` appears in the argv — see `Parsed.json`).
|
||||
// The convention a front-end follows: in json mode stdout carries ONLY the
|
||||
// machine result, and human diagnostics go to stderr (e.g. via
|
||||
// `modules/log.sx`'s `log.err`). Detect json mode by reading `parsed.json`.
|
||||
// =====================================================================
|
||||
|
||||
EX_OK :u8: 0; // success
|
||||
EX_USAGE :u8: 64; // command-line usage error (sysexits.h EX_USAGE)
|
||||
EX_UNAVAILABLE :u8: 70; // service / platform unavailable (sysexits.h)
|
||||
|
||||
// End the process successfully (`EX_OK` = 0). Thin wrapper over the
|
||||
// canonical `process.exit` terminator — immediate `_exit(2)`, no unwinding.
|
||||
exit_ok :: () -> noreturn { proc.exit(EX_OK); }
|
||||
|
||||
// End the process with the usage-error code (`EX_USAGE` = 64). A CLI
|
||||
// front-end calls this after `parse` raises a `CliError` and the human
|
||||
// diagnostic has been written to stderr.
|
||||
exit_usage :: () -> noreturn { proc.exit(EX_USAGE); }
|
||||
|
||||
// Number of process arguments (argc). >= 1 for any normally-launched
|
||||
// process, since argv[0] is the executable path.
|
||||
@@ -53,7 +85,7 @@ os_argc :: () -> s64 {
|
||||
case .macos: { return cast(s64) ns_get_argc().*; }
|
||||
else: {
|
||||
out("std.cli: unsupported platform — only macOS is implemented (needs _NSGetArgv/_NSGetArgc).\n");
|
||||
cli_bail_exit(70);
|
||||
proc.exit(EX_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +120,7 @@ os_args :: (buf: []string) -> []string {
|
||||
}
|
||||
else: {
|
||||
out("std.cli: unsupported platform — only macOS is implemented (needs _NSGetArgv/_NSGetArgc).\n");
|
||||
cli_bail_exit(70);
|
||||
proc.exit(EX_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +226,7 @@ Parsed :: struct {
|
||||
group: string;
|
||||
command: string;
|
||||
cmd_index: s64;
|
||||
json: bool;
|
||||
json: bool; // `--json` mode: true iff `--json` is in argv
|
||||
rest: []string;
|
||||
spec: []FlagSpec; // view of the matched command's flag specs
|
||||
values: [16]FlagValue; // fixed inline storage, indexed by spec position
|
||||
|
||||
Reference in New Issue
Block a user