climb/
command.rs

1use crate::types::*;
2
3///  Holds information about command options.
4///
5/// `CommandOption`s are the options that can be passed in when calling
6/// application commands from the command line. They can also accept an
7/// argument.
8///
9/// They can be added to commands using the [Command] API.
10///
11/// # Examples
12///
13/// Creating a command option and assigning it an alias and argument:
14///
15/// ```
16/// use climb::CommandOption;
17///
18/// let my_option = CommandOption::new(
19///     "delete-folder",
20///     "This option tells the command you want to delete a folder"
21/// )
22/// .alias("d")
23/// .arg("folder_name");
24/// ```
25///
26/// This option can be used with the command like this:
27///
28/// `[COMMAND] --delete-folder <folder_name> ...`
29///
30/// or
31///
32/// `[COMMAND] -d <folder_name> ...`
33pub struct CommandOption {
34    pub(crate) alias_long: String,
35    pub(crate) alias_short: Option<String>,
36    pub(crate) arg: Option<String>,
37    pub(crate) description: String,
38}
39
40impl CommandOption {
41    /// Construct and returns a default CommandOption.
42    ///
43    /// Creates the command option and initializes it with the given alias
44    /// and description. The provided alias is the long alias. It will be
45    /// prepended with `--` automatically when the application is constructed.
46    ///
47    /// # Arguments
48    ///
49    /// * `alias_long` - The long alias of the option (more than one character)
50    /// * `desc` - The description of the command
51    ///
52    /// # Examples
53    ///
54    /// Creating a new command. This command will be called using the
55    /// `--recursive` option in the command line:
56    ///
57    /// ```
58    /// use climb::CommandOption;
59    ///
60    /// let my_option = CommandOption::new(
61    ///     "recursive",
62    ///     "This option lets the command behave recursively"
63    /// );
64    /// ```
65    pub fn new(alias_long: &str, desc: &str) -> Self {
66        if alias_long.len() <= 1 {
67            panic!(
68                "Long option aliases should have more than a single character: `{}`",
69                alias_long
70            );
71        }
72
73        let mut alias = String::from(alias_long);
74        alias.insert_str(0, "--");
75        CommandOption {
76            alias_long: alias,
77            alias_short: None,
78            arg: None,
79            description: String::from(desc),
80        }
81    }
82
83    /// Assign a short alias to the option
84    ///
85    /// The short alias is a single character alias prepended by
86    /// a single dash `-`. For example, `--help` can also be called with
87    /// `-h`. Options do not have a short alias by default.
88    ///
89    /// # Arguments
90    /// * `alias` - A string slice holding the short alias of the option.
91    /// Must be a single character.
92    ///
93    /// # Examples
94    ///
95    /// Creating an option and adding a short alias. This option can
96    /// be called using `--recursive` or `-r`:
97    ///
98    /// ```
99    /// use climb::CommandOption;
100    ///
101    /// let my_option = CommandOption::new(
102    ///     "recursive",
103    ///     "This option lets the command behave recursively"
104    /// )
105    /// .alias("r");
106    /// ```
107    pub fn alias(mut self, alias: &str) -> Self {
108        if alias.len() != 1 {
109            panic!(
110                "Short option aliases can only be a single character: `{}`",
111                alias
112            );
113        }
114
115        let mut alias = String::from(alias);
116        alias.insert(0, '-');
117        self.alias_short = Some(alias);
118        self
119    }
120
121    /// Assign an argument to the option
122    ///
123    /// Option arguments are passed in after the option is provided
124    /// in the command line arguments.
125    ///
126    /// # Arguments:
127    /// * `argument_name` - The name of the argument
128    ///
129    /// # Examples
130    ///
131    /// Creating an option and adding an argument named `folder_name`.
132    /// This option argument can be used like this:
133    ///
134    /// \[command_name\] --delete-folder <folder_name> ...
135    ///
136    /// ```
137    /// use climb::CommandOption;
138    ///
139    /// let my_option = CommandOption::new(
140    ///     "delete-folder",
141    ///     "This tells the command you want to delete a folder"
142    /// )
143    /// .arg("folder_name");
144    /// ```
145    pub fn arg(mut self, argument_name: &str) -> Self {
146        self.arg = Some(argument_name.to_uppercase());
147        self
148    }
149}
150
151/// Holds information about the commands the application can call.
152///
153/// A command stores an alias to call it by, valid options, arguments, and
154/// a function that is called whenever the command is executed. This function
155/// is the code that you should write to perform the logic of the command.
156///
157/// When you call the command from the command line, Climb will parse the arguments
158/// and pass them into your function. See more about this in the [CommandFunction]
159/// documentation.
160///
161/// # Examples
162///
163/// Creating a command, changing some of its values, and adding it to
164/// an application:
165///
166/// ```
167/// use climb::*;
168///
169/// fn example_cmd_fn(_: FunctionInput, _: FunctionOptions) -> FunctionResult {
170///     println!("my example function");
171///     Ok(None)
172/// }
173///
174/// let my_command = Command::new(
175///     "cmd_name",
176///     "cmd_desc",
177///     example_cmd_fn
178/// )
179/// .alias("c")
180/// .option(CommandOption::new(
181///     "option_name",
182///     "option_desc"
183/// ))
184/// .arg("arg1")
185/// .arg("arg2");
186///
187/// let my_app = create_app!()
188///     .command(my_command);
189/// ```
190pub struct Command {
191    pub(crate) function: CommandFunction,
192    pub(crate) alias_long: String,
193    pub(crate) alias_short: Option<String>,
194    pub(crate) options: Vec<CommandOption>,
195    pub(crate) args: Vec<String>,
196    pub(crate) desc: String,
197}
198
199impl Command {
200    /// Construct and return a command with the given alias, desc,
201    /// and function.
202    ///
203    /// # Arguments
204    /// * `alias` - String slice that holds the alias used to call the
205    /// command from the terminal
206    /// * `desc` - String slice that holds the command description
207    /// * `function` - Function that matches the [CommandFunction] signature
208    ///
209    /// # Examples
210    ///
211    /// Construct a new command:
212    ///
213    /// ```
214    /// use climb::*;
215    ///
216    /// fn example_cmd_fn(_: FunctionInput, _: FunctionOptions) -> FunctionResult {
217    ///     println!("my example function");
218    ///     Ok(None)
219    /// }
220    ///
221    /// let my_command = Command::new(
222    ///     "cmd_name",
223    ///     "cmd_desc",
224    ///     example_cmd_fn
225    /// );
226    /// ```
227    pub fn new(alias: &str, desc: &str, function: CommandFunction) -> Self {
228        if alias.len() <= 1 {
229            panic!(
230                "Long command aliases should have more than a single character: `{}`",
231                alias
232            );
233        }
234
235        // Construct a default command with only the help menu option
236        Command {
237            function,
238            alias_long: alias.to_lowercase(),
239            alias_short: None,
240            options: vec![CommandOption::new("help", "Print help information").alias("h")],
241            args: vec![],
242            desc: String::from(desc),
243        }
244    }
245
246    /// Assign an short alias to the command (single character).
247    ///
248    /// The command can be called using either the normal long alias or this
249    /// shorter one.
250    ///
251    /// # Arguments
252    /// * `alias` - String slice holding the short alias (must be a single
253    /// character)
254    ///
255    /// # Examples
256    ///
257    /// Construct a new command and assign a new alias `c`:
258    ///
259    /// ```
260    /// use climb::*;
261    ///
262    /// fn example_cmd_fn(_: FunctionInput, _: FunctionOptions) -> FunctionResult {
263    ///     println!("my example function");
264    ///     Ok(None)
265    /// }
266    ///
267    /// let my_command = Command::new(
268    ///     "cmd_name",
269    ///     "cmd_desc",
270    ///     example_cmd_fn
271    /// )
272    /// .alias("c");
273    /// ```
274    ///
275    /// The command can now be called using either `cmd_name` or `c`.
276    pub fn alias(mut self, alias: &str) -> Self {
277        if alias.len() != 1 {
278            panic!(
279                "Short command aliases can only be a single character: `{}`",
280                alias
281            );
282        }
283
284        self.alias_short = Some(alias.to_lowercase());
285        self
286    }
287
288    /// Add an option to the command.
289    ///
290    /// You can add as many options as you like to a command. For more
291    /// about options, see [CommandOption].
292    ///
293    /// # Arguments
294    /// * `option` - A `CommandOption` struct holding the option to be added
295    ///
296    /// # Examples
297    ///
298    /// Construct a command and add an option:
299    ///
300    /// ```
301    /// use climb::*;
302    ///
303    /// fn example_cmd_fn(_: FunctionInput, _: FunctionOptions) -> FunctionResult {
304    ///     println!("my example function");
305    ///     Ok(None)
306    /// }
307    ///
308    /// let my_command = Command::new(
309    ///     "cmd_name",
310    ///     "cmd_desc",
311    ///     example_cmd_fn
312    /// )
313    /// .option(CommandOption::new(
314    ///     "option_name",
315    ///     "option_desc"
316    /// ));
317    /// ```
318    ///
319    /// The option can now be called using `[cmd_name] --option_name ...`
320    pub fn option(mut self, option: CommandOption) -> Self {
321        self.options.push(option);
322        self
323    }
324
325    /// Add an argument to the command.
326    ///
327    /// Command arguments allow you to pass data into the command from the
328    /// command line. For example, if you wanted a command to perform actions on
329    /// a file, you could pass in the file name as an argument.
330    ///
331    /// # Arguments
332    /// * `name` - String slice that holds the name of the argument
333    ///
334    /// # Examples
335    ///
336    /// Construct a new command and add an argument to it:
337    ///
338    /// ```
339    /// use climb::*;
340    ///
341    /// fn example_cmd_fn(_: FunctionInput, _: FunctionOptions) -> FunctionResult {
342    ///     println!("my example function");
343    ///     Ok(None)
344    /// }
345    ///
346    /// let my_command = Command::new(
347    ///     "cmd_name",
348    ///     "cmd_desc",
349    ///     example_cmd_fn
350    /// )
351    /// .arg("arg1");
352    /// ```
353    ///
354    /// The command can be called using:
355    ///
356    /// `[app_name] cmd_name <arg1> ...`
357    pub fn arg(mut self, name: &str) -> Self {
358        self.args.push(name.to_uppercase());
359        self
360    }
361
362    // If the command has the option, return a reference to it
363    pub(crate) fn has_option(&self, alias: &String) -> Option<&CommandOption> {
364        for option in &self.options {
365            let equals_alias_short = if let Some(alias_short) = &option.alias_short {
366                *alias == *alias_short
367            } else {
368                false
369            };
370
371            if option.alias_long == *alias || equals_alias_short {
372                return Some(option);
373            }
374        }
375
376        None
377    }
378}