PicoBot/src/cli/input.rs

147 lines
4.0 KiB
Rust

use crate::bus::ChatMessage;
use super::channel::CliChannel;
pub enum InputEvent {
Message(ChatMessage),
Command(InputCommand),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputCommand {
Exit,
New(Option<String>),
Save(Option<String>),
Sessions,
Use(String),
}
pub struct InputHandler {
channel: CliChannel,
}
impl InputHandler {
pub fn new() -> Self {
Self {
channel: CliChannel::new(),
}
}
pub async fn read_input(&mut self, prompt: &str) -> Result<Option<InputEvent>, InputError> {
match self.channel.read_line(prompt).await {
Ok(Some(line)) => {
if line.trim().is_empty() {
return Ok(None);
}
if let Some(cmd) = self.handle_special_commands(&line) {
return Ok(Some(InputEvent::Command(cmd)));
}
Ok(Some(InputEvent::Message(ChatMessage::user(line))))
}
Ok(None) => Ok(None),
Err(e) => Err(InputError::IoError(e)),
}
}
pub async fn write_output(&mut self, content: &str) -> Result<(), InputError> {
self.channel
.write_line(content)
.await
.map_err(InputError::IoError)
}
pub async fn write_response(&mut self, content: &str) -> Result<(), InputError> {
self.channel
.write_response(content)
.await
.map_err(InputError::IoError)
}
fn handle_special_commands(&self, line: &str) -> Option<InputCommand> {
let trimmed = line.trim();
let mut parts = trimmed.splitn(2, char::is_whitespace);
let command = parts.next()?;
let arg = parts
.next()
.map(str::trim)
.filter(|value| !value.is_empty());
match command {
"/quit" | "/exit" | "/q" => Some(InputCommand::Exit),
"/new" => Some(InputCommand::New(arg.map(ToOwned::to_owned))),
"/save" => Some(InputCommand::Save(arg.map(ToOwned::to_owned))),
"/sessions" | "/list" => Some(InputCommand::Sessions),
"/use" => arg.map(|value| InputCommand::Use(value.to_string())),
_ => None,
}
}
}
impl Default for InputHandler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub enum InputError {
IoError(std::io::Error),
}
impl std::fmt::Display for InputError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InputError::IoError(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for InputError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_special_command_parsing() {
let handler = InputHandler::new();
assert_eq!(
handler.handle_special_commands("/quit"),
Some(InputCommand::Exit)
);
assert_eq!(
handler.handle_special_commands("/new"),
Some(InputCommand::New(None))
);
assert_eq!(
handler.handle_special_commands("/new planning"),
Some(InputCommand::New(Some("planning".to_string())))
);
assert_eq!(
handler.handle_special_commands("/save"),
Some(InputCommand::Save(None))
);
assert_eq!(
handler.handle_special_commands("/save ./debug/session.md"),
Some(InputCommand::Save(Some("./debug/session.md".to_string())))
);
assert_eq!(
handler.handle_special_commands("/list"),
Some(InputCommand::Sessions)
);
assert_eq!(
handler.handle_special_commands("/sessions"),
Some(InputCommand::Sessions)
);
assert_eq!(
handler.handle_special_commands("/use abc123"),
Some(InputCommand::Use("abc123".to_string()))
);
assert_eq!(handler.handle_special_commands("/unknown"), None);
assert_eq!(handler.handle_special_commands("/use"), None);
}
}