use std::{ collections::HashSet, fmt::{Debug, Display}, }; use smol_str::SmolStr; use crate::{flag::Flag, token::Token}; #[derive(Debug, Clone)] pub struct Command { // TODO: fix hygiene pub tokens: Vec, pub flags: Vec, pub help: SmolStr, pub cb: SmolStr, pub show_in_suggestions: bool, pub parse_flags_before: usize, pub hidden_flags: HashSet, } impl Command { pub fn new(tokens: impl IntoIterator, cb: impl Into) -> Self { let tokens = tokens.into_iter().collect::>(); assert!(tokens.len() > 0); // figure out which token to parse / put flags after // (by default, put flags after the last token) let mut parse_flags_before = tokens.len(); for (idx, token) in tokens.iter().enumerate().rev() { match token { // we want flags to go before any parameters Token::Parameter(_) => parse_flags_before = idx, Token::Value { .. } => break, } } Self { flags: Vec::new(), help: SmolStr::new_static(""), cb: cb.into(), show_in_suggestions: true, parse_flags_before, tokens, hidden_flags: HashSet::new(), } } pub fn help(mut self, v: impl Into) -> Self { self.help = v.into(); self } pub fn show_in_suggestions(mut self, v: bool) -> Self { self.show_in_suggestions = v; self } pub fn flag(mut self, flag: impl Into) -> Self { self.flags.push(flag.into()); self } pub fn hidden_flag(mut self, flag: impl Into) -> Self { let flag = flag.into(); self.hidden_flags.insert(flag.get_name().into()); self.flags.push(flag); self } } impl Display for Command { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let write_flags = |f: &mut std::fmt::Formatter<'_>, space: bool| { for flag in &self.flags { if self.hidden_flags.contains(flag.get_name()) { continue; } write!( f, "{}[{flag}]{}", space.then_some(" ").unwrap_or(""), space.then_some("").unwrap_or(" ") )?; } std::fmt::Result::Ok(()) }; for (idx, token) in self.tokens.iter().enumerate() { if idx == self.parse_flags_before { write_flags(f, false)?; } write!( f, "{token}{}", (idx < self.tokens.len() - 1).then_some(" ").unwrap_or("") )?; } if self.tokens.len() == self.parse_flags_before { write_flags(f, true)?; } Ok(()) } } // a macro is required because generic cant be different types at the same time (which means you couldnt have ["member", MemberRef, "subcmd"] etc) // (and something like &dyn Trait would require everything to be referenced which doesnt look nice anyway) #[macro_export] macro_rules! command { ($($v:expr),+ => $cb:expr$(,)*) => { $crate::command::Command::new($crate::tokens!($($v),+), $cb) }; }