use std::collections::HashMap;
use usage::{Spec, SpecFlag};
use crate::app::{ArgValue, FlagValue};
fn find_flag_spec<'a>(name: &str, flags: &'a [SpecFlag], global_flags: &'a [SpecFlag]) -> Option<&'a SpecFlag> {
flags
.iter()
.find(|f| f.name == name)
.or_else(|| global_flags.iter().find(|f| f.name == name && f.global))
}
pub fn format_flag_value(
name: &str,
value: &FlagValue,
flags: &[SpecFlag],
global_flags: &[SpecFlag],
) -> Option<String> {
let flag = find_flag_spec(name, flags, global_flags)?;
match value {
FlagValue::Bool(true) => {
let prefix = if let Some(long) = flag.long.first() {
format!("--{long}")
} else if let Some(short) = flag.short.first() {
format!("-{short}")
} else {
return None;
};
Some(prefix)
}
FlagValue::Bool(false) => None,
FlagValue::NegBool(None) => None,
FlagValue::NegBool(Some(true)) => {
let prefix = if let Some(long) = flag.long.first() {
format!("--{long}")
} else if let Some(short) = flag.short.first() {
format!("-{short}")
} else {
return None;
};
Some(prefix)
}
FlagValue::NegBool(Some(false)) => flag.negate.clone(),
FlagValue::Count(0) => None,
FlagValue::Count(n) => {
if let Some(short) = flag.short.first() {
Some(format!("-{}", short.to_string().repeat(*n as usize)))
} else if let Some(long) = flag.long.first() {
Some(
std::iter::repeat_n(format!("--{long}"), *n as usize)
.collect::<Vec<_>>()
.join(" "),
)
} else {
None
}
}
FlagValue::String(s) if s.is_empty() => None,
FlagValue::String(s) => {
let prefix = if let Some(long) = flag.long.first() {
format!("--{long}")
} else if let Some(short) = flag.short.first() {
format!("-{short}")
} else {
return None;
};
if s.contains(' ') {
Some(format!("{prefix} \"{s}\""))
} else {
Some(format!("{prefix} {s}"))
}
}
}
}
pub fn format_flag_parts(
name: &str,
value: &FlagValue,
flags: &[SpecFlag],
global_flags: &[SpecFlag],
parts: &mut Vec<String>,
) {
let Some(flag) = find_flag_spec(name, flags, global_flags) else {
return;
};
match value {
FlagValue::Bool(true) => {
if let Some(long) = flag.long.first() {
parts.push(format!("--{long}"));
} else if let Some(short) = flag.short.first() {
parts.push(format!("-{short}"));
}
}
FlagValue::Bool(false) => {}
FlagValue::NegBool(None) => {}
FlagValue::NegBool(Some(true)) => {
if let Some(long) = flag.long.first() {
parts.push(format!("--{long}"));
} else if let Some(short) = flag.short.first() {
parts.push(format!("-{short}"));
}
}
FlagValue::NegBool(Some(false)) => {
if let Some(negate) = &flag.negate {
parts.push(negate.clone());
}
}
FlagValue::Count(0) => {}
FlagValue::Count(n) => {
if let Some(short) = flag.short.first() {
parts.push(format!("-{}", short.to_string().repeat(*n as usize)));
} else if let Some(long) = flag.long.first() {
for _ in 0..*n {
parts.push(format!("--{long}"));
}
}
}
FlagValue::String(s) if s.is_empty() => {}
FlagValue::String(s) => {
if let Some(long) = flag.long.first() {
parts.push(format!("--{long}"));
} else if let Some(short) = flag.short.first() {
parts.push(format!("-{short}"));
} else {
return;
}
parts.push(s.clone());
}
}
}
pub struct LiveArgPreview<'a> {
pub choice_select_index: Option<usize>,
pub choice_select_text: &'a str,
pub is_editing: bool,
pub editing_index: usize,
pub editing_text: &'a str,
}
fn effective_arg_value<'a>(
index: usize,
arg: &'a ArgValue,
preview: &'a LiveArgPreview<'a>,
) -> &'a str {
if preview.choice_select_index == Some(index) {
preview.choice_select_text
} else if preview.is_editing && preview.editing_index == index {
preview.editing_text
} else {
&arg.value
}
}
pub fn build_command(
spec: &Spec,
flag_values: &HashMap<String, Vec<(String, FlagValue)>>,
command_path: &[String],
arg_values: &[ArgValue],
preview: &LiveArgPreview,
) -> String {
let mut parts: Vec<String> = Vec::new();
let bin = if spec.bin.is_empty() {
&spec.name
} else {
&spec.bin
};
parts.push(bin.clone());
let root_key = String::new();
if let Some(root_flags) = flag_values.get(&root_key) {
for (name, value) in root_flags {
if let Some(flag_str) = format_flag_value(name, value, &spec.cmd.flags, &spec.cmd.flags)
{
parts.push(flag_str);
}
}
}
let mut cmd = &spec.cmd;
for (i, name) in command_path.iter().enumerate() {
parts.push(name.clone());
if let Some(sub) = cmd.find_subcommand(name) {
cmd = sub;
let path_key = command_path[..=i].join(" ");
if let Some(level_flags) = flag_values.get(&path_key) {
for (fname, fvalue) in level_flags {
let is_global = spec.cmd.flags.iter().any(|f| f.global && f.name == *fname);
if is_global {
continue;
}
if let Some(flag_str) =
format_flag_value(fname, fvalue, &cmd.flags, &spec.cmd.flags)
{
parts.push(flag_str);
}
}
}
}
}
for (i, arg) in arg_values.iter().enumerate() {
let value = effective_arg_value(i, arg, preview);
if !value.is_empty() {
if value.contains(' ') {
parts.push(format!("\"{value}\""));
} else {
parts.push(value.to_string());
}
}
}
parts.join(" ")
}
pub fn build_command_parts(
spec: &Spec,
flag_values: &HashMap<String, Vec<(String, FlagValue)>>,
command_path: &[String],
arg_values: &[ArgValue],
) -> Vec<String> {
let mut parts: Vec<String> = Vec::new();
let bin = if spec.bin.is_empty() {
&spec.name
} else {
&spec.bin
};
for word in bin.split_whitespace() {
parts.push(word.to_string());
}
let root_key = String::new();
if let Some(root_flags) = flag_values.get(&root_key) {
for (name, value) in root_flags {
format_flag_parts(name, value, &spec.cmd.flags, &spec.cmd.flags, &mut parts);
}
}
let mut cmd = &spec.cmd;
for (i, name) in command_path.iter().enumerate() {
parts.push(name.clone());
if let Some(sub) = cmd.find_subcommand(name) {
cmd = sub;
let path_key = command_path[..=i].join(" ");
if let Some(level_flags) = flag_values.get(&path_key) {
for (fname, fvalue) in level_flags {
let is_global = spec.cmd.flags.iter().any(|f| f.global && f.name == *fname);
if is_global {
continue;
}
format_flag_parts(fname, fvalue, &cmd.flags, &spec.cmd.flags, &mut parts);
}
}
}
}
for arg in arg_values {
if !arg.value.is_empty() {
parts.push(arg.value.clone());
}
}
parts
}