fish-shell/src/builtins/argparse.rs
2024-01-13 03:58:33 +01:00

978 lines
31 KiB
Rust

use std::collections::HashMap;
use super::prelude::*;
use crate::env::{EnvMode, EnvStack};
use crate::exec::exec_subshell;
use crate::wcstringutil::split_string;
use crate::wutil::fish_iswalnum;
const VAR_NAME_PREFIX: &wstr = L!("_flag_");
const BUILTIN_ERR_INVALID_OPT_SPEC: &str = "%ls: Invalid option spec '%ls' at char '%lc'\n";
#[derive(PartialEq)]
enum ArgCardinality {
Optional = -1isize,
None = 0,
Once = 1,
AtLeastOnce = 2,
}
impl Default for ArgCardinality {
fn default() -> Self {
Self::None
}
}
#[derive(Default)]
struct OptionSpec<'args> {
short_flag: char,
long_flag: &'args wstr,
validation_command: &'args wstr,
vals: Vec<WString>,
short_flag_valid: bool,
num_allowed: ArgCardinality,
num_seen: isize,
}
impl OptionSpec<'_> {
fn new(s: char) -> Self {
Self {
short_flag: s,
short_flag_valid: true,
..Default::default()
}
}
}
#[derive(Default)]
struct ArgParseCmdOpts<'args> {
ignore_unknown: bool,
print_help: bool,
stop_nonopt: bool,
min_args: usize,
max_args: usize,
implicit_int_flag: char,
name: WString,
raw_exclusive_flags: Vec<&'args wstr>,
args: Vec<&'args wstr>,
options: HashMap<char, OptionSpec<'args>>,
long_to_short_flag: HashMap<WString, char>,
exclusive_flag_sets: Vec<Vec<char>>,
}
impl ArgParseCmdOpts<'_> {
fn new() -> Self {
Self {
max_args: usize::MAX,
..Default::default()
}
}
}
const SHORT_OPTIONS: &wstr = L!("+:hn:six:N:X:");
const LONG_OPTIONS: &[woption] = &[
wopt(L!("stop-nonopt"), woption_argument_t::no_argument, 's'),
wopt(L!("ignore-unknown"), woption_argument_t::no_argument, 'i'),
wopt(L!("name"), woption_argument_t::required_argument, 'n'),
wopt(L!("exclusive"), woption_argument_t::required_argument, 'x'),
wopt(L!("help"), woption_argument_t::no_argument, 'h'),
wopt(L!("min-args"), woption_argument_t::required_argument, 'N'),
wopt(L!("max-args"), woption_argument_t::required_argument, 'X'),
];
// Check if any pair of mutually exclusive options was seen. Note that since every option must have
// a short name we only need to check those.
fn check_for_mutually_exclusive_flags(
opts: &ArgParseCmdOpts,
streams: &mut IoStreams,
) -> Option<c_int> {
for opt_spec in opts.options.values() {
if opt_spec.num_seen == 0 {
continue;
}
// We saw this option at least once. Check all the sets of mutually exclusive options to see
// if this option appears in any of them.
for xarg_set in &opts.exclusive_flag_sets {
if xarg_set.contains(&opt_spec.short_flag) {
// Okay, this option is in a mutually exclusive set of options. Check if any of the
// other mutually exclusive options have been seen.
for xflag in xarg_set {
let Some(xopt_spec) = opts.options.get(xflag) else {
continue;
};
// Ignore this flag in the list of mutually exclusive flags.
if xopt_spec.short_flag == opt_spec.short_flag {
continue;
}
// If it is a different flag check if it has been seen.
if xopt_spec.num_seen != 0 {
let mut flag1: WString = WString::new();
if opt_spec.short_flag_valid {
flag1.push(opt_spec.short_flag);
}
if !opt_spec.long_flag.is_empty() {
if opt_spec.short_flag_valid {
flag1.push('/');
}
flag1.push_utfstr(&opt_spec.long_flag);
}
let mut flag2: WString = WString::new();
if xopt_spec.short_flag_valid {
flag2.push(xopt_spec.short_flag);
}
if !xopt_spec.long_flag.is_empty() {
if xopt_spec.short_flag_valid {
flag2.push('/');
}
flag2.push_utfstr(&xopt_spec.long_flag);
}
// We want the flag order to be deterministic. Primarily to make unit
// testing easier.
if flag1 > flag2 {
std::mem::swap(&mut flag1, &mut flag2);
}
streams.err.append(wgettext_fmt!(
"%ls: %ls %ls: options cannot be used together\n",
opts.name,
flag1,
flag2
));
return STATUS_CMD_ERROR;
}
}
}
}
}
return STATUS_CMD_OK;
}
// This should be called after all the option specs have been parsed. At that point we have enough
// information to parse the values associated with any `--exclusive` flags.
fn parse_exclusive_args(opts: &mut ArgParseCmdOpts, streams: &mut IoStreams) -> Option<c_int> {
for raw_xflags in &opts.raw_exclusive_flags {
let xflags = split_string(raw_xflags, ',');
if xflags.len() < 2 {
streams.err.append(wgettext_fmt!(
"%ls: exclusive flag string '%ls' is not valid\n",
opts.name,
raw_xflags
));
return STATUS_CMD_ERROR;
}
let exclusive_set: &mut Vec<char> = &mut vec![];
for flag in &xflags {
if flag.char_count() == 1 && opts.options.contains_key(&flag.char_at(0)) {
let short = flag.char_at(0);
// It's a short flag.
exclusive_set.push(short);
} else if let Some(short_equiv) = opts.long_to_short_flag.get(flag) {
// It's a long flag we store as its short flag equivalent.
exclusive_set.push(*short_equiv);
} else {
streams.err.append(wgettext_fmt!(
"%ls: exclusive flag '%ls' is not valid\n",
opts.name,
flag
));
return STATUS_CMD_ERROR;
}
}
// Store the set of exclusive flags for use when parsing the supplied set of arguments.
opts.exclusive_flag_sets.push(exclusive_set.to_vec());
}
return STATUS_CMD_OK;
}
fn parse_flag_modifiers<'args>(
opts: &ArgParseCmdOpts<'args>,
opt_spec: &mut OptionSpec<'args>,
option_spec: &wstr,
opt_spec_str: &mut &'args wstr,
streams: &mut IoStreams,
) -> bool {
let mut s = *opt_spec_str;
if opt_spec.short_flag == opts.implicit_int_flag && !s.is_empty() && s.char_at(0) != '!' {
streams.err.append(wgettext_fmt!(
"%ls: Implicit int short flag '%lc' does not allow modifiers like '%lc'\n",
opts.name,
opt_spec.short_flag,
s.char_at(0)
));
return false;
}
if s.char_at(0) == '=' {
s = s.slice_from(1);
opt_spec.num_allowed = match s.char_at(0) {
'?' => ArgCardinality::Optional,
'+' => ArgCardinality::AtLeastOnce,
_ => ArgCardinality::Once,
};
if opt_spec.num_allowed != ArgCardinality::Once {
s = s.slice_from(1);
}
}
if s.char_at(0) == '!' {
s = s.slice_from(1);
opt_spec.validation_command = s;
// Move cursor to the end so we don't expect a long flag.
s = s.slice_from(s.char_count());
} else if !s.is_empty() {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(0)
));
return false;
}
// Make sure we have some validation for implicit int flags.
if opt_spec.short_flag == opts.implicit_int_flag && opt_spec.validation_command.is_empty() {
opt_spec.validation_command = L!("_validate_int");
}
if opts.options.contains_key(&opt_spec.short_flag) {
streams.err.append(wgettext_fmt!(
"%ls: Short flag '%lc' already defined\n",
opts.name,
opt_spec.short_flag
));
return false;
}
*opt_spec_str = s;
return true;
}
/// Parse the text following the short flag letter.
fn parse_option_spec_sep<'args>(
opts: &mut ArgParseCmdOpts<'args>,
opt_spec: &mut OptionSpec<'args>,
option_spec: &'args wstr,
opt_spec_str: &mut &'args wstr,
counter: &mut u32,
streams: &mut IoStreams,
) -> bool {
let mut s = *opt_spec_str;
let mut i = 1usize;
// C++ used -1 to check for # here, we instead adjust opt_spec_str to start one earlier
if s.char_at(i - 1) == '#' {
if s.char_at(i) != '-' {
// Long-only!
i -= 1;
opt_spec.short_flag = char::from_u32(*counter).unwrap();
*counter += 1;
}
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%ls: Implicit int flag '%lc' already defined\n",
opts.name,
opts.implicit_int_flag
));
return false;
}
opts.implicit_int_flag = opt_spec.short_flag;
opt_spec.short_flag_valid = false;
i += 1;
*opt_spec_str = s.slice_from(i);
return true;
}
match s.char_at(i) {
'-' => {
opt_spec.short_flag_valid = false;
i += 1;
if i == s.char_count() {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(i - 1)
));
return false;
}
}
'/' => {
i += 1; // the struct is initialized assuming short_flag_valid should be true
if i == s.char_count() {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_INVALID_OPT_SPEC,
opts.name,
option_spec,
s.char_at(i - 1)
));
return false;
}
}
'#' => {
if opts.implicit_int_flag != '\0' {
streams.err.append(wgettext_fmt!(
"%ls: Implicit int flag '%lc' already defined\n",
opts.name,
opts.implicit_int_flag
));
return false;
}
opts.implicit_int_flag = opt_spec.short_flag;
opt_spec.num_allowed = ArgCardinality::Once;
i += 1; // the struct is initialized assuming short_flag_valid should be true
}
'!' | '?' | '=' => {
// Try to parse any other flag modifiers
// parse_flag_modifiers assumes opt_spec_str starts where it should, not one earlier
s = s.slice_from(i);
i = 0;
if !parse_flag_modifiers(opts, opt_spec, option_spec, &mut s, streams) {
return false;
}
}
_ => {
// No short flag separator and no other modifiers, so this is a long only option.
// Since getopt needs a wchar, we have a counter that we count up.
opt_spec.short_flag_valid = false;
i -= 1;
opt_spec.short_flag = char::from_u32(*counter).unwrap();
*counter += 1;
}
}
*opt_spec_str = s.slice_from(i);
return true;
}
fn parse_option_spec<'args>(
opts: &mut ArgParseCmdOpts<'args>,
option_spec: &'args wstr,
counter: &mut u32,
streams: &mut IoStreams,
) -> bool {
if option_spec.is_empty() {
streams.err.append(wgettext_fmt!(
"%ls: An option spec must have at least a short or a long flag\n",
opts.name
));
return false;
}
let mut s = option_spec;
if !fish_iswalnum(s.char_at(0)) && s.char_at(0) != '#' {
streams.err.append(wgettext_fmt!(
"%ls: Short flag '%lc' invalid, must be alphanum or '#'\n",
opts.name,
s.char_at(0)
));
return false;
}
let mut opt_spec = OptionSpec::new(s.char_at(0));
// Try parsing stuff after the short flag.
if s.char_count() > 1
&& !parse_option_spec_sep(opts, &mut opt_spec, option_spec, &mut s, counter, streams)
{
return false;
}
// Collect any long flag name.
if !s.is_empty() {
let long_flag_char_count = s
.chars()
.take_while(|&c| c == '-' || c == '_' || fish_iswalnum(c))
.count();
if long_flag_char_count > 0 {
opt_spec.long_flag = s.slice_to(long_flag_char_count);
if opts.long_to_short_flag.contains_key(opt_spec.long_flag) {
streams.err.append(wgettext_fmt!(
"%ls: Long flag '%ls' already defined\n",
opts.name,
opt_spec.long_flag
));
return false;
}
}
s = s.slice_from(long_flag_char_count);
}
if !parse_flag_modifiers(opts, &mut opt_spec, option_spec, &mut s, streams) {
return false;
}
// Record our long flag if we have one.
if !opt_spec.long_flag.is_empty() {
let ins = opts
.long_to_short_flag
.insert(WString::from(opt_spec.long_flag), opt_spec.short_flag);
assert!(ins.is_none(), "Should have inserted long flag");
}
// Record our option under its short flag.
opts.options.insert(opt_spec.short_flag, opt_spec);
return true;
}
fn collect_option_specs<'args>(
opts: &mut ArgParseCmdOpts<'args>,
optind: &mut usize,
argc: usize,
args: &[&'args wstr],
streams: &mut IoStreams,
) -> Option<c_int> {
let cmd: &wstr = args[0];
// A counter to give short chars to long-only options because getopt needs that.
// Luckily we have wgetopt so we can use wchars - this is one of the private use areas so we
// have 6400 options available.
let mut counter = 0xE000u32;
loop {
if *optind == argc {
streams
.err
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
return STATUS_INVALID_ARGS;
}
if "--" == args[*optind] {
*optind += 1;
break;
}
if !parse_option_spec(opts, args[*optind], &mut counter, streams) {
return STATUS_CMD_ERROR;
}
*optind += 1;
}
// Check for counter overreach once at the end because this is very unlikely to ever be reached.
let counter_max = 0xF8FFu32;
if counter > counter_max {
streams
.err
.append(wgettext_fmt!("%ls: Too many long-only options\n", cmd));
return STATUS_INVALID_ARGS;
}
return STATUS_CMD_OK;
}
fn parse_cmd_opts<'args>(
opts: &mut ArgParseCmdOpts<'args>,
optind: &mut usize,
argc: usize,
args: &mut [&'args wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> Option<c_int> {
let cmd = args[0];
let mut args_read = Vec::with_capacity(args.len());
args_read.extend_from_slice(args);
let mut w = wgetopter_t::new(SHORT_OPTIONS, LONG_OPTIONS, args);
while let Some(c) = w.wgetopt_long() {
match c {
'n' => opts.name = w.woptarg.unwrap().to_owned(),
's' => opts.stop_nonopt = true,
'i' => opts.ignore_unknown = true,
// Just save the raw string here. Later, when we have all the short and long flag
// definitions we'll parse these strings into a more useful data structure.
'x' => opts.raw_exclusive_flags.push(w.woptarg.unwrap()),
'h' => opts.print_help = true,
'N' => {
opts.min_args = {
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --min-args value '%ls'\n",
cmd,
w.woptarg.unwrap()
));
return STATUS_INVALID_ARGS;
}
x.try_into().unwrap()
}
}
'X' => {
opts.max_args = {
let x = fish_wcstol(w.woptarg.unwrap()).unwrap_or(-1);
if x < 0 {
streams.err.append(wgettext_fmt!(
"%ls: Invalid --max-args value '%ls'\n",
cmd,
w.woptarg.unwrap()
));
return STATUS_INVALID_ARGS;
}
x.try_into().unwrap()
}
}
':' => {
builtin_missing_argument(
parser,
streams,
cmd,
args[w.woptind - 1],
/* print_hints */ false,
);
return STATUS_INVALID_ARGS;
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.woptind - 1], false);
return STATUS_INVALID_ARGS;
}
_ => panic!("unexpected retval from wgetopt_long"),
}
}
if opts.print_help {
return STATUS_CMD_OK;
}
if "--" == args_read[w.woptind - 1] {
w.woptind -= 1;
}
if argc == w.woptind {
// The user didn't specify any option specs.
streams
.err
.append(wgettext_fmt!("%ls: Missing -- separator\n", cmd));
return STATUS_INVALID_ARGS;
}
if opts.name.is_empty() {
// If no name has been given, we default to the function name.
// If any error happens, the backtrace will show which argparse it was.
opts.name = parser
.get_function_name(1)
.unwrap_or_else(|| L!("argparse").to_owned());
}
*optind = w.woptind;
return collect_option_specs(opts, optind, argc, args, streams);
}
fn populate_option_strings<'args>(
opts: &ArgParseCmdOpts<'args>,
short_options: &mut WString,
long_options: &mut Vec<woption<'args>>,
) {
for opt_spec in opts.options.values() {
if opt_spec.short_flag_valid {
short_options.push(opt_spec.short_flag);
}
let arg_type = match opt_spec.num_allowed {
ArgCardinality::Optional => {
if opt_spec.short_flag_valid {
short_options.push_str("::");
}
woption_argument_t::optional_argument
}
ArgCardinality::Once | ArgCardinality::AtLeastOnce => {
if opt_spec.short_flag_valid {
short_options.push_str(":");
}
woption_argument_t::required_argument
}
ArgCardinality::None => woption_argument_t::no_argument,
};
if !opt_spec.long_flag.is_empty() {
long_options.push(wopt(opt_spec.long_flag, arg_type, opt_spec.short_flag));
}
}
}
fn validate_arg<'opts>(
parser: &Parser,
opts_name: &wstr,
opt_spec: &mut OptionSpec<'opts>,
is_long_flag: bool,
woptarg: &'opts wstr,
streams: &mut IoStreams,
) -> Option<c_int> {
// Obviously if there is no arg validation command we assume the arg is okay.
if opt_spec.validation_command.is_empty() {
return STATUS_CMD_OK;
}
let vars = parser.vars();
vars.push(true /* new_scope */);
let env_mode = EnvMode::LOCAL | EnvMode::EXPORT;
vars.set_one(L!("_argparse_cmd"), env_mode, opts_name.to_owned());
let flag_name = WString::from(VAR_NAME_PREFIX) + "name";
if is_long_flag {
vars.set_one(&flag_name, env_mode, opt_spec.long_flag.to_owned());
} else {
vars.set_one(
&flag_name,
env_mode,
WString::from_chars(vec![opt_spec.short_flag]),
);
}
vars.set_one(
&(WString::from(VAR_NAME_PREFIX) + "value"),
env_mode,
woptarg.to_owned(),
);
let mut cmd_output = Vec::new();
let retval = exec_subshell(
opt_spec.validation_command,
parser,
Some(&mut cmd_output),
false,
);
for output in cmd_output {
streams.err.append(output);
streams.err.append_char('\n');
}
vars.pop();
Some(retval)
}
/// \return whether the option 'opt' is an implicit integer option.
fn is_implicit_int(opts: &ArgParseCmdOpts, val: &wstr) -> bool {
if opts.implicit_int_flag == '\0' {
// There is no implicit integer option.
return false;
}
// We succeed if this argument can be parsed as an integer.
fish_wcstol(val).is_ok()
}
// Store this value under the implicit int option.
fn validate_and_store_implicit_int<'args>(
parser: &Parser,
opts: &mut ArgParseCmdOpts<'args>,
val: &'args wstr,
w: &mut wgetopter_t,
is_long_flag: bool,
streams: &mut IoStreams,
) -> Option<c_int> {
let opt_spec = opts.options.get_mut(&opts.implicit_int_flag).unwrap();
let retval = validate_arg(parser, &opts.name, opt_spec, is_long_flag, val, streams);
if retval != STATUS_CMD_OK {
return retval;
}
// It's a valid integer so store it and return success.
opt_spec.vals.clear();
opt_spec.vals.push(val.into());
opt_spec.num_seen += 1;
w.nextchar = L!("");
return STATUS_CMD_OK;
}
fn handle_flag<'args>(
parser: &Parser,
opts: &mut ArgParseCmdOpts<'args>,
opt: char,
is_long_flag: bool,
woptarg: Option<&'args wstr>,
streams: &mut IoStreams,
) -> Option<c_int> {
let opt_spec = opts.options.get_mut(&opt).unwrap();
opt_spec.num_seen += 1;
if opt_spec.num_allowed == ArgCardinality::None {
// It's a boolean flag. Save the flag we saw since it might be useful to know if the
// short or long flag was given.
assert!(woptarg.is_none());
let s = if is_long_flag {
WString::from("--") + opt_spec.long_flag
} else {
WString::from_chars(['-', opt_spec.short_flag])
};
opt_spec.vals.push(s);
return STATUS_CMD_OK;
}
if let Some(woptarg) = woptarg {
let retval = validate_arg(parser, &opts.name, opt_spec, is_long_flag, woptarg, streams);
if retval != STATUS_CMD_OK {
return retval;
}
}
match opt_spec.num_allowed {
ArgCardinality::Optional | ArgCardinality::Once => {
// We're depending on `wgetopt_long()` to report that a mandatory value is missing if
// `opt_spec->num_allowed == 1` and thus return ':' so that we don't take this branch if
// the mandatory arg is missing.
opt_spec.vals.clear();
if let Some(arg) = woptarg {
opt_spec.vals.push(arg.into());
}
}
_ => {
opt_spec.vals.push(woptarg.unwrap().into());
}
}
return STATUS_CMD_OK;
}
fn argparse_parse_flags<'args>(
parser: &Parser,
opts: &mut ArgParseCmdOpts<'args>,
argc: usize,
args: &mut [&'args wstr],
optind: &mut usize,
streams: &mut IoStreams,
) -> Option<c_int> {
let mut args_read = Vec::with_capacity(args.len());
args_read.extend_from_slice(args);
// "+" means stop at nonopt, "-" means give nonoptions the option character code `1`, and don't
// reorder.
let mut short_options = WString::from(if opts.stop_nonopt { L!("+:") } else { L!("-:") });
let mut long_options = vec![];
populate_option_strings(opts, &mut short_options, &mut long_options);
let mut long_idx: usize = usize::MAX;
let mut w = wgetopter_t::new(&short_options, &long_options, args);
while let Some(opt) = w.wgetopt_long_idx(&mut long_idx) {
let retval = match opt {
':' => {
builtin_missing_argument(
parser,
streams,
&opts.name,
args_read[w.woptind - 1],
false,
);
STATUS_INVALID_ARGS
}
'?' => {
// It's not a recognized flag. See if it's an implicit int flag.
let arg_contents = &args_read[w.woptind - 1].slice_from(1);
if is_implicit_int(opts, arg_contents) {
validate_and_store_implicit_int(
parser,
opts,
arg_contents,
&mut w,
long_idx != usize::MAX,
streams,
)
} else if !opts.ignore_unknown {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_UNKNOWN,
opts.name,
args_read[w.woptind - 1]
));
STATUS_INVALID_ARGS
} else {
// Any unrecognized option is put back if ignore_unknown is used.
// This allows reusing the same argv in multiple argparse calls,
// or just ignoring the error (e.g. in completions).
opts.args.push(args_read[w.woptind - 1]);
// Work around weirdness with wgetopt, which crashes if we `continue` here.
if w.woptind == argc {
break;
}
// Explain to wgetopt that we want to skip to the next arg,
// because we can't handle this opt group.
w.nextchar = L!("");
STATUS_CMD_OK
}
}
NONOPTION_CHAR_CODE => {
// A non-option argument.
// We use `-` as the first option-string-char to disable GNU getopt's reordering,
// otherwise we'd get ignored options first and normal arguments later.
// E.g. `argparse -i -- -t tango -w` needs to keep `-t tango -w` in $argv, not `-t -w
// tango`.
opts.args.push(args_read[w.woptind - 1]);
continue;
}
// It's a recognized flag.
_ => handle_flag(
parser,
opts,
opt,
long_idx != usize::MAX,
w.woptarg,
streams,
),
};
if retval != STATUS_CMD_OK {
return retval;
}
long_idx = usize::MAX;
}
*optind = w.woptind;
return STATUS_CMD_OK;
}
// This function mimics the `wgetopt_long()` usage found elsewhere in our other builtin commands.
// It's different in that the short and long option structures are constructed dynamically based on
// arguments provided to the `argparse` command.
fn argparse_parse_args<'args>(
opts: &mut ArgParseCmdOpts<'args>,
args: &mut [&'args wstr],
argc: usize,
parser: &Parser,
streams: &mut IoStreams,
) -> Option<c_int> {
if argc <= 1 {
return STATUS_CMD_OK;
}
let mut optind = 0usize;
let retval = argparse_parse_flags(parser, opts, argc, args, &mut optind, streams);
if retval != STATUS_CMD_OK {
return retval;
}
let retval = check_for_mutually_exclusive_flags(opts, streams);
if retval != STATUS_CMD_OK {
return retval;
}
opts.args.extend_from_slice(&args[optind..]);
return STATUS_CMD_OK;
}
fn check_min_max_args_constraints(
opts: &ArgParseCmdOpts,
streams: &mut IoStreams,
) -> Option<c_int> {
let cmd = &opts.name;
if opts.args.len() < opts.min_args {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_MIN_ARG_COUNT1,
cmd,
opts.min_args,
opts.args.len()
));
return STATUS_CMD_ERROR;
}
if opts.max_args != usize::MAX && opts.args.len() > opts.max_args {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_MAX_ARG_COUNT1,
cmd,
opts.max_args,
opts.args.len()
));
return STATUS_CMD_ERROR;
}
return STATUS_CMD_OK;
}
/// Put the result of parsing the supplied args into the caller environment as local vars.
fn set_argparse_result_vars(vars: &EnvStack, opts: &ArgParseCmdOpts) {
for opt_spec in opts.options.values() {
if opt_spec.num_seen == 0 {
continue;
}
if opt_spec.short_flag_valid {
let mut var_name = WString::from(VAR_NAME_PREFIX);
var_name.push(opt_spec.short_flag);
vars.set(&var_name, EnvMode::LOCAL, opt_spec.vals.clone());
}
if !opt_spec.long_flag.is_empty() {
// We do a simple replacement of all non alphanum chars rather than calling
// escape_string(long_flag, 0, STRING_STYLE_VAR).
let long_flag = opt_spec
.long_flag
.chars()
.map(|c| if fish_iswalnum(c) { c } else { '_' });
let var_name_long: WString = VAR_NAME_PREFIX.chars().chain(long_flag).collect();
vars.set(&var_name_long, EnvMode::LOCAL, opt_spec.vals.clone());
}
}
let args = opts.args.iter().map(|&s| s.to_owned()).collect();
vars.set(L!("argv"), EnvMode::LOCAL, args);
}
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
/// command. That's because fish doesn't have the weird quoting problems of POSIX shells. So we
/// don't need to support flags like `--unquoted`. Similarly we don't want to support introducing
/// long options with a single dash so we don't support the `--alternative` flag. That `getopt` is
/// an external command also means its output has to be in a form that can be eval'd. Because our
/// version is a builtin it can directly set variables local to the current scope (e.g., a
/// function). It doesn't need to write anything to stdout that then needs to be eval'd.
pub fn argparse(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
let cmd = args[0];
let argc = args.len();
let mut opts = ArgParseCmdOpts::new();
let mut optind = 0usize;
let retval = parse_cmd_opts(&mut opts, &mut optind, argc, args, parser, streams);
if retval != STATUS_CMD_OK {
// This is an error in argparse usage, so we append the error trailer with a stack trace.
// The other errors are an error in using *the command* that is using argparse,
// so our help doesn't apply.
builtin_print_error_trailer(parser, streams.err, cmd);
return retval;
}
if opts.print_help {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
let retval = parse_exclusive_args(&mut opts, streams);
if retval != STATUS_CMD_OK {
return retval;
}
// wgetopt expects the first argument to be the command, and skips it.
// if optind was 0 we'd already have returned.
assert!(optind > 0, "Optind is 0?");
let retval = argparse_parse_args(
&mut opts,
&mut args[optind - 1..],
argc - optind + 1,
parser,
streams,
);
if retval != STATUS_CMD_OK {
return retval;
}
let retval = check_min_max_args_constraints(&opts, streams);
if retval != STATUS_CMD_OK {
return retval;
}
set_argparse_result_vars(parser.vars(), &opts);
return retval;
}