From ec4735a44e9fa7f9d04411bd1d495e45623e45e7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 2 May 2022 06:21:52 -0500 Subject: [PATCH] docs: Add REPL example This is to help in cases like #3668 and #3673 --- Cargo.toml | 6 +++ examples/README.md | 3 ++ examples/multicall-hostname.rs | 3 +- examples/repl.rs | 95 ++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 examples/repl.rs diff --git a/Cargo.toml b/Cargo.toml index 316562ce160..aacc18d4446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,7 @@ rustversion = "1" trycmd = { version = "0.13", default-features = false, features = ["color-auto", "diff", "examples"] } humantime = "2" snapbox = "0.2.9" +shlex = "1.1.0" [[example]] name = "demo" @@ -182,6 +183,11 @@ name = "hostname" path = "examples/multicall-hostname.rs" required-features = ["unstable-multicall"] +[[example]] +name = "repl" +path = "examples/repl.rs" +required-features = ["unstable-multicall"] + [[example]] name = "01_quick" path = "examples/tutorial_builder/01_quick.rs" diff --git a/examples/README.md b/examples/README.md index 08f4f443ee5..42ba88991a5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -26,6 +26,9 @@ - hostname: [builder](multicall-hostname.md) - Topics: - Subcommands +- repl: [builder](repl.rs) + - Topics: + - Read-Eval-Print Loops / Custom command lines ## Contributing diff --git a/examples/multicall-hostname.rs b/examples/multicall-hostname.rs index 45542611059..2c89a14484a 100644 --- a/examples/multicall-hostname.rs +++ b/examples/multicall-hostname.rs @@ -4,14 +4,13 @@ use clap::Command; fn main() { let cmd = Command::new(env!("CARGO_CRATE_NAME")) + .multicall(true) .arg_required_else_help(true) .subcommand_value_name("APPLET") .subcommand_help_heading("APPLETS") .subcommand(Command::new("hostname").about("show hostname part of FQDN")) .subcommand(Command::new("dnsdomainname").about("show domain name part of FQDN")); - let cmd = cmd.multicall(true); - match cmd.get_matches().subcommand_name() { Some("hostname") => println!("www"), Some("dnsdomainname") => println!("example.com"), diff --git a/examples/repl.rs b/examples/repl.rs new file mode 100644 index 00000000000..ac88a7c26d4 --- /dev/null +++ b/examples/repl.rs @@ -0,0 +1,95 @@ +// Note: this requires the `unstable-multicall` feature + +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<'static> { + // 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) + .disable_help_flag(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) +}