1use std::fmt;
20
21use super::CommandTree;
22
23#[derive(Debug, Clone, Copy, Default)]
25pub enum DisplayStyle {
26 #[default]
35 Simple,
36
37 WithDescriptions,
46
47 WithSignature,
56
57 TreeBox,
66}
67
68#[derive(Debug, Clone)]
70pub struct CommandTreeDisplay<'a> {
71 tree: &'a CommandTree<'a>,
72 style: DisplayStyle,
73 indent_str: &'a str,
74}
75
76impl<'a> CommandTreeDisplay<'a> {
77 pub fn new(tree: &'a CommandTree<'a>) -> Self {
79 Self {
80 tree,
81 style: DisplayStyle::default(),
82 indent_str: " ",
83 }
84 }
85
86 pub fn style(mut self, style: DisplayStyle) -> Self {
88 self.style = style;
89 self
90 }
91
92 pub fn indent(mut self, indent: &'a str) -> Self {
94 self.indent_str = indent;
95 self
96 }
97
98 pub fn render(&self) -> String {
100 let mut output = String::new();
101 self.render_to(&mut output);
102 output
103 }
104
105 fn render_to(&self, output: &mut String) {
107 let top_level: Vec<_> = self.tree.iter().filter(|cmd| cmd.depth == 0).collect();
109
110 let mut sorted: Vec<_> = top_level.iter().collect();
111 sorted.sort_by_key(|cmd| cmd.name);
112
113 match self.style {
114 DisplayStyle::Simple => self.render_simple(output, &sorted),
115 DisplayStyle::WithDescriptions => self.render_with_descriptions(output, &sorted),
116 DisplayStyle::WithSignature => self.render_with_signature(output, &sorted),
117 DisplayStyle::TreeBox => self.render_tree_box(output, &sorted),
118 }
119 }
120
121 fn render_simple(&self, output: &mut String, commands: &[&&super::FlatCommand<'a>]) {
122 for cmd in commands {
123 self.render_simple_recursive(output, cmd.name, cmd.command, 0);
124 }
125 }
126
127 fn render_simple_recursive(
128 &self,
129 output: &mut String,
130 name: &str,
131 cmd: &baobao_manifest::Command,
132 depth: usize,
133 ) {
134 let indent = format!("{}{}", self.indent_str, self.indent_str.repeat(depth));
136 output.push_str(&indent);
137 output.push_str(name);
138 output.push('\n');
139
140 if cmd.has_subcommands() {
141 let mut sorted: Vec<_> = cmd.commands.iter().collect();
142 sorted.sort_by_key(|(n, _)| *n);
143 for (sub_name, sub_cmd) in sorted {
144 self.render_simple_recursive(output, sub_name, sub_cmd, depth + 1);
145 }
146 }
147 }
148
149 fn render_with_descriptions(&self, output: &mut String, commands: &[&&super::FlatCommand<'a>]) {
150 for cmd in commands {
151 self.render_descriptions_recursive(output, cmd.name, cmd.command, 0);
152 }
153 }
154
155 fn render_descriptions_recursive(
156 &self,
157 output: &mut String,
158 name: &str,
159 cmd: &baobao_manifest::Command,
160 depth: usize,
161 ) {
162 let indent = format!("{}{}", self.indent_str, self.indent_str.repeat(depth));
164 output.push_str(&indent);
165 output.push_str(name);
166 output.push_str(" - ");
167 output.push_str(&cmd.description);
168 output.push('\n');
169
170 if cmd.has_subcommands() {
171 let mut sorted: Vec<_> = cmd.commands.iter().collect();
172 sorted.sort_by_key(|(n, _)| *n);
173 for (sub_name, sub_cmd) in sorted {
174 self.render_descriptions_recursive(output, sub_name, sub_cmd, depth + 1);
175 }
176 }
177 }
178
179 fn render_with_signature(&self, output: &mut String, commands: &[&&super::FlatCommand<'a>]) {
180 for cmd in commands {
181 self.render_signature_recursive(output, cmd.name, cmd.command, 0);
182 }
183 }
184
185 fn render_signature_recursive(
186 &self,
187 output: &mut String,
188 name: &str,
189 cmd: &baobao_manifest::Command,
190 depth: usize,
191 ) {
192 let indent = format!("{}{}", self.indent_str, self.indent_str.repeat(depth));
194 output.push_str(&indent);
195
196 if cmd.has_subcommands() {
197 output.push_str(name);
199 output.push('\n');
200
201 let mut sorted: Vec<_> = cmd.commands.iter().collect();
202 sorted.sort_by_key(|(n, _)| *n);
203 for (sub_name, sub_cmd) in sorted {
204 self.render_signature_recursive(output, sub_name, sub_cmd, depth + 1);
205 }
206 } else {
207 output.push_str(&Self::format_signature(name, cmd));
209 output.push('\n');
210 }
211 }
212
213 fn format_signature(name: &str, cmd: &baobao_manifest::Command) -> String {
214 let mut parts = vec![name.to_string()];
215
216 let mut sorted_args: Vec<_> = cmd.args.iter().collect();
218 sorted_args.sort_by_key(|(n, _)| *n);
219 for (arg_name, arg) in sorted_args {
220 if arg.required {
221 parts.push(format!("<{}>", arg_name));
222 } else {
223 parts.push(format!("[{}]", arg_name));
224 }
225 }
226
227 let mut sorted_flags: Vec<_> = cmd.flags.iter().collect();
229 sorted_flags.sort_by_key(|(n, _)| *n);
230 let flags: Vec<String> = sorted_flags
231 .iter()
232 .map(|(flag_name, flag)| {
233 if let Some(short) = flag.short_char() {
234 format!("-{}/--{}", short, flag_name)
235 } else {
236 format!("--{}", flag_name)
237 }
238 })
239 .collect();
240
241 if !flags.is_empty() {
242 parts.push(format!("[{}]", flags.join(" ")));
243 }
244
245 parts.join(" ")
246 }
247
248 fn render_tree_box(&self, output: &mut String, commands: &[&&super::FlatCommand<'a>]) {
249 let total = commands.len();
250 for (i, cmd) in commands.iter().enumerate() {
251 let is_last = i == total - 1;
252 self.render_tree_box_recursive(output, cmd.name, cmd.command, self.indent_str, is_last);
254 }
255 }
256
257 fn render_tree_box_recursive(
258 &self,
259 output: &mut String,
260 name: &str,
261 cmd: &baobao_manifest::Command,
262 prefix: &str,
263 is_last: bool,
264 ) {
265 let connector = if is_last { "└─" } else { "├─" };
266 let child_prefix = if is_last { " " } else { "│ " };
267
268 output.push_str(prefix);
269 output.push_str(connector);
270 output.push(' ');
271 output.push_str(name);
272
273 let meta = Self::format_metadata(cmd);
275 if !meta.is_empty() {
276 output.push_str(" (");
277 output.push_str(&meta);
278 output.push(')');
279 }
280 output.push('\n');
281
282 if cmd.has_subcommands() {
284 let mut sorted: Vec<_> = cmd.commands.iter().collect();
285 sorted.sort_by_key(|(n, _)| *n);
286 let total = sorted.len();
287
288 for (i, (sub_name, sub_cmd)) in sorted.iter().enumerate() {
289 let sub_is_last = i == total - 1;
290 let new_prefix = format!("{}{}", prefix, child_prefix);
291 self.render_tree_box_recursive(output, sub_name, sub_cmd, &new_prefix, sub_is_last);
292 }
293 }
294 }
295
296 fn format_metadata(cmd: &baobao_manifest::Command) -> String {
297 let mut meta = Vec::new();
298
299 if !cmd.args.is_empty() {
300 let count = cmd.args.len();
301 meta.push(format!(
302 "{} arg{}",
303 count,
304 if count == 1 { "" } else { "s" }
305 ));
306 }
307
308 if !cmd.flags.is_empty() {
309 let count = cmd.flags.len();
310 meta.push(format!(
311 "{} flag{}",
312 count,
313 if count == 1 { "" } else { "s" }
314 ));
315 }
316
317 meta.join(", ")
318 }
319}
320
321impl fmt::Display for CommandTreeDisplay<'_> {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 let output = self.render();
324 write!(f, "{}", output.trim_end())
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
337 fn test_display_style_default() {
338 assert!(matches!(DisplayStyle::default(), DisplayStyle::Simple));
339 }
340}