// 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.
///|
/// Declarative command specification.
pub struct Command {
priv name : String
priv args : Array[Arg]
priv groups : Array[ArgGroup]
priv subcommands : Array[Command]
priv about : String?
priv version : String?
priv disable_help_flag : Bool
priv disable_version_flag : Bool
priv disable_help_subcommand : Bool
priv arg_required_else_help : Bool
priv subcommand_required : Bool
priv default_subcommand : String?
priv hidden : Bool
priv mut build_error : ArgBuildError?
} derive(@debug.Debug)
///|
/// Create a declarative command specification.
///
/// Notes:
/// - `flags`, `options`, and `positionals` declare arguments by kind.
/// - `groups` declares argument-group membership and policies.
/// - `disable_help_flag` / `disable_version_flag` disable built-in
/// `--help` / `--version`.
/// - `disable_help_subcommand` disables built-in `help ` routing.
/// - `arg_required_else_help=true` prints help when no argv tokens are provided.
/// - `subcommand_required=true` requires selecting a subcommand.
/// - `default_subcommand` dispatches missing subcommands to a visible child.
/// - `hidden=true` omits this command from parent command listings.
#alias(new, deprecated="Use `Command()` instead")
pub fn Command::Command(
name : StringView,
flags? : ArrayView[FlagArg] = [],
options? : ArrayView[OptionArg] = [],
positionals? : ArrayView[PositionArg] = [],
subcommands? : ArrayView[Command] = [],
about? : StringView,
version? : StringView,
disable_help_flag? : Bool = false,
disable_version_flag? : Bool = false,
disable_help_subcommand? : Bool = false,
arg_required_else_help? : Bool = false,
subcommand_required? : Bool = false,
hidden? : Bool = false,
groups? : ArrayView[ArgGroup] = [],
default_subcommand? : StringView,
) -> Command {
let (parsed_args, arg_error) = collect_args(flags, options, positionals)
let groups = groups.to_owned()
let cmd = Command::{
name: name.to_owned(),
args: parsed_args,
groups,
subcommands: subcommands.to_owned(),
about: about.map(v => v.to_owned()),
version: version.map(v => v.to_owned()),
disable_help_flag,
disable_version_flag,
disable_help_subcommand,
arg_required_else_help,
subcommand_required,
default_subcommand: default_subcommand.map(v => v.to_owned()),
hidden,
build_error: arg_error,
}
if cmd.build_error is None {
validate_command(cmd, parsed_args, groups, []) catch {
err => cmd.build_error = Some(err)
}
}
cmd
}
///|
/// Render help text without parsing.
pub fn Command::render_help(self : Command) -> String {
render_help(self)
}
///|
/// Parse argv/environment according to this command spec.
///
/// Behavior:
/// - Help/version requests print output immediately and terminate with exit code
/// `0`.
/// - Parse failures raise display-ready error text with full contextual help.
/// - Command-definition validation failures raise display-ready validation
/// text (without appended help).
///
/// Value precedence is `argv > env > default_values`.
#as_free_fn
pub fn Command::parse(
self : Command,
argv? : ArrayView[String] = default_argv(),
env? : Map[String, String] = Map([]),
) -> Matches raise {
try {
let raw = parse_command(self, argv, env, [], Map([]), Map([]), self.name)
build_matches(self, raw, [])
} catch {
DisplayHelp::Message(text) => print_and_exit_success(text)
DisplayVersion::Message(text) => print_and_exit_success(text)
ArgError::Message(_) as err => raise err
err => {
println(err.to_string())
panic()
}
}
}
///|
fn build_matches(
cmd : Command,
raw : Matches,
inherited_globals : Array[Arg],
) -> Matches {
let flags = Map([])
let values = Map([])
let flag_counts = Map([])
let sources = Map([])
let specs = inherited_globals + cmd.args
for spec in specs {
let name = arg_name(spec)
if raw.values.get(name) is Some(vs) {
values[name] = vs.copy()
}
let count = raw.counts.get_or_default(name, 0)
if count > 0 {
flag_counts[name] = count
}
let source = match raw.flag_sources.get(name) {
Some(v) => Some(v)
None => raw.value_sources.get(name)
}
if source is Some(source) {
sources[name] = source
if spec.info is FlagInfo(action~, ..) {
if action is Count {
flags[name] = count > 0
} else {
flags[name] = raw.flags.get(name).unwrap_or(false)
}
}
}
}
let child_globals = merge_global_defs(
inherited_globals,
collect_globals(cmd.args),
)
let subcommand = match raw.parsed_subcommand {
Some((name, sub_raw)) =>
if find_decl_subcommand(cmd.subcommands, name) is Some(sub_spec) {
Some((name, build_matches(sub_spec, sub_raw, child_globals)))
} else {
Some(
(
name,
{
flags: Map([]),
values: Map([]),
flag_counts: Map([]),
sources: Map([]),
subcommand: None,
counts: Map([]),
flag_sources: Map([]),
value_sources: Map([]),
parsed_subcommand: None,
},
),
)
}
None => None
}
{
flags,
values,
flag_counts,
sources,
subcommand,
counts: Map([]),
flag_sources: Map([]),
value_sources: Map([]),
parsed_subcommand: None,
}
}
///|
fn find_decl_subcommand(subs : Array[Command], name : String) -> Command? {
for sub in subs {
if sub.name == name {
return Some(sub)
}
}
None
}
///|
fn collect_args(
flags : ArrayView[FlagArg],
options : ArrayView[OptionArg],
positionals : ArrayView[PositionArg],
) -> (Array[Arg], ArgBuildError?) {
let args : Array[Arg] = []
for flag in flags {
args.push(flag.arg)
}
for option in options {
args.push(option.arg)
}
for positional in positionals {
args.push(positional.arg)
}
let ctx = ValidationCtx::new()
let first_error : ArgBuildError? = for
flag in flags
first_error = (None : ArgBuildError?) {
validate_flag_arg(flag.arg, ctx) catch {
err => if first_error is None { continue Some(err) }
}
continue first_error
} nobreak {
first_error
}
let first_error : ArgBuildError? = for
option in options
first_error = first_error {
validate_option_arg(option.arg, ctx) catch {
err => if first_error is None { continue Some(err) }
}
continue first_error
} nobreak {
first_error
}
let first_error : ArgBuildError? = for
positional in positionals
first_error = first_error {
validate_positional_arg(positional.arg, ctx) catch {
err => if first_error is None { continue Some(err) }
}
continue first_error
} nobreak {
first_error
}
let first_error = if first_error is None {
try {
ctx.finalize()
None
} catch {
err => Some(err)
}
} else {
first_error
}
(args, first_error)
}