fish-shell/fish-rust/src/wgetopt.rs

615 lines
23 KiB
Rust
Raw Normal View History

2023-01-15 06:56:24 +08:00
// A version of the getopt library for use with wide character strings.
//
/* Declarations for getopt.
Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc.
This file is part of the GNU C Library. Its master source is NOT part of
the C library, however. The master source lives in /gd/gnu/lib.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB. If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */
/// Note wgetopter expects an mutable array of const strings. It modifies the order of the
/// strings, but not their contents.
use crate::wchar::{utf32str, wstr, WExt};
// Describe how to deal with options that follow non-option ARGV-elements.
//
// If the caller did not specify anything, the default is PERMUTE.
//
// REQUIRE_ORDER means don't recognize them as options; stop option processing when the first
// non-option is seen. This is what Unix does. This mode of operation is selected by using `+'
// as the first character of the list of option characters.
//
// PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all
// the non-options are at the end. This allows options to be given in any order, even with
// programs that were not written to expect this.
//
// RETURN_IN_ORDER is an option available to programs that were written to expect options and
// other ARGV-elements in any order and that care about the ordering of the two. We describe
// each non-option ARGV-element as if it were the argument of an option with character code 1.
// Using `-' as the first character of the list of option characters selects this mode of
// operation.
//
// The special argument `--' forces an end of option-scanning regardless of the value of
// `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return EOF with
// `woptind' != ARGC.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2023-02-05 07:46:20 +08:00
#[allow(clippy::upper_case_acronyms)]
2023-01-15 06:56:24 +08:00
enum Ordering {
REQUIRE_ORDER,
PERMUTE,
RETURN_IN_ORDER,
}
impl Default for Ordering {
fn default() -> Self {
Ordering::PERMUTE
}
}
fn empty_wstr() -> &'static wstr {
Default::default()
}
pub struct wgetopter_t<'opts, 'args, 'argarray> {
// Argv.
argv: &'argarray mut [&'args wstr],
// For communication from `getopt' to the caller. When `getopt' finds an option that takes an
// argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each
// non-option ARGV-element is returned here.
pub woptarg: Option<&'args wstr>,
shortopts: &'opts wstr,
longopts: &'opts [woption<'opts>],
// The next char to be scanned in the option-element in which the last option character we
// returned was found. This allows us to pick up the scan where we left off.
//
// If this is empty, it means resume the scan by advancing to the next ARGV-element.
nextchar: &'args wstr,
// Index in ARGV of the next element to be scanned. This is used for communication to and from
// the caller and for communication between successive calls to `getopt'.
//
// On entry to `getopt', zero means this is the first call; initialize.
//
// When `getopt' returns EOF, this is the index of the first of the non-option elements that the
// caller should itself scan.
//
// Otherwise, `woptind' communicates from one call to the next how much of ARGV has been scanned
// so far.
// XXX 1003.2 says this must be 1 before any call.
pub woptind: usize,
// Set to an option character which was unrecognized.
woptopt: char,
// Describe how to deal with options that follow non-option ARGV-elements.
ordering: Ordering,
// Handle permutation of arguments.
// Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt'
// is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them.
pub first_nonopt: usize,
pub last_nonopt: usize,
missing_arg_return_colon: bool,
initialized: bool,
}
// Names for the values of the `has_arg' field of `woption'.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum woption_argument_t {
no_argument,
required_argument,
optional_argument,
}
/// Describe the long-named options requested by the application. The LONG_OPTIONS argument to
/// getopt_long or getopt_long_only is a vector of `struct option' terminated by an element
/// containing a name which is zero.
///
/// The field `has_arg' is:
/// no_argument (or 0) if the option does not take an argument,
/// required_argument (or 1) if the option requires an argument,
/// optional_argument (or 2) if the option takes an optional argument.
///
/// If the field `flag' is not NULL, it points to a variable that is set to the value given in the
/// field `val' when the option is found, but left unchanged if the option is not found.
///
/// To have a long-named option do something other than set an `int' to a compiled-in constant, such
/// as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a
/// nonzero value (the equivalent single-letter option character, if there is one). For long
/// options that have a zero `flag' field, `getopt' returns the contents of the `val' field.
#[derive(Debug, Clone, Copy)]
pub struct woption<'a> {
/// Long name for switch.
pub name: &'a wstr,
pub has_arg: woption_argument_t,
/// If \c flag is non-null, this is the value that flag will be set to. Otherwise, this is the
/// return-value of the function call.
pub val: char,
}
/// Helper function to create a woption.
2023-02-05 07:45:25 +08:00
pub const fn wopt(name: &wstr, has_arg: woption_argument_t, val: char) -> woption<'_> {
2023-01-15 06:56:24 +08:00
woption { name, has_arg, val }
}
impl<'opts, 'args, 'argarray> wgetopter_t<'opts, 'args, 'argarray> {
pub fn new(
shortopts: &'opts wstr,
longopts: &'opts [woption],
argv: &'argarray mut [&'args wstr],
) -> Self {
return wgetopter_t {
woptopt: '?',
argv,
shortopts,
longopts,
first_nonopt: 0,
initialized: false,
last_nonopt: 0,
missing_arg_return_colon: false,
nextchar: Default::default(),
ordering: Ordering::PERMUTE,
woptarg: None,
woptind: 0,
};
}
pub fn wgetopt_long(&mut self) -> Option<char> {
assert!(self.woptind <= self.argc(), "woptind is out of range");
let mut ignored = 0;
return self._wgetopt_internal(&mut ignored, false);
}
pub fn wgetopt_long_idx(&mut self, opt_index: &mut usize) -> Option<char> {
return self._wgetopt_internal(opt_index, false);
}
/// \return the number of arguments.
fn argc(&self) -> usize {
return self.argv.len();
}
// Exchange two adjacent subsequences of ARGV. One subsequence is elements
// [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The
// other is elements [last_nonopt,woptind), which contains all the options processed since those
// non-options were skipped.
//
// `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the
// non-options in ARGV after they are moved.
fn exchange(&mut self) {
let mut bottom = self.first_nonopt;
let middle = self.last_nonopt;
let mut top = self.woptind;
// Exchange the shorter segment with the far end of the longer segment. That puts the shorter
// segment into the right place. It leaves the longer segment in the right place overall, but it
// consists of two parts that need to be swapped next.
while top > middle && middle > bottom {
if top - middle > middle - bottom {
// Bottom segment is the short one.
let len = middle - bottom;
// Swap it with the top part of the top segment.
for i in 0..len {
self.argv.swap(bottom + i, top - (middle - bottom) + i);
}
// Exclude the moved bottom segment from further swapping.
top -= len;
} else {
// Top segment is the short one.
let len = top - middle;
// Swap it with the bottom part of the bottom segment.
for i in 0..len {
self.argv.swap(bottom + i, middle + i);
}
// Exclude the moved top segment from further swapping.
bottom += len;
}
}
// Update records for the slots the non-options now occupy.
self.first_nonopt += self.woptind - self.last_nonopt;
self.last_nonopt = self.woptind;
}
// Initialize the internal data when the first call is made.
fn _wgetopt_initialize(&mut self) {
// Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the
// sequence of previously skipped non-option ARGV-elements is empty.
self.first_nonopt = 1;
self.last_nonopt = 1;
self.woptind = 1;
self.nextchar = empty_wstr();
let mut optstring = self.shortopts;
// Determine how to handle the ordering of options and nonoptions.
if optstring.char_at(0) == '-' {
self.ordering = Ordering::RETURN_IN_ORDER;
optstring = &optstring[1..];
} else if optstring.char_at(0) == '+' {
self.ordering = Ordering::REQUIRE_ORDER;
optstring = &optstring[1..];
} else {
self.ordering = Ordering::PERMUTE;
}
if optstring.char_at(0) == ':' {
self.missing_arg_return_colon = true;
optstring = &optstring[1..];
}
self.shortopts = optstring;
self.initialized = true;
}
// Advance to the next ARGV-element.
// \return Some(\0) on success, or None or another value if we should stop.
fn _advance_to_next_argv(&mut self) -> Option<char> {
let argc = self.argc();
if self.ordering == Ordering::PERMUTE {
// If we have just processed some options following some non-options, exchange them so
// that the options come first.
if self.first_nonopt != self.last_nonopt && self.last_nonopt != self.woptind {
self.exchange();
} else if self.last_nonopt != self.woptind {
self.first_nonopt = self.woptind;
}
// Skip any additional non-options and extend the range of non-options previously
// skipped.
while self.woptind < argc
&& (self.argv[self.woptind].char_at(0) != '-' || self.argv[self.woptind].len() == 1)
{
self.woptind += 1;
}
self.last_nonopt = self.woptind;
}
// The special ARGV-element `--' means premature end of options. Skip it like a null option,
// then exchange with previous non-options as if it were an option, then skip everything
// else like a non-option.
if self.woptind != argc && self.argv[self.woptind] == "--" {
self.woptind += 1;
if self.first_nonopt != self.last_nonopt && self.last_nonopt != self.woptind {
self.exchange();
} else if self.first_nonopt == self.last_nonopt {
self.first_nonopt = self.woptind;
}
self.last_nonopt = argc;
self.woptind = argc;
}
// If we have done all the ARGV-elements, stop the scan and back over any non-options that
// we skipped and permuted.
if self.woptind == argc {
// Set the next-arg-index to point at the non-options that we previously skipped, so the
// caller will digest them.
if self.first_nonopt != self.last_nonopt {
self.woptind = self.first_nonopt;
}
return None;
}
// If we have come to a non-option and did not permute it, either stop the scan or describe
// it to the caller and pass it by.
if self.argv[self.woptind].char_at(0) != '-' || self.argv[self.woptind].len() == 1 {
if self.ordering == Ordering::REQUIRE_ORDER {
return None;
}
self.woptarg = Some(self.argv[self.woptind]);
self.woptind += 1;
return Some(char::from(1));
}
// We have found another option-ARGV-element. Skip the initial punctuation.
let skip = if !self.longopts.is_empty() && self.argv[self.woptind].char_at(1) == '-' {
2
} else {
1
};
self.nextchar = self.argv[self.woptind][skip..].into();
return Some(char::from(0));
}
// Check for a matching short opt.
fn _handle_short_opt(&mut self) -> char {
// Look at and handle the next short option-character.
let mut c = self.nextchar.char_at(0);
self.nextchar = &self.nextchar[1..];
let temp = match self.shortopts.chars().position(|sc| sc == c) {
Some(pos) => &self.shortopts[pos..],
None => utf32str!(""),
};
// Increment `woptind' when we start to process its last character.
if self.nextchar.is_empty() {
self.woptind += 1;
}
if temp.is_empty() || c == ':' {
self.woptopt = c;
if !self.nextchar.is_empty() {
self.woptind += 1;
}
return '?';
}
if temp.char_at(1) != ':' {
return c;
}
if temp.char_at(2) == ':' {
// This is an option that accepts an argument optionally.
if !self.nextchar.is_empty() {
2023-02-05 07:45:25 +08:00
self.woptarg = Some(self.nextchar);
2023-01-15 06:56:24 +08:00
self.woptind += 1;
} else {
self.woptarg = None;
}
self.nextchar = empty_wstr();
} else {
// This is an option that requires an argument.
if !self.nextchar.is_empty() {
2023-02-05 07:45:25 +08:00
self.woptarg = Some(self.nextchar);
2023-01-15 06:56:24 +08:00
// If we end this ARGV-element by taking the rest as an arg, we must advance to
// the next element now.
self.woptind += 1;
} else if self.woptind == self.argc() {
self.woptopt = c;
c = if self.missing_arg_return_colon {
':'
} else {
'?'
};
} else {
// We already incremented `woptind' once; increment it again when taking next
// ARGV-elt as argument.
self.woptarg = Some(self.argv[self.woptind]);
self.woptind += 1;
}
self.nextchar = empty_wstr();
}
return c;
}
fn _update_long_opt(
&mut self,
pfound: &woption,
nameend: usize,
longind: &mut usize,
option_index: usize,
retval: &mut char,
) {
self.woptind += 1;
assert!(self.nextchar.char_at(nameend) == '\0' || self.nextchar.char_at(nameend) == '=');
if self.nextchar.char_at(nameend) == '=' {
if pfound.has_arg != woption_argument_t::no_argument {
self.woptarg = Some(self.nextchar[(nameend + 1)..].into());
} else {
self.nextchar = empty_wstr();
*retval = '?';
return;
}
} else if pfound.has_arg == woption_argument_t::required_argument {
if self.woptind < self.argc() {
self.woptarg = Some(self.argv[self.woptind]);
self.woptind += 1;
} else {
self.nextchar = empty_wstr();
*retval = if self.missing_arg_return_colon {
':'
} else {
'?'
};
return;
}
}
self.nextchar = empty_wstr();
*longind = option_index;
*retval = pfound.val;
}
// Find a matching long opt.
fn _find_matching_long_opt(
&self,
nameend: usize,
exact: &mut bool,
ambig: &mut bool,
indfound: &mut usize,
) -> Option<woption<'opts>> {
let mut pfound: Option<woption> = None;
// Test all long options for either exact match or abbreviated matches.
2023-02-05 07:45:25 +08:00
for (option_index, p) in self.longopts.iter().enumerate() {
2023-01-15 06:56:24 +08:00
if p.name.starts_with(&self.nextchar[..nameend]) {
// Exact match found.
pfound = Some(*p);
*indfound = option_index;
*exact = true;
break;
} else if pfound.is_none() {
// First nonexact match found.
pfound = Some(*p);
*indfound = option_index;
} else {
// Second or later nonexact match found.
*ambig = true;
}
}
return pfound;
}
// Check for a matching long opt.
fn _handle_long_opt(
&mut self,
longind: &mut usize,
long_only: bool,
retval: &mut char,
) -> bool {
let mut exact = false;
let mut ambig = false;
let mut indfound: usize = 0;
let mut nameend = 0;
while self.nextchar.char_at(nameend) != '\0' && self.nextchar.char_at(nameend) != '=' {
nameend += 1;
}
let pfound = self._find_matching_long_opt(nameend, &mut exact, &mut ambig, &mut indfound);
if ambig && !exact {
self.nextchar = empty_wstr();
self.woptind += 1;
*retval = '?';
return true;
}
if let Some(pfound) = pfound {
self._update_long_opt(&pfound, nameend, longind, indfound, retval);
return true;
}
// Can't find it as a long option. If this is not getopt_long_only, or the option starts
// with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a
// short option.
if !long_only
|| self.argv[self.woptind].char_at(1) == '-'
|| !self
.shortopts
.as_char_slice()
.contains(&self.nextchar.char_at(0))
{
self.nextchar = empty_wstr();
self.woptind += 1;
*retval = '?';
return true;
}
return false;
}
// Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING.
//
// If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option
// element. The characters of this element (aside from the initial '-') are option characters. If
// `getopt' is called repeatedly, it returns successively each of the option characters from each of
// the option elements.
//
// If `getopt' finds another option character, it returns that character, updating `woptind' and
// `nextchar' so that the next call to `getopt' can resume the scan with the following option
// character or ARGV-element.
//
// If there are no more option characters, `getopt' returns `EOF'. Then `woptind' is the index in
// ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so
// that those that are not options now come last.)
//
// OPTSTRING is a string containing the legitimate option characters. If an option character is seen
// that is not listed in OPTSTRING, return '?'.
//
// If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text
// in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'.
// Two colons mean an option that wants an optional arg; if there is text in the current
// ARGV-element, it is returned in `w.woptarg', otherwise `w.woptarg' is set to zero.
//
// If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option
// ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
//
// Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the
// abbreviation is unique or is an exact match for some defined option. If they have an argument,
// it follows the option name in the same ARGV-element, separated from the option name by a `=', or
// else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that
// option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is
// zero.
//
// LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero.
//
// LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a
// long-named option has been found by the most recent call.
//
// If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options.
fn _wgetopt_internal(&mut self, longind: &mut usize, long_only: bool) -> Option<char> {
if !self.initialized {
self._wgetopt_initialize();
}
self.woptarg = None;
if self.nextchar.is_empty() {
let narg = self._advance_to_next_argv();
if narg != Some(char::from(0)) {
return narg;
}
}
// Decode the current option-ARGV-element.
// Check whether the ARGV-element is a long option.
//
// If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't
// consider it an abbreviated form of a long option that starts with f. Otherwise there would
// be no way to give the -f short option.
//
// On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do
// consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg
// "u".
//
// This distinction seems to be the most useful approach.
if !self.longopts.is_empty() && self.woptind < self.argc() {
let arg = self.argv[self.woptind];
2023-02-05 07:45:25 +08:00
2023-02-05 07:46:20 +08:00
#[allow(clippy::if_same_then_else)]
#[allow(clippy::needless_bool)]
2023-02-05 07:45:25 +08:00
let try_long = if arg.char_at(0) == '-' && arg.char_at(1) == '-' {
2023-01-15 06:56:24 +08:00
// Like --foo
2023-02-05 07:45:25 +08:00
true
2023-01-15 06:56:24 +08:00
} else if long_only && arg.len() >= 3 {
// Like -fu
2023-02-05 07:45:25 +08:00
true
2023-01-15 06:56:24 +08:00
} else if !self.shortopts.as_char_slice().contains(&arg.char_at(1)) {
// Like -f, but f is not a short arg.
2023-02-05 07:45:25 +08:00
true
} else {
false
};
2023-01-15 06:56:24 +08:00
if try_long {
let mut retval = '\0';
if self._handle_long_opt(longind, long_only, &mut retval) {
return Some(retval);
}
}
}
return Some(self._handle_short_opt());
}
}