From 3035f8d52efe33749a8c027e193559ee7dd4c357 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Mon, 13 Mar 2023 22:03:58 +0200 Subject: Git clone / fetch --- src/parser.rs | 132 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 57 deletions(-) (limited to 'src/parser.rs') diff --git a/src/parser.rs b/src/parser.rs index 75ac333..ef7d5f8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,72 +1,90 @@ -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 clap::{Parser, Subcommand}; use std::str::FromStr; -// TODO: It might work well to use clap and parse_from for this particular case -#[derive(Clone)] -pub enum Command { +#[derive(Parser, Clone, Debug, PartialEq, Eq)] +#[command(name = "")] +pub enum ShackleCommand { + #[command(skip)] Whitespace, Exit, - GitInit(String), - GitUploadPack(String), - GitReceivePack(String), + GitInit(GitInitArgs), + GitUploadPack(GitUploadPackArgs), } -impl FromStr for Command { - // the error must be owned as well - type Err = String; +#[derive(Subcommand, Clone, Debug, PartialEq, Eq)] +pub enum GitCommand { + UploadPack(GitUploadPackArgs), +} + +#[derive(Parser, Clone, Debug, PartialEq, Eq)] +pub struct GitInitArgs { + pub repo_name: String, +} + +#[derive(Parser, Clone, Debug, PartialEq, Eq)] +pub struct GitUploadPackArgs { + #[arg(long)] + pub strict: bool, + #[arg(long)] + pub no_strict: bool, + #[arg(long)] + pub timeout: Option, + #[arg(long)] + pub stateless_rpc: bool, + #[arg(long)] + pub advertise_refs: bool, + pub directory: String, +} + +impl FromStr for ShackleCommand { + type Err = clap::error::Error; 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()), + let trimmed = s.trim(); + if s.len() == 0 { + Ok(ShackleCommand::Exit) + } else if trimmed.len() == 0 { + Ok(ShackleCommand::Whitespace) + } else { + ShackleCommand::try_parse_from([""].into_iter().chain(trimmed.split_whitespace())) } } } -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) -} +#[cfg(test)] +mod test { + use super::*; -/// 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) -} + #[test] + fn it_parses_exit_correctly() { + assert_eq!( + "exit".parse::().unwrap(), + ShackleCommand::Exit + ); + } -fn not_space(s: &str) -> IResult<&str, &str> { - is_not(" \t\r\n")(s) + #[test] + fn it_parses_git_upload_pack_correctly() { + assert_eq!( + "git-upload-pack --stateless-rpc foobar.git" + .parse::() + .unwrap(), + ShackleCommand::GitUploadPack(GitUploadPackArgs { + strict: false, + no_strict: false, + timeout: None, + stateless_rpc: true, + advertise_refs: false, + directory: "foobar.git".to_owned(), + }) + ); + } + + #[test] + fn it_parses_whitespace_correctly() { + assert_eq!( + " ".parse::().unwrap(), + ShackleCommand::Whitespace + ); + } } -- cgit v1.2.3