Skip to main content

nickel_lang_core/repl/
command.rs

1//! REPL commands helpers common to all frontends.
2use super::*;
3use std::fmt;
4
5/// Available commands.
6#[derive(Copy, Clone, Eq, PartialEq, Debug)]
7pub enum CommandType {
8    Load,
9    Typecheck,
10    Query,
11    Print,
12    Help,
13    Exit,
14}
15
16impl CommandType {
17    pub fn all() -> Vec<&'static str> {
18        vec!["load", "typecheck", "query", "print", "help", "exit"]
19    }
20}
21
22/// A parsed command with corresponding argument(s). Required argument are checked for
23/// non-emptiness.
24#[derive(Clone, Eq, PartialEq, Debug)]
25pub enum Command {
26    Load(OsString),
27    Typecheck(String),
28    Query(String),
29    Print(String),
30    Help(Option<String>),
31    Exit,
32}
33
34pub struct UnknownCommandError {}
35
36/// Check that an argument is non-empty, or return an error with the given optional message.
37fn require_arg(cmd: CommandType, arg: &str, msg_opt: Option<&str>) -> Result<(), ReplErrorKind> {
38    if arg.trim().is_empty() {
39        Err(ReplErrorKind::MissingArg {
40            cmd,
41            msg_opt: msg_opt.map(String::from),
42        })
43    } else {
44        Ok(())
45    }
46}
47
48impl FromStr for CommandType {
49    type Err = UnknownCommandError;
50
51    fn from_str(s: &str) -> Result<Self, Self::Err> {
52        use CommandType::*;
53
54        match s {
55            "load" | "l" => Ok(Load),
56            "typecheck" | "tc" => Ok(Typecheck),
57            "query" | "q" => Ok(Query),
58            "print" | "p" => Ok(Print),
59            "help" | "?" | "h" => Ok(Help),
60            "exit" | "e" => Ok(Exit),
61            _ => Err(UnknownCommandError {}),
62        }
63    }
64}
65
66impl CommandType {
67    /// Return the aliases of a command.
68    pub fn aliases(&self) -> Vec<String> {
69        use CommandType::*;
70
71        match self {
72            Load => vec![String::from("l")],
73            Typecheck => vec![String::from("tc")],
74            Query => vec![String::from("q")],
75            Print => vec![String::from("p")],
76            Help => vec![String::from("h"), String::from("?")],
77            Exit => vec![String::from("e")],
78        }
79    }
80}
81
82impl std::fmt::Display for CommandType {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        use CommandType::*;
85
86        match self {
87            Load => write!(f, "load"),
88            Typecheck => write!(f, "typecheck"),
89            Query => write!(f, "query"),
90            Print => write!(f, "print"),
91            Help => write!(f, "help"),
92            Exit => write!(f, "exit"),
93        }
94    }
95}
96
97impl FromStr for Command {
98    type Err = ReplErrorKind;
99
100    fn from_str(s: &str) -> Result<Self, Self::Err> {
101        let cmd_end = s.find(' ').unwrap_or(s.len());
102        let cmd_str: String = s.chars().take(cmd_end).collect();
103        let cmd: CommandType = cmd_str
104            .parse()
105            .map_err(|_| ReplErrorKind::UnknownCommand(cmd_str.clone()))?;
106        let arg: String = s
107            .chars()
108            .skip(cmd_end + 1)
109            .collect::<String>()
110            .trim()
111            .to_string();
112
113        match cmd {
114            CommandType::Load => {
115                require_arg(cmd, &arg, Some("Please provide a file to load"))?;
116                let arg = if arg.starts_with('"') && arg.ends_with('"') {
117                    arg.chars().skip(1).take(arg.len() - 2).collect::<String>()
118                } else {
119                    arg
120                };
121                println!("{arg}");
122                Ok(Command::Load(OsString::from(arg)))
123            }
124            CommandType::Typecheck => {
125                require_arg(cmd, &arg, None)?;
126                Ok(Command::Typecheck(arg))
127            }
128            CommandType::Query => {
129                require_arg(cmd, &arg, None)?;
130                Ok(Command::Query(arg))
131            }
132            CommandType::Print => {
133                require_arg(cmd, &arg, None)?;
134                Ok(Command::Print(arg))
135            }
136            CommandType::Exit => Ok(Command::Exit),
137            CommandType::Help => {
138                let arg_opt = if arg.trim().is_empty() {
139                    None
140                } else {
141                    Some(String::from(arg.trim()))
142                };
143
144                Ok(Command::Help(arg_opt))
145            }
146        }
147    }
148}
149
150impl Command {
151    pub fn typ(&self) -> CommandType {
152        use Command::*;
153
154        match self {
155            Load(..) => CommandType::Load,
156            Typecheck(..) => CommandType::Typecheck,
157            Query { .. } => CommandType::Query,
158            Print(..) => CommandType::Print,
159            Help(..) => CommandType::Help,
160            Exit => CommandType::Exit,
161        }
162    }
163}