1use std::sync::Arc;
6
7use crate::cli::parse::FlagDef;
8use crate::errors;
9use crate::registry::Registry;
10use crate::spec::entry::Entry;
11use crate::spec::field::{FieldKind, FieldMeta};
12
13#[derive(Clone)]
14pub struct CmdNode {
15 pub path: String,
17 pub segment: String,
19 pub usage: String,
20 pub short: String,
21 pub long: String,
22 pub aliases: Vec<String>,
23 pub hidden: bool,
24 pub leaf: bool,
25 pub entry: Option<Arc<Entry>>,
26 pub defs: Vec<FlagDef>,
27 pub env_only: Vec<FieldMeta>,
29 pub pos_f: Vec<FieldMeta>,
30 pub min_pos: usize,
31 pub max_pos: usize,
32 pub children: Vec<CmdNode>,
34 pub default_segment: Option<String>,
37}
38
39impl CmdNode {
40 fn new(segment: &str) -> Self {
41 CmdNode {
42 path: String::new(),
43 segment: segment.to_string(),
44 usage: String::new(),
45 short: String::new(),
46 long: String::new(),
47 aliases: Vec::new(),
48 hidden: false,
49 leaf: false,
50 entry: None,
51 defs: Vec::new(),
52 env_only: Vec::new(),
53 pos_f: Vec::new(),
54 min_pos: 0,
55 max_pos: 0,
56 children: Vec::new(),
57 default_segment: None,
58 }
59 }
60}
61
62pub fn build_tree(reg: &Registry) -> errors::Result<CmdNode> {
65 let mut root = CmdNode::new("");
66 for e in reg.all() {
67 add_entry(&mut root, &e)?;
68 }
69 for child in &mut root.children {
70 sort_node(child);
71 }
72 root.children.sort_by(|a, b| a.segment.cmp(&b.segment));
73 Ok(root)
74}
75
76fn sort_node(node: &mut CmdNode) {
77 for child in &mut node.children {
78 sort_node(child);
79 }
80 node.children.sort_by(|a, b| a.segment.cmp(&b.segment));
81}
82
83fn add_entry(root: &mut CmdNode, e: &Arc<Entry>) -> errors::Result<()> {
84 let parts: Vec<&str> = e.name.split('.').collect();
85 add_parts(root, &parts, 0, e)
86}
87
88fn add_parts(node: &mut CmdNode, parts: &[&str], idx: usize, e: &Arc<Entry>) -> errors::Result<()> {
89 if e.cli.skip {
90 return Ok(()); }
92 let part = parts[idx];
93 let is_leaf_segment = idx == parts.len() - 1;
94 if !is_leaf_segment {
95 if !node.children.iter().any(|c| c.segment == part) {
96 node.children.push(CmdNode::new(part));
97 }
98 let idx_of = node
99 .children
100 .iter()
101 .position(|c| c.segment == part)
102 .unwrap();
103 return add_parts(&mut node.children[idx_of], parts, idx + 1, e);
104 }
105
106 match node.children.iter().position(|c| c.segment == part) {
108 Some(i) => {
109 if node.children[i].leaf || !node.children[i].children.is_empty() {
110 return Err(errors::Error::new(
111 errors::Kind::Internal,
112 format!(
113 "cli: command {:?} conflicts with an existing command path",
114 e.name
115 ),
116 ));
117 }
118 for alias in &e.cli.aliases {
120 if node
121 .children
122 .iter()
123 .enumerate()
124 .any(|(j, c)| j != i && c.segment == *alias)
125 {
126 return Err(errors::Error::new(
127 errors::Kind::Internal,
128 format!(
129 "cli: command {:?}: alias {:?} collides with an existing command path",
130 e.name, alias
131 ),
132 ));
133 }
134 }
135 fill_leaf(&mut node.children[i], e)?;
136 set_default(node, part, e)?;
137 Ok(())
138 }
139 None => {
140 for alias in &e.cli.aliases {
141 if node.children.iter().any(|c| c.segment == *alias) {
142 return Err(errors::Error::new(
143 errors::Kind::Internal,
144 format!(
145 "cli: command {:?}: alias {:?} collides with an existing command path",
146 e.name, alias
147 ),
148 ));
149 }
150 }
151 let mut leaf = CmdNode::new(part);
152 fill_leaf(&mut leaf, e)?;
153 node.children.push(leaf);
154 set_default(node, part, e)?;
155 Ok(())
156 }
157 }
158}
159
160fn set_default(node: &mut CmdNode, part: &str, e: &Arc<Entry>) -> errors::Result<()> {
162 if !e.cli.default {
163 return Ok(());
164 }
165 if let Some(prev) = &node.default_segment {
166 if prev != part {
167 return Err(errors::Error::new(
168 errors::Kind::Internal,
169 format!(
170 "cli: command {:?}: default conflicts with existing default {:?}",
171 e.name, prev
172 ),
173 ));
174 }
175 return Ok(());
176 }
177 node.default_segment = Some(part.to_string());
178 Ok(())
179}
180
181fn fill_leaf(node: &mut CmdNode, e: &Arc<Entry>) -> errors::Result<()> {
182 node.path = e.name.clone();
183 node.usage = e.cli.usage.clone();
184 node.short = e.summary.clone();
185 node.long = e.description.clone();
186 node.aliases = e.cli.aliases.clone();
187 node.hidden = e.cli.hidden;
188 node.leaf = true;
189 node.entry = Some(Arc::clone(e));
190
191 let mut seen_sh: Vec<(char, String)> = Vec::new();
192 for f in &e.root.children {
193 if f.cli.skip {
194 continue;
195 }
196 if f.skip {
197 if f.cli.env_var.is_some() {
199 node.env_only.push(f.clone());
200 }
201 continue;
202 }
203 if f.cli.positional {
204 node.pos_f.push(f.clone());
205 continue;
206 }
207 let (kind, ok) = flag_kind_for(f, &e.name)?;
208 if !ok {
209 continue;
210 }
211 if let Some(sh) = f.cli.shorthand {
212 if let Some((_, prev)) = seen_sh.iter().find(|(c, _)| *c == sh) {
213 return Err(errors::Error::new(
214 errors::Kind::Internal,
215 format!(
216 "cli: command {:?}: shorthand {sh:?} of field {:?} already used by {prev:?}",
217 e.name, f.json_name
218 ),
219 ));
220 }
221 seen_sh.push((sh, f.json_name.clone()));
222 }
223 node.defs.push(FlagDef {
224 long: f.json_name.clone(),
225 short: f.cli.shorthand,
226 kind,
227 field: f.clone(),
228 });
229 }
230
231 let mut min_pos = 0usize;
233 let mut all_required = true;
234 for (i, f) in node.pos_f.iter().enumerate() {
235 if f.required && !all_required {
236 return Err(errors::Error::new(
237 errors::Kind::Internal,
238 format!(
239 "cli: command {:?}: required positional {:?} must not follow optional ones",
240 e.name, f.json_name
241 ),
242 ));
243 }
244 if f.required {
245 min_pos = i + 1;
246 } else {
247 all_required = false;
248 }
249 }
250 node.min_pos = min_pos;
251 node.max_pos = node.pos_f.len();
252 Ok(())
253}
254
255fn flag_kind_for(
257 f: &FieldMeta,
258 cmd_name: &str,
259) -> errors::Result<(crate::cli::parse::FlagKind, bool)> {
260 use crate::cli::parse::FlagKind::{Bool, Slice, Str};
261 match f.kind {
262 FieldKind::Bool => Ok((Bool, true)),
263 FieldKind::Slice => {
264 if matches!(
266 f.elem.as_deref().map(|e| e.kind),
267 Some(FieldKind::Struct | FieldKind::Ptr | FieldKind::Slice)
268 ) {
269 return Err(errors::Error::new(
270 errors::Kind::Internal,
271 format!(
272 "cli: command {cmd_name:?}: field {:?}: slice of {:?} is not supported by the CLI frontend yet",
273 f.json_name,
274 f.elem.as_deref().map(|e| e.kind)
275 ),
276 ));
277 }
278 Ok((Slice, true))
279 }
280 FieldKind::Struct => Err(errors::Error::new(
281 errors::Kind::Internal,
282 format!(
283 "cli: command {cmd_name:?}: field {:?}: nested struct is not supported by the CLI frontend yet",
284 f.json_name
285 ),
286 )),
287 FieldKind::Union => {
288 Ok((Str, false))
292 }
293 FieldKind::Ptr => {
294 if matches!(f.elem.as_deref().map(|e| e.kind), Some(FieldKind::Struct)) {
295 return Err(errors::Error::new(
296 errors::Kind::Internal,
297 format!(
298 "cli: command {cmd_name:?}: field {:?}: pointer to struct is not supported by the CLI frontend yet",
299 f.json_name
300 ),
301 ));
302 }
303 Ok((Str, true))
304 }
305 _ => Ok((Str, true)),
306 }
307}