From 01b7b60dc260aa7cf1dab6c15db13a88d5d92ada Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Fri, 10 Oct 2025 00:55:51 -0700 Subject: basic structure of the interactive features of the CLI tool Change-Id: Ifec5dbbe20b2c625d448eba436620622af6c5120 --- src/commands.rs | 56 +++++++++++++++++++++++++++++++++ src/error.rs | 17 ++++++++++ src/main.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 src/commands.rs create mode 100644 src/error.rs (limited to 'src') 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 { + name: String, + pub implementation: fn(mode: Box, params: &[&str]) + -> Result>>, +} + + +pub struct CommandMap(HashMap>); + +impl CommandMap { + pub fn new() -> CommandMap { + CommandMap(HashMap::new()) + } + + pub fn get(self: &Self, name: &str) -> Option<&Command> { + self.0.get(name) + } + + pub fn insert(self: &mut Self, command: Command) + -> 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, params: &[&str]) + -> Result>>; +} + +impl Command { + pub fn new(name: impl Deref, + implementation: fn(mode: Box, params: &[&str]) + -> Result>>) + -> 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 = std::result::Result; + +#[derive(Debug)] +pub enum Error { + IO(std::io::Error), + Internal(String), +} + +impl From 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>, +} + +impl TopLevelMode { + fn new() -> Result { + 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, params: &[&str]) + -> Result>> + { + 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> + = 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> { + let mut stdout = std::io::stdout(); + stdout.write_all(format!("\n{} ", raw).as_bytes())?; + stdout.flush()?; + + Ok(std::io::stdin().lines().next().transpose()?) } -- cgit 1.4.1