repl/
repl.rs

1use std::io::Write;
2
3use clap::Command;
4
5fn main() -> Result<(), String> {
6    loop {
7        let line = readline()?;
8        let line = line.trim();
9        if line.is_empty() {
10            continue;
11        }
12
13        match respond(line) {
14            Ok(quit) => {
15                if quit {
16                    break;
17                }
18            }
19            Err(err) => {
20                write!(std::io::stdout(), "{err}").map_err(|e| e.to_string())?;
21                std::io::stdout().flush().map_err(|e| e.to_string())?;
22            }
23        }
24    }
25
26    Ok(())
27}
28
29fn respond(line: &str) -> Result<bool, String> {
30    let args = shlex::split(line).ok_or("error: Invalid quoting")?;
31    let matches = cli()
32        .try_get_matches_from(args)
33        .map_err(|e| e.to_string())?;
34    match matches.subcommand() {
35        Some(("ping", _matches)) => {
36            write!(std::io::stdout(), "Pong").map_err(|e| e.to_string())?;
37            std::io::stdout().flush().map_err(|e| e.to_string())?;
38        }
39        Some(("quit", _matches)) => {
40            write!(std::io::stdout(), "Exiting ...").map_err(|e| e.to_string())?;
41            std::io::stdout().flush().map_err(|e| e.to_string())?;
42            return Ok(true);
43        }
44        Some((name, _matches)) => unimplemented!("{name}"),
45        None => unreachable!("subcommand required"),
46    }
47
48    Ok(false)
49}
50
51fn cli() -> Command {
52    // strip out usage
53    const PARSER_TEMPLATE: &str = "\
54        {all-args}
55    ";
56    // strip out name/version
57    const APPLET_TEMPLATE: &str = "\
58        {about-with-newline}\n\
59        {usage-heading}\n    {usage}\n\
60        \n\
61        {all-args}{after-help}\
62    ";
63
64    Command::new("repl")
65        .multicall(true)
66        .arg_required_else_help(true)
67        .subcommand_required(true)
68        .subcommand_value_name("APPLET")
69        .subcommand_help_heading("APPLETS")
70        .help_template(PARSER_TEMPLATE)
71        .subcommand(
72            Command::new("ping")
73                .about("Get a response")
74                .help_template(APPLET_TEMPLATE),
75        )
76        .subcommand(
77            Command::new("quit")
78                .alias("exit")
79                .about("Quit the REPL")
80                .help_template(APPLET_TEMPLATE),
81        )
82}
83
84fn readline() -> Result<String, String> {
85    write!(std::io::stdout(), "$ ").map_err(|e| e.to_string())?;
86    std::io::stdout().flush().map_err(|e| e.to_string())?;
87    let mut buffer = String::new();
88    std::io::stdin()
89        .read_line(&mut buffer)
90        .map_err(|e| e.to_string())?;
91    Ok(buffer)
92}