#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(unused_parens)]
#![allow(non_snake_case)]
#![deny(rustdoc::bare_urls)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::missing_crate_level_docs)]
#![deny(rustdoc::invalid_codeblock_attributes)]
#![deny(rustdoc::invalid_rust_codeblocks)]
#![cfg_attr(all(feature = "no_std", not(test)), no_std)]
#[cfg(not(feature = "no_std"))]
use { std::collections::HashMap,
std::io::ErrorKind,
std::io::Error,
std::io::Result
};
#[cfg(feature = "no_std")]
extern crate alloc;
#[cfg(feature = "no_std")]
use {
alloc::collections::BTreeMap as HashMap,
alloc::vec::Vec,
alloc::format,
alloc::string::String,
alloc::string::ToString,
core::error::Error
};
pub struct getopt {
pub options : HashMap <char,String>,
pub arguments : Vec <String>,
pub option_has_arg: HashMap <char, bool>
}
impl getopt {
pub fn len(self: &Self) -> usize {
self.arguments.len()
}
pub fn iter(&self) -> std::slice::Iter<'_, String> {
self.arguments.iter()
}
pub fn get(&self, option: char ) -> Option<&String>
{
self.options.get(&option)
}
pub fn has(&self, option: char ) -> bool
{
self.options.contains_key(&option)
}
pub fn is_empty(&self) -> bool {
self.arguments.is_empty()
}
}
impl std::ops::Index<usize> for getopt {
type Output = String;
fn index(&self, index: usize) -> &Self::Output {
self.arguments.index(index)
}
}
impl IntoIterator for getopt {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.arguments.into_iter()
}
}
impl<'a> IntoIterator for &'a getopt {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.arguments.iter()
}
}
pub fn new(arg: impl IntoIterator<Item=impl AsRef<str>>, optstring: impl AsRef<str>) -> Result<getopt> {
let mut opts = HashMap::new();
let mut args = Vec::new();
let mut next_opt: Option<char> = None;
let mut stop_parsing = false;
let options_map = build_options_map(validate_optstring(optstring.as_ref())?);
for element in arg {
let element = element.as_ref();
if stop_parsing {
args.push(element.to_string());
} else if let Some(next_opt_char) = next_opt {
opts.insert(next_opt_char, element.to_string());
next_opt = None;
} else {
match parseElement(&element, &options_map) {
Ok((omap, el, nopt)) => {
opts.extend(omap);
if let Some(el_str) = el {
args.push(el_str);
}
next_opt = nopt;
}
Err(_) => stop_parsing = true,
}
}
}
if let Some(next_opt_char) = next_opt {
opts.insert(next_opt_char, String::new());
}
Ok ( getopt {options:opts, arguments:args, option_has_arg:options_map} )
}
fn validate_optstring(optstring: &str) -> Result<&str> {
if optstring.is_empty() {
Err(Error::new(ErrorKind::UnexpectedEof, "optstring can't be empty"))
} else if optstring.eq(":") {
Err(Error::new(ErrorKind::UnexpectedEof, "optstring can't be only ':'"))
} else {
for c in optstring.chars() {
match c {
'a'..='z' => Ok(()),
'A'..='Z' => Ok(()),
'0'..='9' => Ok(()),
'?' => Ok(()),
':' => Ok(()),
_ => Err(Error::new(ErrorKind::InvalidInput, "unsupported characters in optstring"))
}?
}
if optstring.contains("::") {
Err(Error::new(ErrorKind::InvalidInput, "double ':' are not permited in optstring"))
} else {
Ok(optstring)
}
}
}
fn build_options_map(optstring: &str) -> HashMap<char, bool> {
let mut rc : HashMap<char, bool> = HashMap::with_capacity(optstring.len());
let mut previous: char = ':';
let mut insert_one = |c:char| -> () { match c {
':' if previous != ':' => rc.insert(*&previous,true),
_ if previous != ':' => rc.insert(*&previous,false),
_ => None
}; previous = c;};
for c in optstring.chars() {
insert_one(c);
}
optstring.chars().last().filter(|c| *c != ':').into_iter().for_each(|c| insert_one(c));
rc
}
fn parseElement(element: &str, options_map: &HashMap<char, bool>) ->
Result<(HashMap<char,String>, Option<String>, Option<char>)> {
if( element == "--" ) {
Err( Error::new(ErrorKind::UnexpectedEof, "no more arguments") )
} else
if( element.is_empty() ) {
Err( Error::new(ErrorKind::InvalidInput, "element must not be empty") )
} else
if( ! element.starts_with('-') || element == "-" ) {
Ok( (HashMap::new(), Some(element.to_string()), None ) )
} else
{
let mut opts = HashMap::<char,String>::new();
let mut argfollows = false;
let mut opt_name: Option<char> = None;
let mut opt_argument = String::new();
for opt in element.chars().skip(1) {
if( argfollows == true ) {
opt_argument.push(opt);
} else {
match options_map.get(&opt) {
None => {
opts.insert(opt, String::new());
}
Some(optarg) => {
if( *optarg == true ) {
argfollows = true;
opt_name = Some(opt);
} else {
opts.insert(opt, String::new());
}
}
}
}
}
if ( argfollows == true ) {
if ( opt_argument.is_empty() ) {
Ok ( (opts, None, opt_name) )
} else {
opts.insert(opt_name.unwrap(), opt_argument);
Ok ( ( opts, None, None ) )
}
} else {
Ok ( (opts, None, None ) )
}
}
}
pub fn validate(getopt: getopt) -> Result<getopt> {
for opt in getopt.options.keys() {
if !getopt.option_has_arg.contains_key(opt) {
return Err(Error::new(ErrorKind::InvalidInput, format!("Unknown option -{}", opt)));
}
}
for (opt,_) in getopt.option_has_arg.iter().filter(|(_,arg)| **arg) {
if let Some(val) = getopt.options.get(opt) {
if val.is_empty() {
return Err(Error::new(ErrorKind::InvalidInput, format!("Option -{} does not have required argument", opt)));
}
}
}
Ok(getopt)
}
pub fn hideBin(argv: impl IntoIterator<Item=String>) -> impl IntoIterator<Item=String> {
argv.into_iter().skip(1)
}
#[cfg(feature = "posix")]
include!("getopt_posix.rs");
#[cfg(test)]
#[path = "getopt_helpers_tests.rs"]
mod helpers;
#[cfg(test)]
#[path = "getopt_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "getopt_validate_tests.rs"]
mod validations;
#[cfg(test)]
#[path = "getopt_hidebin_tests.rs"]
mod hidebin;
#[cfg(test)]
#[path = "getopt_doubledash_tests.rs"]
mod doubledash;
#[cfg(test)]
#[path = "getopt_iterator_tests.rs"]
mod it;
#[cfg(test)]
#[path = "getopt_index_tests.rs"]
mod idx;