summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/commands.rs56
-rw-r--r--src/error.rs17
-rw-r--r--src/main.rs96
3 files changed, 167 insertions, 2 deletions
diff --git a/src/commands.rs b/src/commands.rs
new file mode 100644
index 0000000..56cc853
--- /dev/null
+++ b/src/commands.rs
@@ -0,0 +1,56 @@
+#![forbid(unsafe_code)]
+use crate::error::*;
+
+use std::collections::HashMap;
+use std::ops::Deref;
+
+
+pub struct Command<M: Mode + ?Sized> {
+  name: String,
+  pub implementation: fn(mode: Box<M>, params: &[&str])
+    -> Result<Option<Box<dyn Mode>>>,
+}
+
+
+pub struct CommandMap<M: Mode + ?Sized>(HashMap<String, Command<M>>);
+
+impl<M: Mode> CommandMap<M> {
+  pub fn new() -> CommandMap<M> {
+    CommandMap(HashMap::new())
+  }
+
+  pub fn get(self: &Self, name: &str) -> Option<&Command<M>> {
+    self.0.get(name)
+  }
+
+  pub fn insert(self: &mut Self, command: Command<M>)
+    -> Result<()>
+  {
+    let name = command.name.to_string();
+
+    let _ = self.0.insert(name, command);
+
+    Ok(())
+  }
+}
+
+
+pub trait Mode {
+  fn prompt_text(&self) -> Result<&str>;
+  fn dispatch(self: Box<Self>, params: &[&str])
+    -> Result<Option<Box<dyn Mode>>>;
+}
+
+impl<M: Mode> Command<M> {
+  pub fn new(name: impl Deref<Target=str>,
+             implementation: fn(mode: Box<M>, params: &[&str])
+                               -> Result<Option<Box<dyn Mode>>>)
+    -> Self
+  {
+    Command {
+      name: name.to_string(),
+      implementation: implementation,
+    }
+  }
+}
+
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..86e7928
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,17 @@
+#![forbid(unsafe_code)]
+
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug)]
+pub enum Error {
+  IO(std::io::Error),
+  Internal(String),
+}
+
+impl From<std::io::Error> for Error {
+  fn from(value: std::io::Error) -> Self {
+    Error::IO(value)
+  }
+}
+
diff --git a/src/main.rs b/src/main.rs
index c9de2c5..80645df 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,98 @@
 #![forbid(unsafe_code)]
+use crate::commands::{ Command, CommandMap, Mode };
+use crate::error::*;
 
-fn main() {
-  println!("yay");
+use std::io::Write;
+
+mod commands;
+mod error;
+
+
+struct TopLevelMode {
+  command_map: Box<CommandMap<TopLevelMode>>,
+}
+
+impl TopLevelMode {
+  fn new() -> Result<Self> {
+    let mut command_map = Box::new(CommandMap::new());
+
+    command_map.insert(Command::new("help", |mode, _params| {
+      println!("help        Print this message.");
+      println!("quit        Exit the program.");
+
+      Ok(Some(mode))
+    }))?;
+
+    command_map.insert(Command::new("quit", |_mode, _params| {
+      Ok(None)
+    }))?;
+
+    Ok(TopLevelMode { command_map })
+  }
+}
+
+impl Mode for TopLevelMode {
+  fn prompt_text(&self) -> Result<&str> {
+    Ok("secrets>")
+  }
+
+  fn dispatch(self: Box<Self>, params: &[&str])
+    -> Result<Option<Box<dyn Mode>>>
+  {
+    if params.len() > 0 {
+      if let Some(command) = self.command_map.get(params[0]) {
+        (command.implementation)(self, &params[1..])
+      } else {
+        println!("No command named {}. Type help if you need help.",
+                 params[0]);
+        Ok(Some(self))
+      }
+    } else {
+      default_empty_command()?;
+
+      Ok(Some(self))
+    }
+  }
+}
+
+
+fn default_empty_command() -> Result<()> {
+  println!("No command? Type help if you need help.");
+
+  Ok(())
+}
+
+
+fn main() -> Result<()> {
+  help();
+
+  let mut current_mode: Option<Box<dyn Mode>>
+    = Some(Box::new(TopLevelMode::new()?));
+
+  while let Some(mode) = current_mode {
+    if let Some(input) = prompt(mode.prompt_text()?)? {
+      let words: Vec<&str> = input.split_whitespace().collect();
+
+      current_mode = mode.dispatch(&words[..])?;
+    } else {
+      current_mode = None;
+    }
+  }
+
+  Ok(())
+}
+
+
+fn help() {
+  println!("Type a command (there are none yet)");
+}
+
+
+fn prompt(raw: &str) -> Result<Option<String>> {
+  let mut stdout = std::io::stdout();
+  stdout.write_all(format!("\n{} ", raw).as_bytes())?;
+  stdout.flush()?;
+
+  Ok(std::io::stdin().lines().next().transpose()?)
 }