shellfish/handler/
async_app.rs

1use std::collections::HashMap;
2use std::env;
3use std::path::PathBuf;
4
5use async_trait::async_trait;
6use yansi::Paint;
7
8use super::{AsyncHandler, CommandLineHandler};
9use crate::command::CommandType;
10use crate::Command;
11
12/// Shellfish's CLI handler. This is helpful for when you want to parse
13/// input from the command line, rather than in an interactive case.
14///
15/// The main differences are:
16///  * It expects the binary name to be first
17///  * Aswell as `help` one can use `--help`
18#[derive(Default, Clone, Eq, PartialEq)]
19pub struct DefaultAsyncCLIHandler {
20    pub proj_name: Option<String>,
21}
22
23impl CommandLineHandler for DefaultAsyncCLIHandler {
24    fn get_cache(&self) -> Option<PathBuf> {
25        let mut path = home::home_dir()?;
26        #[cfg(target_os = "windows")]
27        {
28            path.push("AppData");
29            path.push("Local");
30        }
31        #[cfg(target_os = "linux")]
32        {
33            path.push(".cache");
34        }
35        #[cfg(target_os = "macos")]
36        {
37            path.push("Library Support");
38        }
39        path.push(self.proj_name.as_ref().unwrap_or(&env::args().next()?));
40        path.push("shellfish.json");
41        Some(path)
42    }
43}
44
45#[async_trait]
46impl<T: Send> AsyncHandler<T> for DefaultAsyncCLIHandler {
47    async fn handle_async(
48        &self,
49        line: Vec<String>,
50        commands: &HashMap<&str, Command<T>>,
51        state: &mut T,
52        description: &str,
53    ) -> bool {
54        if let Some(command) = line.get(1) {
55            match command.as_str() {
56                "quit" | "exit" | "--quit" | "--exit" => return true,
57                "help" | "--help" => {
58                    // Print the binary name
59                    println!("{}", line[0]);
60
61                    // Description
62                    println!("{}", description);
63
64                    // Usage section
65                    println!("USAGE:");
66                    println!(
67                        "    {} [SUBCOMMAND]",
68                        self.proj_name.as_ref().unwrap_or(&line[0])
69                    );
70                    println!();
71
72                    // Subcommand section
73                    println!("Where [SUBCOMMAND] is one of:");
74
75                    // Create a list of commands
76                    let mut cmd_help = HashMap::new();
77                    let mut cmd_len = 4;
78
79                    // Add the built ins
80                    cmd_help.insert("help", "displays help information.");
81                    cmd_help.insert(
82                        "quit",
83                        "deletes all temporary state information.",
84                    );
85                    cmd_help.insert(
86                        "exit",
87                        "deletes all temporary state information.",
88                    );
89
90                    // Add the user defined
91                    for (name, command) in commands {
92                        cmd_help.insert(name, &command.help);
93                        cmd_len = cmd_len.max(name.len());
94                    }
95
96                    // Go through the built in subcommands
97                    for (name, command) in cmd_help {
98                        println!(
99                            "    {:<width$}{}",
100                            name,
101                            command,
102                            width = cmd_len + 5
103                        );
104                    }
105                }
106                _ => {
107                    let command = commands.get(command as &str);
108                    let line =
109                        line[1..].iter().map(|x| x.to_string()).collect();
110
111                    // Checks if we got it
112                    match command {
113                        Some(command) => {
114                            if let Err(e) = match command.command {
115                                CommandType::Sync(c) => c(state, line),
116                                #[cfg(feature = "async")]
117                                CommandType::Async(a) => a(state, line).await,
118                            } {
119                                eprintln!("{}", Paint::red(&format!("Command exited unsuccessfully:\n{}\n({:?})", &e, &e)))
120                            }
121                        }
122                        None => {
123                            eprintln!(
124                                "{}",
125                                Paint::red(&format!(
126                                    "Command not found: {}",
127                                    line[0]
128                                )),
129                            )
130                        }
131                    }
132                }
133            }
134
135            // Padding
136            println!();
137        }
138        false
139    }
140}