diff options
| author | Irene Knapp <ireneista@internetsafetylabs.org> | 2025-10-10 00:55:51 -0700 |
|---|---|---|
| committer | Irene Knapp <ireneista@internetsafetylabs.org> | 2025-11-26 14:47:27 -0800 |
| commit | 01b7b60dc260aa7cf1dab6c15db13a88d5d92ada (patch) | |
| tree | 7cb838c4b6c941e6d67e7265438600d2bbcd15cd | |
| parent | a0061153aca2bb13b167efd6263a6fa83150e160 (diff) | |
basic structure of the interactive features of the CLI tool
Change-Id: Ifec5dbbe20b2c625d448eba436620622af6c5120
| -rw-r--r-- | src/commands.rs | 56 | ||||
| -rw-r--r-- | src/error.rs | 17 | ||||
| -rw-r--r-- | src/main.rs | 96 |
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, ¶ms[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()?) } |