1#![warn(missing_docs)]
4#[derive(Debug)]
6pub enum TreeError {
7 ClapHelp(String),
10 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
25pub type NodePreFn<P> = Box<dyn FnOnce(&clap::ArgMatches, Option<P>) -> Option<P>>;
28
29pub type NodeFn<P, R> = Box<dyn FnOnce(&clap::ArgMatches, Option<P>) -> Result<R, TreeError>>;
31
32pub trait Node<P, R> {
34 fn name(&self) -> &str;
36 fn command(&self) -> clap::Command;
38 fn children_nodes(&self) -> Vec<Box<dyn Node<P, R>>>;
40 fn pre_f(&self) -> Option<NodePreFn<P>>;
42 fn f(&self) -> Option<NodeFn<P, R>>;
44}
45
46pub 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
51pub 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
86fn 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}