mod flag;
pub use crate::id;
pub use flag::{CompleteWithEqual, Flag, flag_type};
use crate::arg_context::ArgsContext;
use crate::completion::{CompletionGroup, Unready};
use crate::error::Error;
use crate::parsed_flag::ParsedFlag;
use crate::{Completion, Result, Seen};
use std::fmt::Debug;
use std::iter::Peekable;
pub use std::borrow::Cow;
type CowStr = Cow<'static, str>;
#[derive(Debug)]
pub enum CowSlice<T: 'static> {
Borrow(&'static [T]),
Owned(Vec<T>),
}
impl<T> std::ops::Deref for CowSlice<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
match self {
CowSlice::Borrow(t) => t,
CowSlice::Owned(t) => t.as_slice(),
}
}
}
#[derive(Debug)]
pub enum CowOwned<T: 'static, U: 'static> {
Borrow(&'static [T]),
Owned(Vec<U>),
}
type StringPair = (String, String);
type PossibleValues = CowOwned<(&'static str, &'static str), StringPair>;
impl PossibleValues {
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
let (v1, v2): (&[(&str, &str)], &[StringPair]) = match self {
CowOwned::Borrow(v) => (*v, &[]),
CowOwned::Owned(v) => (&[], v.as_slice()),
};
v1.iter()
.map(|s| (s.0, s.1))
.chain(v2.iter().map(|s| (&*s.0, &*s.1)))
}
}
#[derive(Debug)]
pub struct Arg<ID> {
pub id: Option<ID>,
pub seen_id: id::Valued,
pub max_values: usize,
pub possible_values: PossibleValues,
}
fn comp_with_possible<ID>(
mut unready: Unready,
values: &PossibleValues,
value: String,
id: Option<ID>,
) -> CompletionGroup<ID> {
let values = values.iter().map(|(v, d)| Completion::new(v, d));
unready = unready.preexist(values);
match id {
Some(id) => CompletionGroup::Unready { id, unready, value },
None => CompletionGroup::Ready(unready.to_ready(vec![])),
}
}
#[derive(Debug)]
pub struct Command<ID: 'static> {
pub name: CowStr,
pub description: CowStr,
pub all_flags: CowSlice<Flag<ID>>,
pub args: CowSlice<Arg<ID>>,
pub commands: CowSlice<Command<ID>>,
}
fn supplement_arg<ID: PartialEq + Copy + Debug>(
seen: &mut Seen,
ctx: &mut ArgsContext<ID>,
arg: String,
) -> Result {
let Some(arg_obj) = ctx.next_arg() else {
return Err(Error::UnexpectedArg(arg));
};
seen.push_valued(arg_obj.seen_id, arg);
Ok(())
}
fn parse_flag(s: &str, disable_flag: bool) -> ParsedFlag<'_> {
if disable_flag {
log::info!("flag is disabled: {}", s);
ParsedFlag::NotFlag
} else {
ParsedFlag::new(s)
}
}
fn check_no_flag<ID>(arg: String, v: Vec<Completion>) -> Result<CompletionGroup<ID>> {
if v.is_empty() {
return Err(Error::UnexpectedFlag);
}
Ok(CompletionGroup::new_ready(v, arg))
}
impl<ID: 'static + Copy + PartialEq + Debug> Command<ID> {
pub fn supplement(
&self,
args: impl Iterator<Item = String>,
) -> Result<(Seen, CompletionGroup<ID>)> {
let mut seen = Seen::new();
let grp = self.supplement_with_seen(&mut seen, args)?;
Ok((seen, grp))
}
pub fn supplement_with_seen(
&self,
seen: &mut Seen,
mut args: impl Iterator<Item = String>,
) -> Result<CompletionGroup<ID>> {
args.next();
let mut args = args.peekable();
if args.peek().is_none() {
return Err(Error::ArgsTooShort);
}
self.supplement_recur(&mut None, seen, &mut args)
}
fn doing_external(&self, ctx: &ArgsContext<'_, ID>) -> bool {
let has_subcmd = !self.commands.is_empty();
has_subcmd && ctx.has_seen_arg()
}
fn flags(&self, seen: &Seen) -> impl Iterator<Item = &Flag<ID>> {
self.all_flags.iter().filter(|f| {
if !f.once {
true
} else {
let exists = f.exists_in_seen(seen);
if exists {
log::debug!("flag {:?} already exists", f.name());
}
!exists
}
})
}
fn find_flag<F: FnMut(&Flag<ID>) -> bool>(
&self,
arg: &str,
seen: &Seen,
mut filter: F,
) -> Result<&Flag<ID>> {
match self.flags(seen).find(|f| filter(f)) {
Some(flag) => Ok(flag),
None => Err(Error::FlagNotFound(arg.to_owned())),
}
}
fn find_long_flag(&self, flag: &str, seen: &Seen) -> Result<&Flag<ID>> {
self.find_flag(flag, seen, |f| f.long.iter().any(|f| f == flag))
}
fn find_short_flag(&self, flag: char, seen: &Seen) -> Result<&Flag<ID>> {
self.find_flag(&flag.to_string(), seen, |f| f.short.contains(&flag))
}
fn supplement_recur<'a>(
&'a self,
args_ctx_opt: &mut Option<ArgsContext<'a, ID>>,
seen: &mut Seen,
args: &mut Peekable<impl Iterator<Item = String>>,
) -> Result<CompletionGroup<ID>> {
let arg = args.next().unwrap();
let args_ctx = if let Some(ctx) = args_ctx_opt {
ctx
} else {
*args_ctx_opt = Some(ArgsContext::new(&self.args));
args_ctx_opt.as_mut().unwrap()
};
if args.peek().is_none() {
return self.supplement_last(args_ctx, seen, arg);
}
macro_rules! handle_flag {
($flag:expr, $equal:expr, $seen:expr) => {
if let Some(equal) = $equal {
match &$flag.ty {
flag_type::Type::Valued(flag) => flag.push($seen, equal.to_string()),
_ => return Err(Error::BoolFlagEqualsValue(arg)),
}
} else {
let res = $flag.supplement($seen, args)?;
if let Some(res) = res {
return Ok(res);
}
}
};
}
match parse_flag(&arg, self.doing_external(args_ctx)) {
ParsedFlag::SingleDash | ParsedFlag::DoubleDash | ParsedFlag::Empty => {
supplement_arg(seen, args_ctx, arg)?;
}
ParsedFlag::NotFlag => {
let command = if args_ctx.has_seen_arg() {
None
} else {
self.commands.iter().find(|c| arg == c.name)
};
match command {
Some(command) => {
return command.supplement_recur(&mut None, seen, args);
}
None => {
log::info!("No subcommand. Try fallback args.");
supplement_arg(seen, args_ctx, arg)?;
}
}
}
ParsedFlag::Long { body, equal } => {
let flag = self.find_long_flag(body, seen)?;
handle_flag!(flag, equal, seen);
}
ParsedFlag::Shorts => {
let resolved = self.resolve_shorts(seen, &arg)?;
handle_flag!(resolved.last_flag, resolved.value, seen);
}
}
self.supplement_recur(args_ctx_opt, seen, args)
}
fn supplement_last(
&self,
args_ctx: &mut ArgsContext<'_, ID>,
seen: &mut Seen,
arg: String,
) -> Result<CompletionGroup<ID>> {
let ret: CompletionGroup<ID> = match parse_flag(&arg, self.doing_external(args_ctx)) {
ParsedFlag::Empty | ParsedFlag::NotFlag => {
let cmd_slice = if args_ctx.has_seen_arg() {
log::info!("no completion for subcmd because we've already seen some args");
&[]
} else {
log::debug!("completion for {} subcommands", self.commands.len());
&*self.commands
};
let cmd_comps = cmd_slice
.iter()
.map(|c| Completion::new(&c.name, &c.description).group("command"));
if let Some(arg_obj) = args_ctx.next_arg() {
log::debug!("completion for args {:?}", arg_obj.id);
let unready = Unready::new(String::new(), arg.clone()).preexist(cmd_comps);
comp_with_possible(unready, &arg_obj.possible_values, arg, arg_obj.id)
} else {
if cmd_slice.is_empty() {
return Err(Error::UnexpectedArg(arg));
}
CompletionGroup::new_ready(cmd_comps.collect(), arg)
}
}
ParsedFlag::DoubleDash | ParsedFlag::Long { equal: None, .. } => check_no_flag(
arg,
self.flags(seen)
.flat_map(|f| f.gen_completion(Some(true)))
.collect(),
)?,
ParsedFlag::SingleDash => check_no_flag(
arg,
self.flags(seen)
.flat_map(|f| f.gen_completion(None))
.collect(),
)?,
ParsedFlag::Long {
equal: Some(value),
body,
} => {
let flag = self.find_long_flag(body, seen)?;
let valued = match &flag.ty {
flag_type::Type::Valued(valued) => valued,
_ => return Err(Error::BoolFlagEqualsValue(arg)),
};
let prefix = format!("--{body}=");
let value = value.to_string();
let unready = Unready::new(prefix, arg);
comp_with_possible(unready, &valued.possible_values, value, valued.id)
}
ParsedFlag::Shorts => self.supplement_last_short_flags(seen, arg)?,
};
Ok(ret)
}
fn resolve_shorts<'a, 'b>(
&'b self,
seen: &mut Seen,
shorts: &'a str,
) -> Result<ResolvedMultiShort<'a, 'b, ID>> {
let mut chars = shorts.chars().peekable();
let mut len = 1; chars.next(); loop {
len += 1;
let ch = chars.next().unwrap();
let flag = self.find_short_flag(ch, seen)?;
match chars.peek() {
None => {
return Ok(ResolvedMultiShort {
flag_part: shorts,
last_flag: flag,
value: None,
});
}
Some('=') => {
if matches!(flag.ty, flag_type::Type::Bool(_)) {
return Err(Error::BoolFlagEqualsValue(shorts.to_owned()));
};
len += 1;
return Ok(ResolvedMultiShort {
flag_part: &shorts[..len],
last_flag: flag,
value: Some(&shorts[len..]),
});
}
_ => {
let valued = match &flag.ty {
flag_type::Type::Bool(inner) => {
inner.push(seen);
continue;
}
flag_type::Type::Valued(valued) => valued,
};
match valued.complete_with_equal {
CompleteWithEqual::Must => {
return Err(Error::RequiresEqual(flag.name().to_owned()));
}
CompleteWithEqual::Optional => {
log::info!(
"Optional flag {} doesn't have value. Push an empty string to seen because we don't know its default value (clap wouldn't tell us).",
flag.name(),
);
valued.push(seen, String::new());
}
CompleteWithEqual::NoNeed => {
return Ok(ResolvedMultiShort {
flag_part: &shorts[..len],
last_flag: flag,
value: Some(&shorts[len..]),
});
}
}
}
}
}
}
fn supplement_last_short_flags(
&self,
seen: &mut Seen,
arg: String,
) -> Result<CompletionGroup<ID>> {
let resolved = self.resolve_shorts(seen, &arg)?;
let flag = resolved.last_flag;
let ret = match &flag.ty {
flag_type::Type::Valued(valued) => {
let value = resolved.value.unwrap_or_default().to_string();
let mut eq = "";
if valued.complete_with_equal != CompleteWithEqual::NoNeed {
if resolved.value.is_none() {
eq = "=";
} else {
}
}
let prefix = format!("{}{}", resolved.flag_part, eq);
let unready = Unready::new(prefix, arg);
comp_with_possible(unready, &valued.possible_values, value, valued.id)
}
flag_type::Type::Bool(inner) => {
log::debug!("list short flags with seen {:?}", seen);
inner.push(seen);
let comps = self
.flags(seen)
.flat_map(|f| f.gen_completion(Some(false)))
.map(|c| {
c.value(|v| {
let flag = &v[1..]; format!("{}{}", resolved.flag_part, flag)
})
})
.collect();
check_no_flag(arg, comps)?
}
};
Ok(ret)
}
}
#[derive(Clone, Copy)]
struct ResolvedMultiShort<'a, 'b, ID> {
flag_part: &'a str,
last_flag: &'b Flag<ID>,
value: Option<&'a str>,
}