use nom::{ branch::alt, bytes::complete::{is_not, tag}, character::complete::{multispace0, multispace1}, combinator::{eof, map, value}, error::ParseError, sequence::delimited, sequence::tuple, Finish, IResult, }; use std::str::FromStr; // TODO: It might work well to use clap and parse_from for this particular case #[derive(Clone)] pub enum Command { Whitespace, Exit, GitInit(String), GitUploadPack(String), GitReceivePack(String), } impl FromStr for Command { // the error must be owned as well type Err = String; fn from_str(s: &str) -> Result { match command_parser(s).finish() { Ok((remaining, command)) => { if remaining.trim().is_empty() { Ok(command) } else { Err(s.trim().to_owned()) } } Err(_) => Err(s.trim().to_owned()), } } } fn command_parser(input: &str) -> IResult<&str, Command> { alt(( value(Command::Exit, eof), value(Command::Whitespace, tuple((multispace1, eof))), value(Command::Exit, ws(tag("exit"))), map( ws(tuple((tag("git-init"), multispace1, not_space))), |(_, _, repo_name)| Command::GitInit(repo_name.to_owned()), ), map( ws(tuple((tag("git upload-pack"), multispace1, not_space))), |(_, _, git_dir)| Command::GitUploadPack(git_dir.to_owned()), ), map( ws(tuple((tag("git receive-pack"), multispace1, not_space))), |(_, _, git_dir)| Command::GitReceivePack(git_dir.to_owned()), ), ))(input) } /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and /// trailing whitespace, returning the output of `inner`. fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> where F: FnMut(&'a str) -> IResult<&'a str, O, E>, { delimited(multispace0, inner, multispace0) } fn not_space(s: &str) -> IResult<&str, &str> { is_not(" \t\r\n")(s) }