use std::sync::Arc;
use crate::cli::parse::FlagDef;
use crate::errors;
use crate::registry::Registry;
use crate::spec::entry::Entry;
use crate::spec::field::{FieldKind, FieldMeta};
#[derive(Clone)]
pub struct CmdNode {
pub path: String,
pub segment: String,
pub usage: String,
pub short: String,
pub long: String,
pub aliases: Vec<String>,
pub hidden: bool,
pub leaf: bool,
pub entry: Option<Arc<Entry>>,
pub defs: Vec<FlagDef>,
pub env_only: Vec<FieldMeta>,
pub pos_f: Vec<FieldMeta>,
pub min_pos: usize,
pub max_pos: usize,
pub children: Vec<CmdNode>,
pub default_segment: Option<String>,
}
impl CmdNode {
fn new(segment: &str) -> Self {
CmdNode {
path: String::new(),
segment: segment.to_string(),
usage: String::new(),
short: String::new(),
long: String::new(),
aliases: Vec::new(),
hidden: false,
leaf: false,
entry: None,
defs: Vec::new(),
env_only: Vec::new(),
pos_f: Vec::new(),
min_pos: 0,
max_pos: 0,
children: Vec::new(),
default_segment: None,
}
}
}
pub fn build_tree(reg: &Registry) -> errors::Result<CmdNode> {
let mut root = CmdNode::new("");
for e in reg.all() {
add_entry(&mut root, &e)?;
}
for child in &mut root.children {
sort_node(child);
}
root.children.sort_by(|a, b| a.segment.cmp(&b.segment));
Ok(root)
}
fn sort_node(node: &mut CmdNode) {
for child in &mut node.children {
sort_node(child);
}
node.children.sort_by(|a, b| a.segment.cmp(&b.segment));
}
fn add_entry(root: &mut CmdNode, e: &Arc<Entry>) -> errors::Result<()> {
let parts: Vec<&str> = e.name.split('.').collect();
add_parts(root, &parts, 0, e)
}
fn add_parts(node: &mut CmdNode, parts: &[&str], idx: usize, e: &Arc<Entry>) -> errors::Result<()> {
if e.cli.skip {
return Ok(()); }
let part = parts[idx];
let is_leaf_segment = idx == parts.len() - 1;
if !is_leaf_segment {
if !node.children.iter().any(|c| c.segment == part) {
node.children.push(CmdNode::new(part));
}
let idx_of = node
.children
.iter()
.position(|c| c.segment == part)
.unwrap();
return add_parts(&mut node.children[idx_of], parts, idx + 1, e);
}
match node.children.iter().position(|c| c.segment == part) {
Some(i) => {
if node.children[i].leaf || !node.children[i].children.is_empty() {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?} conflicts with an existing command path",
e.name
),
));
}
for alias in &e.cli.aliases {
if node
.children
.iter()
.enumerate()
.any(|(j, c)| j != i && c.segment == *alias)
{
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?}: alias {:?} collides with an existing command path",
e.name, alias
),
));
}
}
fill_leaf(&mut node.children[i], e)?;
set_default(node, part, e)?;
Ok(())
}
None => {
for alias in &e.cli.aliases {
if node.children.iter().any(|c| c.segment == *alias) {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?}: alias {:?} collides with an existing command path",
e.name, alias
),
));
}
}
let mut leaf = CmdNode::new(part);
fill_leaf(&mut leaf, e)?;
node.children.push(leaf);
set_default(node, part, e)?;
Ok(())
}
}
}
fn set_default(node: &mut CmdNode, part: &str, e: &Arc<Entry>) -> errors::Result<()> {
if !e.cli.default {
return Ok(());
}
if let Some(prev) = &node.default_segment {
if prev != part {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?}: default conflicts with existing default {:?}",
e.name, prev
),
));
}
return Ok(());
}
node.default_segment = Some(part.to_string());
Ok(())
}
fn fill_leaf(node: &mut CmdNode, e: &Arc<Entry>) -> errors::Result<()> {
node.path = e.name.clone();
node.usage = e.cli.usage.clone();
node.short = e.summary.clone();
node.long = e.description.clone();
node.aliases = e.cli.aliases.clone();
node.hidden = e.cli.hidden;
node.leaf = true;
node.entry = Some(Arc::clone(e));
let mut seen_sh: Vec<(char, String)> = Vec::new();
for f in &e.root.children {
if f.cli.skip {
continue;
}
if f.skip {
if f.cli.env_var.is_some() {
node.env_only.push(f.clone());
}
continue;
}
if f.cli.positional {
node.pos_f.push(f.clone());
continue;
}
let (kind, ok) = flag_kind_for(f, &e.name)?;
if !ok {
continue;
}
if let Some(sh) = f.cli.shorthand {
if let Some((_, prev)) = seen_sh.iter().find(|(c, _)| *c == sh) {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?}: shorthand {sh:?} of field {:?} already used by {prev:?}",
e.name, f.json_name
),
));
}
seen_sh.push((sh, f.json_name.clone()));
}
node.defs.push(FlagDef {
long: f.json_name.clone(),
short: f.cli.shorthand,
kind,
field: f.clone(),
});
}
let mut min_pos = 0usize;
let mut all_required = true;
for (i, f) in node.pos_f.iter().enumerate() {
if f.required && !all_required {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {:?}: required positional {:?} must not follow optional ones",
e.name, f.json_name
),
));
}
if f.required {
min_pos = i + 1;
} else {
all_required = false;
}
}
node.min_pos = min_pos;
node.max_pos = node.pos_f.len();
Ok(())
}
fn flag_kind_for(
f: &FieldMeta,
cmd_name: &str,
) -> errors::Result<(crate::cli::parse::FlagKind, bool)> {
use crate::cli::parse::FlagKind::{Bool, Slice, Str};
match f.kind {
FieldKind::Bool => Ok((Bool, true)),
FieldKind::Slice => {
if matches!(
f.elem.as_deref().map(|e| e.kind),
Some(FieldKind::Struct | FieldKind::Ptr | FieldKind::Slice)
) {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {cmd_name:?}: field {:?}: slice of {:?} is not supported by the CLI frontend yet",
f.json_name,
f.elem.as_deref().map(|e| e.kind)
),
));
}
Ok((Slice, true))
}
FieldKind::Struct => Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {cmd_name:?}: field {:?}: nested struct is not supported by the CLI frontend yet",
f.json_name
),
)),
FieldKind::Union => {
Ok((Str, false))
}
FieldKind::Ptr => {
if matches!(f.elem.as_deref().map(|e| e.kind), Some(FieldKind::Struct)) {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"cli: command {cmd_name:?}: field {:?}: pointer to struct is not supported by the CLI frontend yet",
f.json_name
),
));
}
Ok((Str, true))
}
_ => Ok((Str, true)),
}
}