/* * Copyright (c) 2023 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. * * This file incorporates work covered by the following copyright and permission * notice: * The Clear BSD License * * Copyright © 2017-2023 David Wildasin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted (subject to the limitations in the disclaimer * below) provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ use std::collections::HashMap; use crate::{ error::Error, errorkind::ErrorKind, opt::Opt, result::Result }; /// The core of the `getopt` crate. /// /// `Parser` is implemented as an iterator over the options present in the given /// argument vector. /// /// The method [`next`](#method.next) does the heavy lifting. /// /// # Examples /// /// ## Simplified usage: /// ``` /// # fn main() -> Result<(), Box> { /// use getopt::Opt; /// /// // args = ["program", "-abc", "foo"]; /// # let args: Vec = vec!["program", "-abc", "foo"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = getopt::Parser::new(&args, "ab:c"); /// /// assert_eq!(Some(Opt('a', None)), opts.next().transpose()?); /// assert_eq!(1, opts.index()); /// assert_eq!(Some(Opt('b', Some("c".to_string()))), opts.next().transpose()?); /// assert_eq!(2, opts.index()); /// assert_eq!(None, opts.next()); /// assert_eq!(2, opts.index()); /// assert_eq!("foo", args[opts.index()]); /// # Ok(()) /// # } /// ``` /// /// ## A more idiomatic example: /// ``` /// # fn main() -> Result<(), Box> { /// use getopt::Opt; /// /// // args = ["program", "-abc", "-d", "foo", "-e", "bar"]; /// # let mut args: Vec = vec!["program", "-abc", "-d", "foo", "-e", "bar"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = getopt::Parser::new(&args, "ab:cd:e"); /// /// let mut a_flag = false; /// let mut b_flag = String::new(); /// let mut c_flag = false; /// let mut d_flag = String::new(); /// let mut e_flag = false; /// /// loop { /// match opts.next().transpose()? { /// None => break, /// Some(opt) => match opt { /// Opt('a', None) => a_flag = true, /// Opt('b', Some(arg)) => b_flag = arg.clone(), /// Opt('c', None) => c_flag = true, /// Opt('d', Some(arg)) => d_flag = arg.clone(), /// Opt('e', None) => e_flag = true, /// _ => unreachable!(), /// }, /// } /// } /// /// let new_args = args.split_off(opts.index()); /// /// assert_eq!(true, a_flag); /// assert_eq!("c", b_flag); /// assert_eq!(false, c_flag); /// assert_eq!("foo", d_flag); /// assert_eq!(true, e_flag); /// /// assert_eq!(1, new_args.len()); /// assert_eq!("bar", new_args.first().unwrap()); /// # Ok(()) /// # } /// ``` #[derive(Debug, Eq, PartialEq)] pub struct Parser { opts: HashMap, args: Vec>, index: usize, point: usize, } impl Parser { /// Create a new `Parser`, which will process the arguments in `args` /// according to the options specified in `optstring`. /// /// For compatibility with /// [`std::env::args`](https://doc.rust-lang.org/std/env/fn.args.html), /// valid options are expected to begin at the second element of `args`, and /// `index` is /// initialised to `1`. /// If `args` is structured differently, be sure to call /// [`set_index`](#method.set_index) before the first invocation of /// [`next`](#method.next). /// /// `optstring` is a string of recognised option characters; if a character /// is followed by a colon (`:`), that option takes an argument. /// /// # Note: /// Transforming the OS-specific argument strings into a vector of `String`s /// is the sole responsibility of the calling program, as it involves some /// level of potential information loss (which this crate does not presume /// to handle unilaterally) and error handling (which would complicate the /// interface). pub fn new(args: &[String], optstring: &str) -> Self { let optstring: Vec = optstring.chars().collect(); let mut opts = HashMap::new(); let mut i = 0; let len = optstring.len(); while i < len { let j = i + 1; if j < len && optstring[j] == ':' { opts.insert(optstring[i], true); i += 1; } else { opts.insert(optstring[i], false); } i += 1; } Self { opts, // "explode" the args into a vector of character vectors, to allow // indexing args: args.iter().map(|e| e.chars().collect()).collect(), index: 1, point: 0, } } /// Return the current `index` of the parser. /// /// `args[index]` will always point to the the next element of `args`; when /// the parser is /// finished with an element, it will increment `index`. /// /// After the last option has been parsed (and [`next`](#method.next) is /// returning `None`), /// `index` will point to the first non-option argument. pub fn index(&self) -> usize { self.index } // `point` must be reset to 0 whenever `index` is changed /// Modify the current `index` of the parser. pub fn set_index(&mut self, value: usize) { self.index = value; self.point = 0; } /// Increment the current `index` of the parser. /// /// This use case is common enough to warrant its own optimised method. pub fn incr_index(&mut self) { self.index += 1; self.point = 0; } } impl Iterator for Parser { type Item = Result; /// Returns the next option, if any. /// /// Returns an [`Error`](struct.Error.html) if an unexpected option is /// encountered or if an /// expected argument is not found. /// /// Parsing stops at the first non-hyphenated argument; or at the first /// argument matching "-"; /// or after the first argument matching "--". /// /// When no more options are available, `next` returns `None`. /// /// # Examples /// /// ## "-" /// ``` /// use getopt::Parser; /// /// // args = ["program", "-", "-a"]; /// # let args: Vec = vec!["program", "-", "-a"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = Parser::new(&args, "a"); /// /// assert_eq!(None, opts.next()); /// assert_eq!("-", args[opts.index()]); /// ``` /// /// ## "--" /// ``` /// use getopt::Parser; /// /// // args = ["program", "--", "-a"]; /// # let args: Vec = vec!["program", "--", "-a"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = Parser::new(&args, "a"); /// /// assert_eq!(None, opts.next()); /// assert_eq!("-a", args[opts.index()]); /// ``` /// /// ## Unexpected option: /// ``` /// use getopt::Parser; /// /// // args = ["program", "-b"]; /// # let args: Vec = vec!["program", "-b"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = Parser::new(&args, "a"); /// /// assert_eq!( /// "unknown option -- 'b'".to_string(), /// opts.next().unwrap().unwrap_err().to_string() /// ); /// ``` /// /// ## Missing argument: /// ``` /// use getopt::Parser; /// /// // args = ["program", "-a"]; /// # let args: Vec = vec!["program", "-a"] /// # .into_iter() /// # .map(String::from) /// # .collect(); /// let mut opts = Parser::new(&args, "a:"); /// /// assert_eq!( /// "option requires an argument -- 'a'".to_string(), /// opts.next().unwrap().unwrap_err().to_string() /// ); /// ``` fn next(&mut self) -> Option> { if self.point == 0 { /* * Rationale excerpts below taken verbatim from "The Open Group Base * Specifications Issue 7, 2018 edition", IEEE Std 1003.1-2017 * (Revision of IEEE Std 1003.1-2008). * Copyright © 2001-2018 IEEE and The Open Group. */ /* * If, when getopt() is called: * argv[optind] is a null pointer * *argv[optind] is not the character '-' * argv[optind] points to the string "-" * getopt() shall return -1 without changing optind. */ if self.index >= self.args.len() || self.args[self.index].is_empty() || self.args[self.index][0] != '-' || self.args[self.index].len() == 1 { return None; } /* * If: * argv[optind] points to the string "--" * getopt() shall return -1 after incrementing index. */ if self.args[self.index][1] == '-' && self.args[self.index].len() == 2 { self.incr_index(); return None; } // move past the starting '-' self.point += 1; } let opt = self.args[self.index][self.point]; self.point += 1; match self.opts.get(&opt) { None => { if self.point >= self.args[self.index].len() { self.incr_index(); } Some(Err(Error::new(ErrorKind::UnknownOption, opt))) } Some(false) => { if self.point >= self.args[self.index].len() { self.incr_index(); } Some(Ok(Opt(opt, None))) } Some(true) => { let arg: String = if self.point >= self.args[self.index].len() { self.incr_index(); if self.index >= self.args.len() { return Some(Err(Error::new( ErrorKind::MissingArgument, opt, ))); } self.args[self.index].iter().collect() } else { self.args[self.index] .clone() .split_off(self.point) .iter() .collect() }; self.incr_index(); Some(Ok(Opt(opt, Some(arg)))) } } } }