1use clap::Command;
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
6pub struct TreeNode {
7 pub name: String,
9 #[serde(skip_serializing_if = "String::is_empty")]
11 pub description: String,
12 pub path: String,
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
16 pub children: Vec<TreeNode>,
17}
18
19impl TreeNode {
20 #[must_use]
22 pub fn new(
23 name: impl Into<String>,
24 description: impl Into<String>,
25 path: impl Into<String>,
26 ) -> Self {
27 Self {
28 name: name.into(),
29 description: description.into(),
30 path: path.into(),
31 children: Vec::new(),
32 }
33 }
34
35 #[must_use]
37 pub fn with_child(mut self, child: TreeNode) -> Self {
38 self.children.push(child);
39 self
40 }
41
42 #[must_use]
44 pub fn with_children(mut self, children: impl IntoIterator<Item = TreeNode>) -> Self {
45 self.children.extend(children);
46 self
47 }
48}
49
50#[must_use]
52pub fn build_tree_from_parts(
53 name: impl Into<String>,
54 description: impl Into<String>,
55 path: impl Into<String>,
56 children: Vec<TreeNode>,
57) -> TreeNode {
58 TreeNode::new(name, description, path).with_children(children)
59}
60
61#[must_use]
63pub fn build_tree_from_clap(command: &Command) -> TreeNode {
64 build_tree_from_clap_with_path(command, command.get_name().to_owned())
65}
66
67fn build_tree_from_clap_with_path(command: &Command, path: String) -> TreeNode {
68 let children = command
69 .get_subcommands()
70 .filter(|child| !child.is_hide_set() && child.get_name() != "completion")
71 .map(|child| {
72 let child_path = format!("{path} {}", child.get_name());
73 build_tree_from_clap_with_path(child, child_path)
74 })
75 .collect::<Vec<_>>();
76
77 TreeNode {
78 name: command.get_name().to_owned(),
79 description: command
80 .get_about()
81 .map(ToString::to_string)
82 .unwrap_or_default(),
83 path,
84 children,
85 }
86}
87
88#[must_use]
90pub fn render_tree_human(node: &TreeNode) -> String {
91 let mut out = String::new();
92 render_node(node, "", true, true, &mut out);
93 out
94}
95
96fn render_node(node: &TreeNode, prefix: &str, is_last: bool, is_root: bool, out: &mut String) {
97 if is_root {
98 out.push_str(&node.name);
99 out.push('\n');
100 } else {
101 let connector = if is_last { "└── " } else { "├── " };
102 out.push_str(prefix);
103 out.push_str(connector);
104 out.push_str(&node.name);
105 if !node.description.is_empty() {
106 out.push_str(" ··· ");
107 out.push_str(&node.description);
108 }
109 out.push('\n');
110 }
111
112 let child_prefix = if is_root {
113 String::new()
114 } else if is_last {
115 format!("{prefix} ")
116 } else {
117 format!("{prefix}│ ")
118 };
119 let child_len = node.children.len();
120 for (index, child) in node.children.iter().enumerate() {
121 render_node(child, &child_prefix, index + 1 == child_len, false, out);
122 }
123}