use crate::{
args::ArgTree,
error::Error,
parser::{self, Parser},
Result,
};
#[derive(PartialEq, PartialOrd, Eq, Ord)]
pub enum UsageState {
Start,
InUsage,
Command,
Optional,
Repeatable,
Required,
Argument,
End,
}
pub struct Docopt<'d> {
parser: Parser<'d>,
pub settings: Option<Settings>,
}
pub struct Settings {
leading_num_spaces: usize,
arguments_uses_equals_sign: bool,
arguments_are_upper_case: bool,
comma_separated_aliases: bool,
short_opt_first: bool,
}
impl Default for Settings {
fn default() -> Self {
Self {
leading_num_spaces: 2,
arguments_uses_equals_sign: true,
arguments_are_upper_case: false,
comma_separated_aliases: true,
short_opt_first: true,
}
}
}
impl<'d> Docopt<'d> {
pub const fn new(data: &'d str) -> Self {
Self {
parser: Parser::new(data, 0),
settings: None,
}
}
const fn parse_usage(mut self, program_name: &'d str) -> Result<'d, (ArgTree<'d>, Self)> {
let Some((_, parser)) = self.parser.split_once_delimiter("usage:", true) else {
return Err(Error::MissingUsage);
};
let Some((line, parser)) = parser.readline() else {
return Err(Error::MissingUsage);
};
let line = parser::trim(line);
if line.len() == 0 {
while let Some((line, _parser)) = parser.readline() {
let line = Parser::new(line, 0);
if !line.starts_with_num(" ", 2) {
return Err(Error::TooFewSpaces);
}
let Some(line) = line.skip(2) else {
return Err(Error::InvalidParserState);
};
let Some(line) = line.read_until(" ", false) else {
return Err(Error::InvalidParserState);
};
}
}
todo!()
}
const fn parse_options(mut self) -> Result<'d, ArgTree<'d>> {
let mut leading_space_count = 0;
let mut leading_dash_count = 0;
while let Some((line, parser)) = self.parser.readline() {
let line = Parser::new(line, 0);
if !line.starts_with_num(" ", 2) {
continue;
}
let Some(line) = line.skip(2) else {
return Err(Error::InvalidParserState);
};
while !line.starts_with_num(" ", 2) {
if !line.starts_with("-") {
continue;
}
let Some((name, line)) = line.read_until(" ", false) else {
return Err(Error::InvalidParserState);
};
if line.starts_with(" ") {
break;
}
if line.starts_with("<") {
line.read_until(">", false);
}
}
let Some((description, _)) = parser.readline() else {
continue;
};
}
todo!()
}
fn parse_option(parser: Parser<'d>) -> Result<((&'d str, Option<&'d str>), Parser<'d>)> {
if parser.starts_with("--") {
let Some((name, parser)) = parser.read_until_either(&[" ", "="], false) else {
return Err(Error::InvalidParserState);
};
let Some(parser) = parser.skip(1) else {
return Err(Error::InvalidParserState);
};
let (param, parser) = match Self::parse_parameter(parser) {
Ok((param, parser)) => (param, parser),
Err(_) => (None, parser),
};
Ok(((name, param), parser))
} else {
if !parser.starts_with("-") {
return Err(Error::NotAnOption);
}
let Some((name, parser)) = parser.read(2) else {
return Err(Error::InvalidParserState);
};
let parser = if parser.starts_with(" ") || parser.starts_with("=") {
match parser.skip(1) {
Some(p) => p,
None => return Err(Error::InvalidParserState),
}
} else {
parser
};
let (param, parser) = match Self::parse_parameter(parser) {
Ok((param, parser)) => (param, parser),
Err(_) => (None, parser),
};
Ok(((name, param), parser))
}
}
const fn parse_parameter(parser: Parser<'d>) -> Result<(Option<&'d str>, Parser<'d>)> {
match parser.read_while_alphanumeric() {
Some((param, parser)) => Ok((Some(param), parser)),
None => Err(Error::MalformedParameter),
}
}
const fn parse_description(&self, parser: Parser<'d>) -> Result<(&'d str, Option<&'d str>)> {
let Some((spaces, parser)) = parser.read_while_whitespace() else {
return Err(Error::InvalidParserState);
};
if let Some(settings) = &self.settings {
if settings.leading_num_spaces != spaces.len() {
return Err(Error::InvalidNumberOfSpaces);
}
} else {
if spaces.len() != 2 {
return Err(Error::InvalidNumberOfSpaces);
}
}
let Some((desc, parser)) = parser.split_once_delimiter("[default: ", true) else {
match parser.readline() {
Some((desc, _)) => return Ok((parser::trim_end(desc), None)),
None => return Err(Error::NotAnOption),
}
};
let Some((parameter_value, parser)) = parser.read_while_alphanumeric() else {
return Err(Error::MalformedParameter);
};
if !parser.starts_with("]") {
return Err(Error::MismatchingBrackets);
}
Ok((parser::trim(desc), Some(parameter_value)))
}
}
#[derive(PartialEq, PartialOrd, Eq, Ord)]
pub enum OptionsState {
Start,
Option,
Argument,
Description,
}
#[cfg(test)]
mod tests {
use super::*;
const USAGE: &'static str = r#"
Usage:
example_program --test=<I-Am-Argument>
Options:
-t, --test=<ARGUMENTO> I am a cute description
"#;
#[test]
fn parse_option() {
let parser = Parser::new("-t ABC --test=ABC I am a cute description", 0);
let ((name, param), mut parser) = Docopt::parse_option(parser).unwrap();
assert_eq!("-t", name);
assert_eq!(Some("ABC"), param);
parser = parser.skip(1).unwrap();
let ((name, param), _) = Docopt::parse_option(parser).unwrap();
assert_eq!("--test", name);
assert_eq!(Some("ABC"), param);
}
#[test]
fn parse_description() {
let parser = Parser::new(" I am a cute description [default: hello]", 0);
let docopt = Docopt {
parser: parser.clone(),
settings: None,
};
let (desc, value) = docopt.parse_description(parser).unwrap();
assert_eq!("I am a cute description", desc);
assert_eq!(Some("hello"), value);
}
}