// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn raise_help(text : String) -> Unit raise DisplayHelp {
raise Message(text)
}
///|
fn raise_version(text : String) -> Unit raise DisplayVersion {
raise Message(text)
}
///|
fn[T] raise_unknown_long(
name : StringView,
long_index : Map[String, Arg],
) -> T raise ArgParseError {
let hint = suggest_long(name, long_index)
raise UnknownArgument("--\{name}", hint)
}
///|
fn[T] raise_unknown_short(
short : Char,
short_index : Map[Char, Arg],
) -> T raise ArgParseError {
let hint = suggest_short(short, short_index)
raise UnknownArgument("-\{short}", hint)
}
///|
fn[T] raise_subcommand_conflict(name : String) -> T raise ArgParseError {
raise InvalidArgument(
"subcommand '\{name}' cannot be used with positional arguments",
)
}
///|
fn help_context_command(
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
) -> Command {
let help_name = if command_path == "" { cmd.name } else { command_path }
{ ..cmd, args: inherited_globals + cmd.args, name: help_name }
}
///|
fn render_help_for_context(
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
) -> String {
let help_cmd = help_context_command(cmd, inherited_globals, command_path)
render_help(help_cmd)
}
///|
fn raise_context_help(
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
) -> Unit raise DisplayHelp {
raise_help(render_help_for_context(cmd, inherited_globals, command_path))
}
///|
fn default_argv() -> ArrayView[String] {
let args = @env.args()
if args.length() > 1 {
args[1:]
} else {
[]
}
}
///|
fn merge_global_defs(
inherited_globals : Array[Arg],
globals_here : Array[Arg],
) -> Array[Arg] {
let merged = inherited_globals.copy()
for global in globals_here {
match merged.search_by(arg => arg.name == global.name) {
Some(idx) => merged[idx] = global
None => merged.push(global)
}
}
merged
}
///|
fn env_resolution_args(
inherited_globals : Array[Arg],
args : Array[Arg],
) -> Array[Arg] {
let merged_globals = merge_global_defs(
inherited_globals,
collect_globals(args),
)
let local_only = args.filter(arg => {
!(arg.global && arg.info is (FlagInfo(_) | OptionInfo(_)))
})
merged_globals + local_only
}
///|
fn format_error_with_help(
msg : String,
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
) -> String {
(
$|\{msg}
$|
$|\{render_help_for_context(cmd, inherited_globals, command_path)}
)
}
///|
fn arg_usage_token_for_group(arg : Arg) -> String {
let base = match arg.info {
OptionInfo(long~, short~, ..) | FlagInfo(long~, short~, ..) =>
if long is Some(long) {
"--\{long}"
} else if short is Some(short) {
"-\{short}"
} else {
arg.name
}
PositionalInfo(_) =>
if arg.multiple {
"<\{arg.name}...>"
} else {
"<\{arg.name}>"
}
}
if arg.info is OptionInfo(_) {
"\{base} <\{arg.name}>"
} else {
base
}
}
///|
fn group_usage_expr(
groups : Array[ArgGroup],
args : Array[Arg],
name : String,
) -> String? {
for group in groups {
if group.name != name {
continue
}
let members = [
for member_name in group.args if args.search_by(arg => {
arg.name == member_name
})
is Some(idx) &&
!args[idx].hidden => arg_usage_token_for_group(args[idx])
]
if members.is_empty() {
return None
}
let joined = members.join("|")
return Some("<\{joined}>")
}
None
}
///|
fn missing_group_error_message(
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
group_name : String,
) -> String {
let help_cmd = help_context_command(cmd, inherited_globals, command_path)
if group_usage_expr(help_cmd.groups, help_cmd.args, group_name) is Some(expr) {
(
$|error: the following required arguments were not provided:
$| \{expr}
)
} else {
MissingGroup(group_name).arg_parse_error_message()
}
}
///|
fn arg_error_for_parse_failure(
err : ArgParseError,
cmd : Command,
inherited_globals : Array[Arg],
command_path : String,
) -> ArgError {
let message = match err {
MissingGroup(name) =>
missing_group_error_message(cmd, inherited_globals, command_path, name)
TooManyPositionals(value, _) => {
let candidates = [ for sub in cmd.subcommands => sub.name ]
if help_subcommand_enabled(cmd) {
candidates.push("help")
}
if suggest_name(value, candidates) is Some(best) {
(
$|\{err.arg_parse_error_message()}
$|
$| tip: a similar subcommand exists: '\{best}'
)
} else {
err.arg_parse_error_message()
}
}
_ => err.arg_parse_error_message()
}
Message(format_error_with_help(message, cmd, inherited_globals, command_path))
}
///|
fn parse_command(
cmd : Command,
argv : ArrayView[String],
env : Map[String, String],
inherited_globals : Array[Arg],
inherited_version_long : Map[String, String],
inherited_version_short : Map[Char, String],
command_path : String,
seed_matches? : Matches = new_matches_parse_state(),
) -> Matches raise {
parse_command_impl(
cmd, argv, env, inherited_globals, inherited_version_long, inherited_version_short,
command_path, seed_matches,
) catch {
UnknownArgument(arg, hint) =>
raise arg_error_for_parse_failure(
UnknownArgument(arg, hint),
cmd,
inherited_globals,
command_path,
)
InvalidArgument(msg) =>
raise arg_error_for_parse_failure(
InvalidArgument(msg),
cmd,
inherited_globals,
command_path,
)
MissingValue(name) =>
raise arg_error_for_parse_failure(
MissingValue(name),
cmd,
inherited_globals,
command_path,
)
MissingRequired(name, by) =>
raise arg_error_for_parse_failure(
MissingRequired(name, by),
cmd,
inherited_globals,
command_path,
)
TooFewValues(name, got, min) =>
raise arg_error_for_parse_failure(
TooFewValues(name, got, min),
cmd,
inherited_globals,
command_path,
)
TooManyValues(name, got, max) =>
raise arg_error_for_parse_failure(
TooManyValues(name, got, max),
cmd,
inherited_globals,
command_path,
)
TooManyPositionals(value, arg) =>
raise arg_error_for_parse_failure(
TooManyPositionals(value, arg),
cmd,
inherited_globals,
command_path,
)
InvalidValue(msg) =>
raise arg_error_for_parse_failure(
InvalidValue(msg),
cmd,
inherited_globals,
command_path,
)
MissingGroup(name) =>
raise arg_error_for_parse_failure(
MissingGroup(name),
cmd,
inherited_globals,
command_path,
)
GroupConflict(name) =>
raise arg_error_for_parse_failure(
GroupConflict(name),
cmd,
inherited_globals,
command_path,
)
Unsupported(msg) =>
raise ArgError::Message(
"error: command definition validation failed: \{msg}",
)
err => raise err
}
}
///|
fn parse_command_impl(
cmd : Command,
argv : ArrayView[String],
env : Map[String, String],
inherited_globals : Array[Arg],
inherited_version_long : Map[String, String],
inherited_version_short : Map[Char, String],
command_path : String,
seed_matches : Matches,
) -> Matches raise {
if cmd.build_error is Some(err) {
raise err
}
let args = cmd.args
let groups = cmd.groups
let subcommands = cmd.subcommands
if cmd.arg_required_else_help && argv.is_empty() {
raise_context_help(cmd, inherited_globals, command_path)
}
let matches = seed_matches
let globals_here = collect_globals(args)
let child_globals = merge_global_defs(inherited_globals, globals_here)
let child_version_long = inherited_version_long.copy()
let child_version_short = inherited_version_short.copy()
for global in globals_here {
if global.info is FlagInfo(long~, short~, action=Version, ..) {
if long is Some(name) {
child_version_long[name] = command_version(cmd)
}
if short is Some(short) {
child_version_short[short] = command_version(cmd)
}
}
}
let long_index = build_long_index(inherited_globals, args)
let short_index = build_short_index(inherited_globals, args)
let builtin_help_short = help_flag_enabled(cmd) &&
short_index.get('h') is None
let builtin_help_long = help_flag_enabled(cmd) &&
long_index.get("help") is None
let builtin_version_short = version_flag_enabled(cmd) &&
short_index.get('V') is None
let builtin_version_long = version_flag_enabled(cmd) &&
long_index.get("version") is None
let positionals = positional_args(args)
let positional_values = []
let mut i = 0
let mut positional_arg_found = false
let default_subcommand = match cmd.default_subcommand {
Some(default_name) =>
subcommands
.iter()
.find_first(sub => sub.name == default_name && !sub.hidden)
None => None
}
fn dispatch_subcommand(
sub : Command,
rest : ArrayView[String],
) -> Matches raise _ {
let sub_path = if command_path == "" {
sub.name
} else {
"\{command_path} \{sub.name}"
}
let child_local_non_globals = collect_non_global_names(sub.args)
let child_seed = seed_child_globals_from_parent(
matches, child_globals, child_local_non_globals,
)
let sub_matches = parse_command(
sub,
rest,
env,
child_globals,
child_version_long,
child_version_short,
sub_path,
seed_matches=child_seed,
)
matches.parsed_subcommand = Some((sub.name, sub_matches))
// Merge argv-provided globals from the subcommand parse into the parent
// so globals work even when they appear after the subcommand name.
merge_globals_from_child(
matches, sub_matches, child_globals, child_local_non_globals,
)
let env_args = env_resolution_args(inherited_globals, args)
let parent_matches = finalize_matches(
cmd, args, groups, matches, positionals, positional_values, env_args, env,
)
validate_relationships(parent_matches, args)
if parent_matches.parsed_subcommand is Some((sub_name, sub_m)) {
// After parent parsing, copy the final globals into the subcommand.
propagate_globals_to_child(
parent_matches, sub_m, child_globals, child_local_non_globals,
)
parent_matches.parsed_subcommand = Some((sub_name, sub_m))
}
parent_matches
}
while i < argv.length() {
let arg = argv[i]
if arg == "--" {
if default_subcommand is Some(sub) {
return dispatch_subcommand(sub, argv[i:])
}
if i + 1 < argv.length() {
positional_arg_found = true
}
for rest in argv[i + 1:] {
positional_values.push(rest)
}
break
}
if builtin_help_short && arg == "-h" {
raise_context_help(cmd, inherited_globals, command_path)
}
if builtin_help_long && arg == "--help" {
raise_context_help(cmd, inherited_globals, command_path)
}
if builtin_version_short && arg == "-V" {
raise_version(command_version(cmd))
}
if builtin_version_long && arg == "--version" {
raise_version(command_version(cmd))
}
if should_parse_as_positional(
arg, positionals, positional_values, long_index, short_index,
) {
positional_values.push(arg)
positional_arg_found = true
i += 1
continue
}
if arg.has_prefix("--") {
let (name, inline) = split_long(arg)
if builtin_help_long && name == "help" {
if inline is Some(_) {
raise ArgParseError::InvalidArgument(arg)
}
raise_context_help(cmd, inherited_globals, command_path)
}
if builtin_version_long && name == "version" {
if inline is Some(_) {
raise ArgParseError::InvalidArgument(arg)
}
raise_version(command_version(cmd))
}
match long_index.get_from_string(name) {
None =>
// Support `--no-` when the underlying flag is marked `negatable`.
if name is [.. "no-", .. target] &&
long_index.get_from_string(target)
is Some({ info: FlagInfo(negatable=true, action~, ..), .. } as spec) {
if inline is Some(_) {
raise ArgParseError::InvalidArgument(arg)
}
let value = match action {
SetFalse => true
_ => false
}
if action is Count {
matches.counts[spec.name] = 0
}
matches.flags[spec.name] = value
matches.flag_sources[spec.name] = Argv
} else if default_subcommand is Some(sub) {
return dispatch_subcommand(sub, argv[i:])
} else {
raise_unknown_long(name, long_index)
}
Some(spec) =>
if spec.info is (OptionInfo(_) | PositionalInfo(_)) {
check_duplicate_set_occurrence(matches, spec)
if inline is Some(v) {
assign_value(matches, spec, v, Argv)
} else {
let can_take_next = i + 1 < argv.length() &&
!should_stop_option_value(
argv[i + 1],
spec,
long_index,
short_index,
)
if can_take_next {
i += 1
assign_value(matches, spec, argv[i], Argv)
} else {
raise ArgParseError::MissingValue("--\{name}")
}
}
} else {
if inline is Some(_) {
raise ArgParseError::InvalidArgument(arg)
}
match spec.info {
FlagInfo(action=Help, ..) =>
raise_context_help(cmd, inherited_globals, command_path)
FlagInfo(action=Version, ..) =>
raise_version(
version_text_for_long_action(
cmd, name, inherited_version_long,
),
)
_ => apply_flag(matches, spec, Argv)
}
}
}
i += 1
continue
}
if arg.has_prefix("-") && arg != "-" {
// Parse short groups like `-abc` and short values like `-c3`.
let chars = arg.iter()
ignore(chars.next())
let mut consumed_next = false
while chars.next() is Some(short) {
if short == 'h' && builtin_help_short {
raise_context_help(cmd, inherited_globals, command_path)
}
if short == 'V' && builtin_version_short {
raise_version(command_version(cmd))
}
let spec = match short_index.get(short) {
Some(v) => v
None =>
if default_subcommand is Some(sub) {
let remaining = Array::new(capacity=argv.length() - i)
remaining.push("-\{short}\{String::from_iter(chars)}")
for rest in argv[i + 1:] {
remaining.push(rest)
}
return dispatch_subcommand(sub, remaining)
} else {
raise_unknown_short(short, short_index)
}
}
if spec.info is (OptionInfo(_) | PositionalInfo(_)) {
check_duplicate_set_occurrence(matches, spec)
let rest = String::from_iter(chars)
if rest != "" {
let inline = match rest.strip_prefix("=") {
Some(view) => view.to_owned()
None => rest
}
assign_value(matches, spec, inline, Argv)
} else {
let can_take_next = i + 1 < argv.length() &&
!should_stop_option_value(
argv[i + 1],
spec,
long_index,
short_index,
)
if can_take_next {
consumed_next = true
assign_value(matches, spec, argv[i + 1], Argv)
} else {
raise ArgParseError::MissingValue("-\{short}")
}
}
break
} else {
match spec.info {
FlagInfo(action=Help, ..) =>
raise_context_help(cmd, inherited_globals, command_path)
FlagInfo(action=Version, ..) =>
raise_version(
version_text_for_short_action(
cmd, short, inherited_version_short,
),
)
_ => apply_flag(matches, spec, Argv)
}
}
}
i += 1 + (if consumed_next { 1 } else { 0 })
continue
}
if help_subcommand_enabled(cmd) && arg == "help" {
if positional_arg_found {
raise_subcommand_conflict("help")
}
let rest = argv[i + 1:]
let (target, target_globals, target_path) = resolve_help_target(
cmd, rest, builtin_help_short, builtin_help_long, inherited_globals, command_path,
)
let text = render_help_for_context(target, target_globals, target_path)
raise_help(text)
}
if subcommands.iter().find_first(sub => sub.name == arg) is Some(sub) {
if positional_arg_found {
raise_subcommand_conflict(sub.name)
}
return dispatch_subcommand(sub, argv[i + 1:])
}
if default_subcommand is Some(sub) {
return dispatch_subcommand(sub, argv[i:])
}
positional_values.push(arg)
positional_arg_found = true
i += 1
}
if default_subcommand is Some(sub) {
return dispatch_subcommand(sub, [])
}
let env_args = env_resolution_args(inherited_globals, args)
let final_matches = finalize_matches(
cmd, args, groups, matches, positionals, positional_values, env_args, env,
)
validate_relationships(final_matches, args)
final_matches
}
///|
fn finalize_matches(
cmd : Command,
args : Array[Arg],
groups : Array[ArgGroup],
matches : Matches,
positionals : Array[Arg],
positional_values : Array[String],
env_args : Array[Arg],
env : Map[String, String],
) -> Matches raise ArgParseError {
assign_positionals(matches, positionals, positional_values)
apply_env(matches, env_args, env)
apply_defaults(matches, env_args)
validate_values(args, matches)
validate_groups(args, groups, matches)
validate_command_policies(cmd, matches)
matches
}
///|
fn help_subcommand_enabled(cmd : Command) -> Bool {
!cmd.disable_help_subcommand && !cmd.subcommands.is_empty()
}
///|
fn help_flag_enabled(cmd : Command) -> Bool {
!cmd.disable_help_flag
}
///|
fn version_flag_enabled(cmd : Command) -> Bool {
!cmd.disable_version_flag && cmd.version is Some(_)
}
///|
fn command_version(cmd : Command) -> String {
cmd.version.unwrap_or("")
}
///|
fn version_text_for_long_action(
cmd : Command,
long : StringView,
inherited_version_long : Map[String, String],
) -> String {
for arg in cmd.args {
if arg.info is FlagInfo(long=Some(name), action=Version, ..) &&
name[:] == long {
return command_version(cmd)
}
}
inherited_version_long.get_from_string(long).unwrap_or(command_version(cmd))
}
///|
fn version_text_for_short_action(
cmd : Command,
short : Char,
inherited_version_short : Map[Char, String],
) -> String {
for arg in cmd.args {
if arg.info is FlagInfo(short=Some(value), action=Version, ..) &&
value == short {
return command_version(cmd)
}
}
inherited_version_short.get(short).unwrap_or(command_version(cmd))
}