mod stream;
use {
alloc::{
collections::{
BTreeMap,
VecDeque,
btree_map::Entry,
},
sync::Arc,
},
core::{
any::TypeId,
fmt::Debug,
mem,
str::FromStr,
},
std::{
env,
fs::File,
io::{BufReader, Error, ErrorKind, Read},
path::Path,
sync::RwLock,
},
crate::{MergeOption, Result},
self::{
kind::Kind,
stream::*,
value::Value,
},
};
#[cfg(test)]
mod tests;
mod kind;
mod value;
type Options = BTreeMap<String, Vec<Value>>;
const TRUE_AS_STR: &str = "true";
const FALSE_AS_STR: &str = "false";
pub const DIA_ARGS_FILE_FORMAT: &str = concat!(
"Argument file format:\n\n",
"- Empty lines or lines starting with `#` will be ignored.\n",
"- Each command, argument, or option must be placed on a separate line.\n",
"- Option key and value are separated by either: equal symbol `=` (can have leading/trailing white spaces), or at least one white space.",
' ', "Key and value will be trimmed.",
);
#[test]
fn test_dia_args_file_format() -> Result<()> {
use crate::docs::Docs;
Docs::new("Some Program".into(), format!("This program does something.\n\n{}", DIA_ARGS_FILE_FORMAT).into()).print()?;
Ok(())
}
const DASH: char = '-';
#[derive(Default, Debug)]
pub struct Args {
args: VecDeque<Value>,
options: Options,
sub_args: Vec<String>,
}
impl Args {
pub fn cmd(&self) -> Option<&str> {
match self.args.front() {
None => None,
Some(Value::Owned(s)) => Some(s.as_ref()),
Some(Value::Borrowed(_)) => None,
}
}
pub fn sub_args(&self) -> &[String] {
&self.sub_args
}
pub fn is_empty(&self) -> bool {
self.options.is_empty() && self.sub_args.is_empty() && self.args.iter().filter(|a| match a {
Value::Owned(_) => true,
Value::Borrowed(a) => match a.try_read() {
Ok(a) => a.is_some(),
Err(_) => true,
},
}).count() == usize::MIN
}
pub fn try_into_sub_cmd(mut self) -> Result<(Option<String>, Self)> {
self.clean_up_args()?;
match self.args.pop_front() {
None => Ok((None, self)),
Some(Value::Owned(s)) => Ok((Some(s), self)),
Some(Value::Borrowed(_)) => Err(Error::new(ErrorKind::Unsupported, "The first argument is not owned")),
}
}
pub fn take<T>(&mut self, keys: &[&str]) -> Result<Option<T>> where T: FromStr + 'static, <T as FromStr>::Err: Debug {
let mut result = None;
for key in keys {
let parse = |s| T::from_str(s).map_err(|err|
Error::new(ErrorKind::InvalidData, format!("Failed parsing value {s:?} of {key:?}: {err:?}"))
);
if let Some(mut values) = self.options.remove(*key) {
if result.is_some() {
return Err(Error::new(ErrorKind::InvalidData, format!("Duplicate value for {key:?}")));
}
match values.len() {
0 => if TypeId::of::<T>() == TypeId::of::<bool>() {
result = Some(T::from_str(TRUE_AS_STR).map_err(|_| err!())?);
} else {
return Err(Error::new(ErrorKind::InvalidData, format!("Missing value for {key:?}")));
},
1 => match values.remove(usize::MIN) {
Value::Owned(v) => result = Some(parse(&v)?),
Value::Borrowed(v) => {
let mut v = v.try_write().map_err(|e| err!("{e}"))?;
let s = v.take().ok_or_else(|| err!())?;
if TypeId::of::<T>() == TypeId::of::<bool>() {
match s.as_str() {
TRUE_AS_STR | FALSE_AS_STR => {
result = Some(parse(&s)?);
drop(v);
self.clean_up_args()?;
},
_ => {
*v = Some(s);
result = Some(T::from_str(TRUE_AS_STR).map_err(|_| err!())?);
},
};
} else {
result = Some(parse(&s)?);
drop(v);
self.clean_up_args()?;
}
},
},
_ => return Err(Error::new(ErrorKind::InvalidData, format!("Expected 1 value, got: {values:?}"))),
};
}
}
Ok(result)
}
fn clean_up_args(&mut self) -> Result<()> {
let args = VecDeque::with_capacity(self.args.len());
self.args = mem::take(&mut self.args).into_iter().try_fold(args, |mut result, next| {
match &next {
Value::Owned(_) => result.push_back(next),
Value::Borrowed(a) => if a.try_read().map_err(|e| err!("{e}"))?.is_some() {
result.push_back(next);
},
};
Result::Ok(result)
})?;
Ok(())
}
pub fn take_vec<T>(&mut self, keys: &[&str]) -> Result<Option<Vec<T>>> where T: FromStr, <T as FromStr>::Err: Debug {
let mut result: Option<Vec<_>> = None;
for key in keys {
if let Some(values) = self.options.remove(*key) {
let count = values.len();
for (index, value) in values.into_iter().enumerate() {
let value = match value {
Value::Owned(s) => s,
Value::Borrowed(s) => {
let s = s.try_write().map_err(|e| err!("{e}"))?.take().ok_or_else(|| err!())?;
self.clean_up_args()?;
s
},
};
let value = T::from_str(&value)
.map_err(|err| Error::new(ErrorKind::InvalidData, format!("Failed parsing value {value:?} of {key:?}: {err:?}")))?;
match result.as_mut() {
Some(result) => {
if index == usize::MIN {
result.reserve(count);
}
result.push(value);
},
None => result = Some({
let mut result = Vec::with_capacity(count);
result.push(value);
result
}),
};
}
}
}
Ok(result)
}
pub fn take_args(&mut self) -> Result<Vec<String>> {
{
let has_options = self.options.is_empty() == false;
for a in &self.args {
match a {
Value::Owned(_) => continue,
Value::Borrowed(a) => if has_options {
return Err(Error::new(ErrorKind::InvalidData, format!("This argument is not owned: {a:?}")));
},
};
}
}
let result = Vec::with_capacity(self.args.len());
self.args.drain(..).try_fold(result, |mut result, next| {
match next {
Value::Owned(s) => result.push(s),
Value::Borrowed(s) => if let Some(s) = s.try_write().map_err(|e| err!("{e}"))?.take() {
result.push(s);
},
};
Ok(result)
})
}
pub fn take_sub_args(&mut self) -> Vec<String> {
mem::take(&mut self.sub_args)
}
pub fn merge_options(&mut self, other: &mut Self, filter: &[&[&str]], merge_option: MergeOption) -> Result<usize> {
if filter.is_empty() || filter.iter().any(|keys| keys.is_empty()) {
return Err(Error::new(ErrorKind::InvalidInput, format!("Invalid filter: {:?}", filter)));
}
let mut count = 0;
for (key, other_value) in mem::take(&mut other.options) {
let keys = {
match filter.iter().find(|keys| keys.contains(&key.as_str())) {
Some(keys) => keys,
None => {
other.options.insert(key, other_value);
continue;
},
}
};
match merge_option {
MergeOption::TakeAll => keys.iter().for_each(|k| drop(self.options.remove(*k))),
MergeOption::IgnoreExisting => if keys.iter().any(|k| self.options.contains_key(*k)) {
other.options.insert(key, other_value);
continue;
},
};
self.options.insert(key, other_value);
count += 1;
}
Ok(count)
}
}
pub fn parse_strings<S, I>(args: I) -> Result<Args> where S: AsRef<str>, I: IntoIterator<Item=S> {
let args = args.into_iter();
let mut result = Args {
args: VecDeque::with_capacity(args.size_hint().0),
options: BTreeMap::new(),
sub_args: Vec::new(),
};
let mut args = args.peekable();
while let Some(arg) = args.next() {
let arg = arg.as_ref();
match Kind::parse(arg)? {
Kind::Command => {
let arg = arg.trim();
if arg.is_empty() == false {
result.args.push_back(Value::Owned(arg.to_string()));
}
},
Kind::ShortOption | Kind::LongOption => {
let value = match args.peek().map(|s| s.as_ref().starts_with(DASH)) {
Some(true) | None => None,
Some(false) => {
let value = Arc::new(RwLock::new(Some(args.next().map(|s| s.as_ref().to_string()).ok_or_else(||
Error::new(ErrorKind::InvalidData, format!("Missing value for {:?}", &arg))
)?)));
result.args.push_back(Value::Borrowed(value.clone()));
Some(Value::Borrowed(value))
},
};
add_option(&mut result.options, arg.to_string(), value)?;
},
Kind::ShortOptionWithValue { option, value } | Kind::LongOptionWithValue { option, value } => {
add_option(&mut result.options, option, Some(Value::Owned(value)))?;
},
Kind::SubArgsSeparator => {
result.sub_args = args.map(|s| s.as_ref().to_string()).collect();
break;
},
};
}
Ok(result)
}
fn add_option(options: &mut Options, option: String, value: Option<Value>) -> Result<()> {
match options.entry(option) {
Entry::Vacant(vacant) => drop(match value {
None => vacant.insert(vec!()),
Some(value) => vacant.insert(vec!(value)),
}),
Entry::Occupied(mut occupied) => match value {
None => return Err(Error::new(ErrorKind::InvalidData, format!("Expected 1 value for {:?}", occupied.key()))),
Some(value) => occupied.get_mut().push(value),
},
};
Ok(())
}
pub fn parse() -> Result<Args> {
parse_strings(env::args().skip(1))
}
pub fn parse_file<P>(file: Option<P>, max_size: Option<u64>) -> Result<Option<Args>> where P: AsRef<Path> {
let max_size = max_size.unwrap_or(crate::MAX_DIA_ARGS_FILE_SIZE);
if max_size == 0 {
return Err(Error::new(ErrorKind::InvalidInput, "max_size must be larger than 0"));
}
let current_exe = env::current_exe()?;
let file = match file.map(|f| f.as_ref().to_path_buf()) {
Some(file) => file,
None => match current_exe.parent() {
Some(dir) => dir.join(crate::DIA_ARGS_FILE_NAME),
None => return Err(Error::new(ErrorKind::NotFound, "Could not find parent directory of the program")),
},
};
let file = if file.exists() {
if file.is_file() {
match file.canonicalize() {
Ok(file) => file,
Err(err) => return Err(Error::new(err.kind(), format!("Failed getting canonical path of {file:?}: {err:?}"))),
}
} else {
return Err(Error::new(ErrorKind::InvalidInput, format!("Not a file: {file:?}")));
}
} else {
return Ok(None);
};
let file_size = file
.metadata().map_err(|e| Error::new(ErrorKind::Other, format!("Failed getting file size of {file:?}: {e:?}")))?
.len();
if file_size > max_size {
return Err(Error::new(ErrorKind::InvalidInput, format!("File too large: {file:?} (max allowed: {max_size})")));
}
let mut args: Vec<String> = vec![];
for line in read_file_to_string(file)?.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with(DASH) == false || false == ['=', ' ', '\t'].iter().any(|separator| match line.find(*separator) {
Some(idx) => {
args.push(format!("{}={}", line[..idx].trim(), line[idx + 1..].trim()));
true
},
None => false,
}) {
args.push(line.to_owned());
}
}
parse_strings(args.into_iter()).map(|args| Some(args))
}
fn read_file_to_string<P>(file: P) -> Result<String> where P: AsRef<Path> {
const BUF_SIZE: usize = 8 * 1024;
let file = file.as_ref();
let limit = file.metadata()?.len();
let mut reader = BufReader::new(File::open(file)?).take(limit);
let mut buf = [0; BUF_SIZE];
let mut data = Vec::with_capacity(limit.try_into().map_err(|_| err!())?);
loop {
match reader.read(&mut buf)? {
0 => return String::from_utf8(data).map_err(|e| Error::new(ErrorKind::InvalidData, e)),
read => data.extend(&buf[..read]),
};
}
}
pub fn parse_stream<R>(stream: &mut R, max_size: Option<u64>) -> Result<Args> where R: Read {
let max_size = max_size.unwrap_or(crate::MAX_DIA_ARGS_FILE_SIZE);
if max_size == 0 {
return Err(Error::new(ErrorKind::InvalidInput, "max_size must be larger than 0"));
}
let mut strings = Vec::with_capacity(128);
for s in Stream::make(stream, max_size)? {
strings.push(s?);
}
parse_strings(strings.into_iter())
}