clap_tree/
lib.rs

1//! A personal helper crate to help me build CLIs. I do not recommend others to use it.
2
3#![warn(missing_docs)]
4/// Represents an error that can occur with a tree Node.
5#[derive(Debug)]
6pub enum TreeError {
7    /// Not a real error. This error occurs when no subcommand gets matched and
8    /// the help menu is displayed.
9    ClapHelp(String),
10    /// IO error.
11    Io(std::io::Error),
12}
13
14impl std::fmt::Display for TreeError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match &self {
17            TreeError::ClapHelp(message) => write!(f, "{}", message),
18            TreeError::Io(error) => write!(f, "{}", error),
19        }
20    }
21}
22
23impl std::error::Error for TreeError {}
24
25/// Defines the type of function to be call before a node's `f` function (if it
26/// exists).
27pub type NodePreFn<P> = Box<dyn FnOnce(&clap::ArgMatches, Option<P>) -> Option<P>>;
28
29/// Defines the type of function to be called in the command tree.
30pub type NodeFn<P, R> = Box<dyn FnOnce(&clap::ArgMatches, Option<P>) -> Result<R, TreeError>>;
31
32/// Represents a command in the command tree.
33pub trait Node<P, R> {
34    /// The clap ID name for the command node.
35    fn name(&self) -> &str;
36    /// The clap command.
37    fn command(&self) -> clap::Command;
38    /// Subcommand nodes.
39    fn children_nodes(&self) -> Vec<Box<dyn Node<P, R>>>;
40    /// A function used to transform custom parameters.
41    fn pre_f(&self) -> Option<NodePreFn<P>>;
42    /// The function to run when the node command is called.
43    fn f(&self) -> Option<NodeFn<P, R>>;
44}
45
46/// Map nodes to their respective clap commands.
47pub fn map_to_clap<P, R>(nodes: Vec<Box<dyn Node<P, R>>>) -> Vec<clap::Command> {
48    nodes.iter().map(|c| c.command()).collect()
49}
50
51/// Run a parent node command.
52pub fn run_tree<P, R>(
53    node: Box<dyn Node<P, R>>,
54    parent_matches: Option<&clap::ArgMatches>,
55    params: Option<P>,
56) -> Result<R, TreeError> {
57    let mut command = node.command();
58    let matches = if let Some(m) = parent_matches {
59        m
60    } else {
61        &command.clone().get_matches()
62    };
63
64    if let Some((name, arg_matches)) = matches.subcommand() {
65        match find_f(node.children_nodes(), name) {
66            Some(f) => {
67                let params = match node.pre_f() {
68                    Some(pre_f) => pre_f(matches, params),
69                    None => params,
70                };
71                match f(arg_matches, params) {
72                    Ok(result) => Ok(result),
73                    Err(e) => Err(e),
74                }
75            }
76            None => unreachable!("Could not find subcommand."),
77        }
78    } else {
79        match command.print_long_help() {
80            Ok(_) => Err(TreeError::ClapHelp(String::from("No command executed."))),
81            Err(e) => Err(TreeError::Io(e)),
82        }
83    }
84}
85
86/// Get the function of a node provided the node name. If there are identical
87/// node names, the node that is first encountered in the vector will have its
88/// function returned.
89fn find_f<P, R>(nodes: Vec<Box<dyn Node<P, R>>>, name: &str) -> Option<NodeFn<P, R>> {
90    nodes
91        .iter()
92        .find(|c| c.name() == name)
93        .map(|c| c.f())
94        .unwrap_or(None)
95}