summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2023-03-17 11:10:53 +0200
committerJustin Wernick <justin@worthe-it.co.za>2023-03-17 11:11:36 +0200
commitf5a00e9090b9d81936137c3fc676cfd0ad25430c (patch)
tree5384a422af6ec34685b31308caf2fd70f1d6114e
parentb541b141e66d5cf0ba4b190eda6422126951af6e (diff)
Use shlex to split shell arguments
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--readme.org2
-rw-r--r--src/main.rs4
-rw-r--r--src/parser.rs21
-rw-r--r--tests/cli.rs36
6 files changed, 64 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4fa6c51..ffc28bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -526,11 +526,18 @@ dependencies = [
"nom",
"once_cell",
"rexpect",
+ "shlex",
"tempfile",
"thiserror",
]
[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 81d8115..f2a906a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ clap = { version = "4.1.8", features = ["derive"] }
git2 = { version = "0.16.1", default-features = false, features = ["vendored-libgit2"] }
nom = "7.1.3"
rexpect = "0.5.0"
+shlex = "1.1.0"
thiserror = "1.0.38"
[dev-dependencies]
diff --git a/readme.org b/readme.org
index 6d68020..fe09dc5 100644
--- a/readme.org
+++ b/readme.org
@@ -21,7 +21,7 @@ Pijul.
- [X] Isolation of workdir between tests
- [X] git fetch with git upload-pack
- [X] git push with git receive-pack
-- [ ] proper shell argument lexing, with quote stuff https://lib.rs/crates/shlex
+- [X] proper shell argument lexing, with quote stuff https://lib.rs/crates/shlex
- [ ] restrict repos to only acceptable paths
- [ ] git init of shared repos
- [ ] history (only within same session) https://lib.rs/crates/rustyline
diff --git a/src/main.rs b/src/main.rs
index 50f5127..bcbfe1c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -51,8 +51,8 @@ fn run_command(user_input: String) -> Result<ControlFlow<(), ()>, ShackleError>
return Ok(ControlFlow::Break(()));
}
Ok(ShackleCommand::GitInit(GitInitArgs { repo_name })) => {
- git::init(&repo_name)?; // TODO should report this error differently
- println!("Successfully created {}.git", repo_name);
+ git::init(&repo_name)?;
+ println!("Successfully created \"{}.git\"", repo_name);
}
Ok(ShackleCommand::GitUploadPack(upload_pack_args)) => {
let mut command = Command::new("git-upload-pack");
diff --git a/src/parser.rs b/src/parser.rs
index 59dc7d8..89e0d76 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,5 +1,6 @@
use clap::{Parser, Subcommand};
use std::str::FromStr;
+use thiserror::Error;
#[derive(Parser, Clone, Debug, PartialEq, Eq)]
#[command(name = "")]
@@ -44,8 +45,16 @@ pub struct GitReceivePackArgs {
pub directory: String,
}
+#[derive(Error, Debug)]
+pub enum ParserError {
+ #[error(transparent)]
+ ClapError(#[from] clap::error::Error),
+ #[error("`{0}`")]
+ LexerError(String),
+}
+
impl FromStr for ShackleCommand {
- type Err = clap::error::Error;
+ type Err = ParserError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let trimmed = s.trim();
@@ -54,7 +63,15 @@ impl FromStr for ShackleCommand {
} else if trimmed.len() == 0 {
Ok(ShackleCommand::Whitespace)
} else {
- ShackleCommand::try_parse_from([""].into_iter().chain(trimmed.split_whitespace()))
+ let lexed = shlex::split(trimmed);
+ match lexed {
+ None => Err(ParserError::LexerError("Incomplete input".to_string())),
+ Some(lexed) => {
+ let parsed =
+ ShackleCommand::try_parse_from(["".to_owned()].into_iter().chain(lexed))?;
+ Ok(parsed)
+ }
+ }
}
}
}
diff --git a/tests/cli.rs b/tests/cli.rs
index 6ace151..e6a59b3 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -90,7 +90,7 @@ fn reports_error_with_nonsense_input() -> Result<()> {
fn can_init_a_new_git_repo() -> Result<()> {
let mut c = spawn_interactive_process()?;
c.p.send_line("git-init my-new-repo")?;
- c.p.exp_string("Successfully created my-new-repo.git")?;
+ c.p.exp_string("Successfully created \"my-new-repo.git\"")?;
expect_prompt(&mut c.p)?;
Command::new("git")
@@ -106,7 +106,39 @@ fn can_init_a_new_git_repo() -> Result<()> {
#[test]
fn runs_a_single_command_and_exit_with_cli_flag() -> Result<()> {
let mut c = run_batch_command("git-init another-new-repo")?;
- c.p.exp_string("Successfully created another-new-repo.git")?;
+ c.p.exp_string("Successfully created \"another-new-repo.git\"")?;
c.p.exp_eof()?;
Ok(())
}
+
+#[test]
+fn allows_quotes_arguments() -> Result<()> {
+ let mut c = spawn_interactive_process()?;
+ c.p.send_line("\"git-init\" 'another-new-repo'")?;
+ c.p.exp_string("Successfully created \"another-new-repo.git\"")?;
+ Ok(())
+}
+
+#[test]
+fn errors_with_an_open_double_quote() -> Result<()> {
+ let mut c = spawn_interactive_process()?;
+ c.p.send_line("\"git-init 'another-new-repo'")?;
+ c.p.exp_string("Incomplete input")?;
+ Ok(())
+}
+
+#[test]
+fn errors_with_an_open_single_quote() -> Result<()> {
+ let mut c = spawn_interactive_process()?;
+ c.p.send_line("'git-init 'another-new-repo'")?;
+ c.p.exp_string("Incomplete input")?;
+ Ok(())
+}
+
+#[test]
+fn allows_single_quotes_and_spaces_inside_double_quotes() -> Result<()> {
+ let mut c = spawn_interactive_process()?;
+ c.p.send_line("git-init \"shukkie's new repo\"")?;
+ c.p.exp_string("Successfully created \"shukkie's new repo.git\"")?;
+ Ok(())
+}