summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2023-02-27 23:12:32 +0200
committerJustin Wernick <justin@worthe-it.co.za>2023-02-27 23:12:32 +0200
commit9f0f47abaa934b66da5b302236bfc89f95a7f329 (patch)
tree37b20fa001c42e61da54ada3a905fda6463fe050 /src
parent5ac9161b7edabbb95c1f8a5d68af62a567687190 (diff)
Revamp parsing to support more complex commands
Diffstat (limited to 'src')
-rw-r--r--src/main.rs37
-rw-r--r--src/parser.rs61
2 files changed, 83 insertions, 15 deletions
diff --git a/src/main.rs b/src/main.rs
index 93880de..0df2754 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,38 +1,45 @@
-use std::error::Error;
use std::{io, io::Write};
+use thiserror::Error;
-fn prompt() -> Result<(), Box<dyn Error>> {
+mod parser;
+
+use parser::Command;
+
+fn prompt() -> Result<(), ShackleError> {
print!("> ");
io::stdout().flush()?;
Ok(())
}
-fn read_stdin() -> Result<String, Box<dyn Error>> {
+fn read_stdin() -> Result<String, ShackleError> {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
Ok(buffer)
}
-fn main() -> Result<(), Box<dyn Error>> {
+fn main() -> Result<(), ShackleError> {
loop {
prompt()?;
let user_input = read_stdin()?;
- if user_input.len() == 0 {
- // control-d or end of input. Needs to be specially handled before
- // the match because this is identical to whitespace after the trim.
- break;
- }
-
- match user_input.trim() {
- "" => {}
- "exit" => {
+ match user_input.parse::<Command>() {
+ Err(unknown_input) => {
+ println!("Unknown input \"{}\"", unknown_input);
+ }
+ Ok(Command::Whitespace) => {}
+ Ok(Command::Exit) => {
break;
}
- other_input => {
- println!("Unknown input {}", other_input);
+ Ok(Command::GitInit(repo_name)) => {
+ println!("Successfully created {}.git", repo_name);
}
}
}
Ok(())
}
+
+#[derive(Error, Debug)]
+enum ShackleError {
+ #[error(transparent)]
+ IoError(#[from] io::Error),
+}
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..2f65180
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,61 @@
+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;
+
+#[derive(Clone)]
+pub enum Command {
+ Whitespace,
+ Exit,
+ GitInit(String),
+}
+
+impl FromStr for Command {
+ // the error must be owned as well
+ type Err = String;
+
+ 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()),
+ }
+ }
+}
+
+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()),
+ ),
+ ))(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)
+}