use crate::error::CliError;
use crate::error::CliErrorKind;
use crate::types::*;
pub fn parse(
def: &CommandDef,
args: &[String],
) -> Result<ParseResult, CliError> {
let mut result = ParseResult::new();
let args = if args.is_empty() { args } else { &args[1..] };
let sub_def = resolve_subcommand(def, args);
if let Some(sub) = sub_def {
result.subcommand = Some(sub.name.to_string());
parse_args(sub, def, args, Some(sub.name), &mut result)?;
} else if let Some(default_name) = def.default_subcommand {
if let Some(default_def) = def.find_subcommand(default_name) {
parse_args(default_def, def, args, None, &mut result)?;
} else {
parse_args(def, def, args, None, &mut result)?;
}
} else {
parse_args(def, def, args, None, &mut result)?;
}
Ok(result)
}
fn resolve_subcommand<'a>(
def: &'a CommandDef,
args: &[String],
) -> Option<&'a CommandDef> {
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if arg == "--" {
break;
}
if arg.starts_with("--") {
let flag_name = arg.trim_start_matches('-');
let flag_name = flag_name.split('=').next().unwrap_or(flag_name);
if let Some(arg_def) = def
.all_args()
.find(|a| a.global && a.long == Some(flag_name))
{
i += 1;
if arg_def.num_args != NumArgs::Exact(0)
&& arg_def.action != ArgAction::SetTrue
&& !arg.contains('=')
{
i += 1;
}
continue;
}
break;
} else if arg.starts_with('-') && arg.len() > 1 {
let short = arg.chars().nth(1).unwrap();
if let Some(arg_def) =
def.all_args().find(|a| a.global && a.short == Some(short))
{
i += 1;
if arg_def.num_args != NumArgs::Exact(0)
&& arg_def.action != ArgAction::SetTrue
&& arg.len() == 2
{
i += 1;
}
continue;
}
break;
} else {
if let Some(sub) = def.find_subcommand(arg) {
return Some(sub);
}
break;
}
}
None
}
fn parse_args(
cmd_def: &CommandDef,
root_def: &CommandDef,
args: &[String],
skip_subcommand: Option<&str>,
result: &mut ParseResult,
) -> Result<(), CliError> {
let mut i = 0;
let mut positional_index = 0;
let mut trailing_mode = false;
let mut found_subcommand = skip_subcommand.is_none();
let mut passthrough_from: Option<usize> = None;
let mut positional_trailing_def: Option<&ArgDef> = None;
let positional_defs: Vec<&ArgDef> =
cmd_def.all_args().filter(|a| a.positional).collect();
while i < args.len() {
let arg = &args[i];
if let Some(trail_def) = positional_trailing_def {
if arg == "--" {
i += 1;
while i < args.len() {
set_arg_value(result, trail_def, args[i].clone())?;
i += 1;
}
continue;
}
set_arg_value(result, trail_def, arg.clone())?;
i += 1;
continue;
}
if trailing_mode {
result.trailing.push(arg.clone());
i += 1;
continue;
}
if arg == "--" {
if let Some(next_pos) = positional_defs.get(positional_index)
&& next_pos.trailing
{
positional_trailing_def = Some(next_pos);
i += 1;
continue;
}
trailing_mode = true;
i += 1;
continue;
}
if !found_subcommand
&& let Some(sub_name) = skip_subcommand
&& !arg.starts_with('-')
&& (arg == sub_name || cmd_def.aliases.contains(&arg.as_str()))
{
found_subcommand = true;
if cmd_def.passthrough {
passthrough_from = Some(i + 1);
break;
}
i += 1;
continue;
}
if arg.starts_with("--") {
i = parse_long_flag(cmd_def, root_def, args, i, result)?;
} else if arg.starts_with('-') && arg.len() > 1 {
i = parse_short_flag(cmd_def, root_def, args, i, result)?;
} else {
if let Some(pos_def) = positional_defs.get(positional_index) {
apply_value_with_delimiter(result, pos_def, arg)?;
if pos_def.trailing {
positional_trailing_def = Some(pos_def);
i += 1;
continue;
}
match pos_def.num_args {
NumArgs::ZeroOrMore | NumArgs::OneOrMore => {
}
_ => {
positional_index += 1;
if let Some(next_pos) = positional_defs.get(positional_index)
&& next_pos.trailing
{
positional_trailing_def = Some(next_pos);
i += 1;
continue;
}
if cmd_def.trailing_var_arg
&& positional_index >= positional_defs.len()
{
i += 1;
while i < args.len() {
result.trailing.push(args[i].clone());
i += 1;
}
continue;
}
}
}
} else {
if cmd_def.trailing_var_arg {
result.trailing.push(arg.clone());
} else {
return Err(CliError::new(
CliErrorKind::UnexpectedPositional,
format!("unexpected argument '{arg}'"),
));
}
}
i += 1;
}
}
if let Some(start) = passthrough_from {
for arg in args.iter().skip(start) {
result.trailing.push(arg.clone());
}
}
if result.contains("help")
|| (result.contains("version") && result.get_one("version").is_none())
{
return Ok(());
}
for arg_def in cmd_def.all_args() {
if arg_def.required && !result.contains(arg_def.name) {
return Err(CliError::new(
CliErrorKind::MissingRequired,
format!(
"the following required arguments were not provided: {}",
arg_def.name
),
));
}
}
for arg_def in cmd_def.all_args() {
if result.contains(arg_def.name) {
for other in arg_def.conflicts {
if result.contains(other) {
return Err(CliError::new(
CliErrorKind::InvalidValue,
format!(
"the argument '{}' cannot be used with '{}'",
arg_def.name, other
),
));
}
}
}
}
for arg_def in cmd_def.all_args() {
if result.contains(arg_def.name) {
for required in arg_def.requires {
if !result.contains(required)
&& !requirement_waived_by_conflict(cmd_def, result, required)
{
return Err(CliError::new(
CliErrorKind::MissingRequired,
format!(
"the following required arguments were not provided: --{required}"
),
));
}
}
}
}
Ok(())
}
fn requirement_waived_by_conflict(
cmd_def: &CommandDef,
result: &ParseResult,
required: &str,
) -> bool {
let required_conflicts = cmd_def
.all_args()
.find(|a| a.name == required)
.map(|a| a.conflicts)
.unwrap_or(&[]);
cmd_def.all_args().any(|a| {
result.contains(a.name)
&& (a.conflicts.contains(&required)
|| required_conflicts.contains(&a.name))
})
}
fn parse_long_flag(
cmd_def: &CommandDef,
root_def: &CommandDef,
args: &[String],
pos: usize,
result: &mut ParseResult,
) -> Result<usize, CliError> {
let arg = &args[pos];
let after_dashes = &arg[2..];
let (flag_name, inline_value) = match after_dashes.find('=') {
Some(eq_pos) => {
(&after_dashes[..eq_pos], Some(&after_dashes[eq_pos + 1..]))
}
None => (after_dashes, None),
};
let arg_def = cmd_def
.find_arg_long(flag_name)
.or_else(|| {
root_def.all_args().find(|a| {
a.global
&& (a.long == Some(flag_name) || a.long_aliases.contains(&flag_name))
})
})
.ok_or_else(|| CliError::unknown_flag(arg, cmd_def))?;
match arg_def.action {
ArgAction::SetTrue => {
set_arg_bool(result, arg_def);
if let Some(val) = inline_value
&& (arg_def.num_args == NumArgs::Optional
|| arg_def.num_args == NumArgs::ZeroOrMore)
{
set_arg_value(result, arg_def, val.to_string())?;
}
Ok(pos + 1)
}
ArgAction::Count => {
increment_arg_count(result, arg_def);
Ok(pos + 1)
}
ArgAction::Set | ArgAction::Append => {
match arg_def.num_args {
NumArgs::Exact(0) => {
set_arg_bool(result, arg_def);
Ok(pos + 1)
}
NumArgs::Optional => {
if let Some(val) = inline_value {
set_arg_bool(result, arg_def);
apply_value_with_delimiter(result, arg_def, val)?;
} else if arg_def.require_equals {
set_arg_bool(result, arg_def);
} else if pos + 1 < args.len() && !args[pos + 1].starts_with('-') {
set_arg_value(result, arg_def, args[pos + 1].clone())?;
return Ok(pos + 2);
} else {
set_arg_bool(result, arg_def);
}
Ok(pos + 1)
}
NumArgs::ZeroOrMore => {
set_arg_bool(result, arg_def);
if let Some(val) = inline_value {
apply_value_with_delimiter(result, arg_def, val)?;
} else if !arg_def.require_equals {
let mut next = pos + 1;
while next < args.len() && !args[next].starts_with('-') {
set_arg_value(result, arg_def, args[next].clone())?;
next += 1;
}
return Ok(next);
}
Ok(pos + 1)
}
NumArgs::OneOrMore | NumArgs::Exact(_) => {
if let Some(val) = inline_value {
apply_value_with_delimiter(result, arg_def, val)?;
Ok(pos + 1)
} else if pos + 1 < args.len() {
apply_value_with_delimiter(result, arg_def, &args[pos + 1])?;
Ok(pos + 2)
} else {
Err(CliError::missing_value(arg))
}
}
}
}
}
}
fn parse_short_flag(
cmd_def: &CommandDef,
root_def: &CommandDef,
args: &[String],
pos: usize,
result: &mut ParseResult,
) -> Result<usize, CliError> {
let arg = &args[pos];
let chars: Vec<char> = arg[1..].chars().collect();
let mut ci = 0;
while ci < chars.len() {
let short = chars[ci];
let arg_def = cmd_def
.find_arg_short(short)
.or_else(|| {
root_def.all_args().find(|a| {
a.global
&& (a.short == Some(short) || a.short_aliases.contains(&short))
})
})
.ok_or_else(|| CliError::unknown_flag(&format!("-{short}"), cmd_def))?;
match arg_def.action {
ArgAction::SetTrue => {
set_arg_bool(result, arg_def);
ci += 1;
}
ArgAction::Count => {
increment_arg_count(result, arg_def);
ci += 1;
}
ArgAction::Set | ArgAction::Append => {
match arg_def.num_args {
NumArgs::Exact(0) => {
set_arg_bool(result, arg_def);
ci += 1;
}
NumArgs::Optional | NumArgs::ZeroOrMore => {
set_arg_bool(result, arg_def);
if ci + 1 < chars.len() {
let next_char = chars[ci + 1];
let next_is_flag = cmd_def
.find_arg_short(next_char)
.or_else(|| {
root_def
.all_args()
.find(|a| a.global && a.short == Some(next_char))
})
.is_some();
if !next_is_flag {
let remaining: String = chars[ci + 1..].iter().collect();
let value = remaining.strip_prefix('=').unwrap_or(&remaining);
apply_value_with_delimiter(result, arg_def, value)?;
return Ok(pos + 1);
}
}
ci += 1;
}
_ => {
if ci + 1 < chars.len() {
let remaining: String = chars[ci + 1..].iter().collect();
let value = remaining.strip_prefix('=').unwrap_or(&remaining);
apply_value_with_delimiter(result, arg_def, value)?;
return Ok(pos + 1);
} else if pos + 1 < args.len() {
apply_value_with_delimiter(result, arg_def, &args[pos + 1])?;
return Ok(pos + 2);
} else {
return Err(CliError::missing_value(&format!("-{short}")));
}
}
}
}
}
}
Ok(pos + 1)
}
fn apply_value_with_delimiter(
result: &mut ParseResult,
arg_def: &ArgDef,
value: &str,
) -> Result<(), CliError> {
if let Some(delim) = arg_def.value_delimiter {
if value.is_empty() {
set_arg_value(result, arg_def, String::new())?;
} else {
for part in value.split(delim) {
set_arg_value(result, arg_def, part.to_string())?;
}
}
} else {
set_arg_value(result, arg_def, value.to_string())?;
}
Ok(())
}
fn set_arg_bool(result: &mut ParseResult, arg_def: &ArgDef) {
if let Some(existing) =
result.args.iter_mut().find(|a| a.name == arg_def.name)
{
existing.is_present = true;
existing.count += 1;
} else {
result.args.push(ParsedArg {
name: arg_def.name,
values: Vec::new(),
is_present: true,
count: 1,
});
}
}
fn set_arg_value(
result: &mut ParseResult,
arg_def: &ArgDef,
value: String,
) -> Result<(), CliError> {
if let Some(parser) = arg_def.value_parser {
let flag = arg_def
.long
.map(|l| format!("--{l}"))
.unwrap_or_else(|| arg_def.name.to_string());
parser.validate(&value, &flag)?;
}
if let Some(existing) =
result.args.iter_mut().find(|a| a.name == arg_def.name)
{
existing.is_present = true;
match arg_def.action {
ArgAction::Set => {
existing.values = vec![value];
}
_ => {
existing.values.push(value);
}
}
} else {
result.args.push(ParsedArg {
name: arg_def.name,
values: vec![value],
is_present: true,
count: 1,
});
}
Ok(())
}
fn increment_arg_count(result: &mut ParseResult, arg_def: &ArgDef) {
if let Some(existing) =
result.args.iter_mut().find(|a| a.name == arg_def.name)
{
existing.count += 1;
existing.is_present = true;
} else {
result.args.push(ParsedArg {
name: arg_def.name,
values: Vec::new(),
is_present: true,
count: 1,
});
}
}