///|
/// CLI argument errors with the process exit code the frontend should use.
priv suberror CliError {
Usage(message~ : String, exit_code~ : Int)
} derive(Debug)
///|
/// Parsed command-line options for one scan target.
priv struct CliOptions {
rules_root : String?
patterns : Array[String]
scan_root : String
verbose : Bool
}
///|
fn runtime_cli_args(argv : Array[String]) -> Array[String] {
let args : Array[String] = []
for index in 1.. String? {
match values.get(name) {
Some(items) if items.length() > 0 => Some(items[items.length() - 1])
_ => None
}
}
///|
fn all_cli_values(
values : Map[String, Array[String]],
name : String,
) -> Array[String] {
match values.get(name) {
Some(items) => items
None => []
}
}
///|
fn cli_flag_enabled(flags : Map[String, Bool], name : String) -> Bool {
match flags.get(name) {
Some(true) => true
_ => false
}
}
///|
fn docs_cli_command(
docs_matches : @argparse.Matches,
) -> (String, CliOptions?, String?) raise {
let list_requested = cli_flag_enabled(docs_matches.flags, "list")
let doc_name = last_cli_value(docs_matches.values, "doc-name")
match (list_requested, doc_name) {
(true, None) => ("docs-list", None, None)
(false, Some(name)) => ("docs-show", None, Some(name))
(true, Some(_)) =>
raise CliError::Usage(
message="docs --list cannot be combined with a document name",
exit_code=2,
)
(false, None) =>
raise CliError::Usage(
message="missing docs --list or document name",
exit_code=2,
)
}
}
///|
fn scan_cli_options(scan_matches : @argparse.Matches) -> CliOptions raise {
let verbose = cli_flag_enabled(scan_matches.flags, "verbose")
let rules_root = last_cli_value(scan_matches.values, "rules")
let patterns = all_cli_values(scan_matches.values, "pattern")
if rules_root is None && patterns.is_empty() {
raise CliError::Usage(
message="missing required --rules or --pattern",
exit_code=2,
)
}
let scan_root = match last_cli_value(scan_matches.values, "scan-root") {
Some(path) => path
None => "."
}
{ rules_root, patterns, scan_root, verbose }
}
///|
/// Parses command-line arguments after the executable name has been removed.
///
/// `scan` is required after the executable name. Within the `scan` subcommand,
/// `--rules `, `--rules=`, `-r `, or at
/// least one `--pattern ` is required, and `--verbose` is optional. One
/// optional scan-root positional may appear in the argument list. If the rules
/// option appears multiple times, the last value wins. Repeated patterns are
/// appended in order. Missing subcommands, required arguments, unknown options,
/// or more than one scan root raise `CliError::Usage` with exit code 2.
fn parse_cli_command(
argv : Array[String],
) -> (String, CliOptions?, String?) raise {
let matches = moongrep_command.parse(argv~, env={}) catch {
err => {
let message = "\{err}"
raise CliError::Usage(message~, exit_code=2)
}
}
match matches.subcommand {
Some(("scan", scan_matches)) =>
("scan", Some(scan_cli_options(scan_matches)), None)
Some(("docs", docs_matches)) => docs_cli_command(docs_matches)
_ => raise CliError::Usage(message="missing required command", exit_code=2)
}
}