summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2023-03-13 22:03:58 +0200
committerJustin Wernick <justin@worthe-it.co.za>2023-03-13 22:03:58 +0200
commit3035f8d52efe33749a8c027e193559ee7dd4c357 (patch)
treed01d741f8d358e6be300fbb0527932c2478e12d0 /src
parent8441f091437418c268d619e4eb67cb75818ef31e (diff)
Git clone / fetch
Diffstat (limited to 'src')
-rw-r--r--src/main.rs50
-rw-r--r--src/parser.rs132
2 files changed, 106 insertions, 76 deletions
diff --git a/src/main.rs b/src/main.rs
index ba29032..6f8c1d9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,8 +2,8 @@ mod git;
mod parser;
use clap::Parser;
-use parser::Command;
-use std::{io, io::Write, ops::ControlFlow, process};
+use parser::*;
+use std::{io, io::Write, ops::ControlFlow, process::Command};
use thiserror::Error;
/// Shackle Shell - A replacement for git-shell with repo management commands built in.
@@ -42,29 +42,41 @@ fn main() -> Result<(), ShackleError> {
}
fn run_command(user_input: String) -> Result<ControlFlow<(), ()>, ShackleError> {
- match user_input.parse::<Command>() {
- Err(unknown_input) => {
- println!("Unknown input \"{}\"", unknown_input);
+ match user_input.parse::<ShackleCommand>() {
+ Err(parse_error) => {
+ println!("{}", parse_error);
}
- Ok(Command::Whitespace) => {}
- Ok(Command::Exit) => {
+ Ok(ShackleCommand::Whitespace) => {}
+ Ok(ShackleCommand::Exit) => {
return Ok(ControlFlow::Break(()));
}
- Ok(Command::GitInit(repo_name)) => {
+ Ok(ShackleCommand::GitInit(GitInitArgs { repo_name })) => {
git::init(&repo_name)?; // TODO should report this error differently
println!("Successfully created {}.git", repo_name);
}
- Ok(Command::GitUploadPack(git_dir)) => {
- process::Command::new("git")
- .args(["upload-pack", &git_dir])
- .spawn()?
- .wait()?;
- }
- Ok(Command::GitReceivePack(git_dir)) => {
- process::Command::new("git")
- .args(["receive-pack", &git_dir])
- .spawn()?
- .wait()?;
+ Ok(ShackleCommand::GitUploadPack(upload_pack_args)) => {
+ let mut command = Command::new("git-upload-pack");
+
+ if upload_pack_args.strict {
+ command.arg("strict");
+ }
+ if upload_pack_args.no_strict {
+ command.arg("no-strict");
+ }
+ if let Some(timeout) = upload_pack_args.timeout {
+ command.args(["timeout", &timeout.to_string()]);
+ }
+ if upload_pack_args.stateless_rpc {
+ command.arg("stateless-rpc");
+ }
+ if upload_pack_args.advertise_refs {
+ command.arg("advertise-refs");
+ }
+
+ // TODO: This should definitely be part of the arg parsing!
+ command.arg(&upload_pack_args.directory.trim_matches('\''));
+
+ command.spawn()?.wait()?;
}
}
Ok(ControlFlow::Continue(()))
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<u32>,
+ #[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<Self, Self::Err> {
- 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::<ShackleCommand>().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::<ShackleCommand>()
+ .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::<ShackleCommand>().unwrap(),
+ ShackleCommand::Whitespace
+ );
+ }
}