use std::io::Write; use clap::Command; fn main() -> Result<(), String> { loop { let line = readline()?; let line = line.trim(); if line.is_empty() { continue; } match respond(line) { Ok(quit) => { if quit { break; } } Err(err) => { write!(std::io::stdout(), "{}", err).map_err(|e| e.to_string())?; std::io::stdout().flush().map_err(|e| e.to_string())?; } } } Ok(()) } fn respond(line: &str) -> Result { let args = shlex::split(line).ok_or("error: Invalid quoting")?; let matches = cli() .try_get_matches_from(&args) .map_err(|e| e.to_string())?; match matches.subcommand() { Some(("ping", _matches)) => { write!(std::io::stdout(), "Pong").map_err(|e| e.to_string())?; std::io::stdout().flush().map_err(|e| e.to_string())?; } Some(("quit", _matches)) => { write!(std::io::stdout(), "Exiting ...").map_err(|e| e.to_string())?; std::io::stdout().flush().map_err(|e| e.to_string())?; return Ok(true); } Some((name, _matches)) => unimplemented!("{}", name), None => unreachable!("subcommand required"), } Ok(false) } fn cli() -> Command { // strip out usage const PARSER_TEMPLATE: &str = "\ {all-args} "; // strip out name/version const APPLET_TEMPLATE: &str = "\ {about-with-newline}\n\ {usage-heading}\n {usage}\n\ \n\ {all-args}{after-help}\ "; Command::new("repl") .multicall(true) .arg_required_else_help(true) .subcommand_required(true) .subcommand_value_name("APPLET") .subcommand_help_heading("APPLETS") .help_template(PARSER_TEMPLATE) .subcommand( Command::new("ping") .about("Get a response") .help_template(APPLET_TEMPLATE), ) .subcommand( Command::new("quit") .alias("exit") .about("Quit the REPL") .help_template(APPLET_TEMPLATE), ) } fn readline() -> Result { write!(std::io::stdout(), "$ ").map_err(|e| e.to_string())?; std::io::stdout().flush().map_err(|e| e.to_string())?; let mut buffer = String::new(); std::io::stdin() .read_line(&mut buffer) .map_err(|e| e.to_string())?; Ok(buffer) }