use super::command::{Action, Arg, Constraint, ConstraintKind, Flag, HelpGroup, Key};
pub const fn table_len<T>(groups: &[&[T]]) -> usize {
let mut total = 0;
let mut group = 0;
while group < groups.len() {
total += groups[group].len();
group += 1;
}
total
}
const fn concat<T: Copy, const N: usize>(groups: &[&[T]], placeholder: T) -> [T; N] {
let mut joined = [placeholder; N];
let mut at = 0;
let mut group = 0;
while group < groups.len() {
let entries = groups[group];
let mut index = 0;
while index < entries.len() {
joined[at] = entries[index];
at += 1;
index += 1;
}
group += 1;
}
assert!(at == N, "concatenated table length must match table_len");
joined
}
pub const fn concat_flags<const N: usize>(
groups: &[&[&'static Flag<'static>]],
) -> [&'static Flag<'static>; N] {
static PLACEHOLDER: Flag<'static> = Flag::BOOL;
concat(groups, &PLACEHOLDER)
}
pub const fn concat_args<const N: usize>(
groups: &[&[&'static Arg<'static>]],
) -> [&'static Arg<'static>; N] {
static PLACEHOLDER: Arg<'static> = Arg::REQUIRED;
concat(groups, &PLACEHOLDER)
}
pub const fn concat_constraints<const N: usize>(groups: &[&[Constraint]]) -> [Constraint; N] {
const PLACEHOLDER: Constraint =
Constraint { kind: ConstraintKind::Requires, source: 0, target: 0 };
concat(groups, PLACEHOLDER)
}
pub const fn concat_help_groups<const N: usize>(
groups: &[&[&'static HelpGroup<'static>]],
) -> [&'static HelpGroup<'static>; N] {
static PLACEHOLDER: HelpGroup<'static> = HelpGroup::EMPTY;
concat(groups, &PLACEHOLDER)
}
pub const fn argument_key_by_name(flags: &[&Flag<'_>], args: &[&Arg<'_>], name: &str) -> Key {
let mut found = 0;
let mut key = 0;
let mut flag = 0;
while flag < flags.len() {
if str_eq(flags[flag].name, name) {
found += 1;
key = flags[flag].key;
}
flag += 1;
}
let mut arg = 0;
while arg < args.len() {
if str_eq(args[arg].name, name) {
found += 1;
key = args[arg].key;
}
arg += 1;
}
assert!(
found == 1,
"constraint target must name exactly one argument field in the composed command",
);
key
}
pub const fn command_keys_unique(flags: &[&Flag<'_>], args: &[&Arg<'_>]) -> bool {
let mut flag = 0;
while flag < flags.len() {
let mut other = flag + 1;
while other < flags.len() {
if flags[flag].key == flags[other].key {
return false;
}
other += 1;
}
let mut arg = 0;
while arg < args.len() {
if flags[flag].key == args[arg].key {
return false;
}
arg += 1;
}
flag += 1;
}
let mut arg = 0;
while arg < args.len() {
let mut other = arg + 1;
while other < args.len() {
if args[arg].key == args[other].key {
return false;
}
other += 1;
}
arg += 1;
}
true
}
pub const fn flag_spellings_unique(flags: &[&Flag<'_>]) -> bool {
let mut left = 0;
while left < flags.len() {
let mut right = left + 1;
while right < flags.len() {
let mut long = 0;
while long < flag_long_len(flags[left]) {
let mut other = 0;
while other < flag_long_len(flags[right]) {
if str_eq(flag_long(flags[left], long), flag_long(flags[right], other)) {
return false;
}
other += 1;
}
long += 1;
}
let mut short = 0;
while short < flags[left].shorts.len() {
let mut other = 0;
while other < flags[right].shorts.len() {
if flags[left].shorts[short] == flags[right].shorts[other] {
return false;
}
other += 1;
}
short += 1;
}
right += 1;
}
left += 1;
}
true
}
pub const fn action_flag_spellings_disjoint(actions: &[&Action<'_>], flags: &[&Flag<'_>]) -> bool {
let mut action = 0;
while action < actions.len() {
let mut flag = 0;
while flag < flags.len() {
let mut long = 0;
while long < actions[action].longs.len() {
let mut other = 0;
while other < flag_long_len(flags[flag]) {
if str_eq(actions[action].longs[long], flag_long(flags[flag], other)) {
return false;
}
other += 1;
}
long += 1;
}
let mut short = 0;
while short < actions[action].shorts.len() {
let mut other = 0;
while other < flags[flag].shorts.len() {
if actions[action].shorts[short] == flags[flag].shorts[other] {
return false;
}
other += 1;
}
short += 1;
}
flag += 1;
}
action += 1;
}
true
}
pub const fn positional_layout_valid(args: &[&Arg<'_>]) -> bool {
let mut optional_seen = false;
let mut variadic_seen = false;
let mut index = 0;
while index < args.len() {
let arg = args[index];
if variadic_seen || (arg.required && optional_seen) {
return false;
}
if !arg.required {
optional_seen = true;
}
if arg.variadic {
variadic_seen = true;
}
index += 1;
}
true
}
const fn flag_long_len(flag: &Flag<'_>) -> usize {
flag.longs.len() + flag.aliases.len()
}
const fn flag_long<'a>(flag: &'a Flag<'a>, index: usize) -> &'a str {
if index < flag.longs.len() {
flag.longs[index]
} else {
flag.aliases[index - flag.longs.len()]
}
}
const fn str_eq(left: &str, right: &str) -> bool {
let left = left.as_bytes();
let right = right.as_bytes();
if left.len() != right.len() {
return false;
}
let mut index = 0;
while index < left.len() {
if left[index] != right[index] {
return false;
}
index += 1;
}
true
}
pub const fn output_flag_spellings_disjoint(flags: &[&Flag<'_>]) -> bool {
let reserved_longs = ["output", "fields"];
let reserved_shorts = *b"OF";
let mut flag = 0;
while flag < flags.len() {
let mut long = 0;
while long < flag_long_len(flags[flag]) {
let mut reserved = 0;
while reserved < reserved_longs.len() {
if str_eq(flag_long(flags[flag], long), reserved_longs[reserved]) {
return false;
}
reserved += 1;
}
long += 1;
}
let mut short = 0;
while short < flags[flag].shorts.len() {
let mut reserved = 0;
while reserved < reserved_shorts.len() {
if flags[flag].shorts[short] == reserved_shorts[reserved] {
return false;
}
reserved += 1;
}
short += 1;
}
flag += 1;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::__private::HELP_ACTION;
static ALPHA: Flag<'static> = Flag {
key: 1,
name: "alpha",
longs: &["alpha"],
aliases: &["first"],
shorts: b"a",
..Flag::BOOL
};
static BETA: Flag<'static> =
Flag { key: 2, name: "beta", longs: &["beta"], shorts: b"b", ..Flag::BOOL };
static INPUT: Arg<'static> = Arg { key: 3, name: "input", ..Arg::REQUIRED };
#[test]
fn table_composition_preserves_group_order() {
let flag_groups: &[&[&Flag<'static>]] = &[&[&ALPHA], &[], &[&BETA]];
assert_eq!(table_len(flag_groups), 2);
assert_eq!(concat_flags::<2>(flag_groups), [&ALPHA, &BETA]);
let arg_groups: &[&[&Arg<'static>]] = &[&[], &[&INPUT]];
assert_eq!(concat_args::<1>(arg_groups), [&INPUT]);
const REQUIRES: Constraint =
Constraint { kind: ConstraintKind::Requires, source: 1, target: 3 };
const CONFLICTS: Constraint =
Constraint { kind: ConstraintKind::Conflicts, source: 2, target: 3 };
assert_eq!(concat_constraints::<2>(&[&[REQUIRES], &[CONFLICTS]]), [REQUIRES, CONFLICTS],);
static PRIMARY_HELP: HelpGroup<'static> =
HelpGroup { heading: "Primary", flags: &[&ALPHA], args: &[] };
static SECONDARY_HELP: HelpGroup<'static> =
HelpGroup { heading: "Secondary", flags: &[&BETA], args: &[&INPUT] };
let help_groups: &[&[&HelpGroup<'static>]] = &[&[&PRIMARY_HELP], &[], &[&SECONDARY_HELP]];
assert_eq!(table_len(help_groups), 2);
assert_eq!(concat_help_groups::<2>(help_groups), [&PRIMARY_HELP, &SECONDARY_HELP],);
}
#[test]
fn argument_lookup_resolves_flags_and_positionals() {
assert_eq!(argument_key_by_name(&[&ALPHA], &[&INPUT], "alpha"), ALPHA.key);
assert_eq!(argument_key_by_name(&[&ALPHA], &[&INPUT], "input"), INPUT.key);
}
#[test]
#[should_panic(expected = "constraint target must name exactly one argument field")]
fn argument_lookup_rejects_an_unknown_name() {
let _ = argument_key_by_name(&[&ALPHA], &[&INPUT], "missing");
}
#[test]
#[should_panic(expected = "constraint target must name exactly one argument field")]
fn argument_lookup_rejects_an_ambiguous_name() {
static SAME: Arg<'static> = Arg { key: 4, name: "alpha", ..Arg::REQUIRED };
let _ = argument_key_by_name(&[&ALPHA], &[&SAME], "alpha");
}
#[test]
fn composed_keys_must_be_unique_across_every_argument_kind() {
assert!(command_keys_unique(&[&ALPHA, &BETA], &[&INPUT]));
static FLAG_DUPLICATE: Flag<'static> = Flag { key: 1, ..Flag::BOOL };
static ARG_DUPLICATE: Arg<'static> = Arg { key: 1, ..Arg::REQUIRED };
static OTHER_ARG_DUPLICATE: Arg<'static> = Arg { key: 3, ..Arg::REQUIRED };
assert!(!command_keys_unique(&[&ALPHA, &FLAG_DUPLICATE], &[]));
assert!(!command_keys_unique(&[&ALPHA], &[&ARG_DUPLICATE]));
assert!(!command_keys_unique(&[], &[&INPUT, &OTHER_ARG_DUPLICATE]));
}
#[test]
fn composed_flag_spellings_include_aliases_and_shorts() {
assert!(flag_spellings_unique(&[&ALPHA, &BETA]));
static LONG_COLLISION: Flag<'static> = Flag { longs: &["alpha"], ..Flag::BOOL };
static ALIAS_COLLISION: Flag<'static> = Flag { aliases: &["first"], ..Flag::BOOL };
static SHORT_COLLISION: Flag<'static> = Flag { shorts: b"a", ..Flag::BOOL };
assert!(!flag_spellings_unique(&[&ALPHA, &LONG_COLLISION]));
assert!(!flag_spellings_unique(&[&ALPHA, &ALIAS_COLLISION]));
assert!(!flag_spellings_unique(&[&ALPHA, &SHORT_COLLISION]));
}
#[test]
fn built_in_actions_must_not_collide_with_canonical_alias_or_short_spellings() {
assert!(action_flag_spellings_disjoint(&[&HELP_ACTION], &[&ALPHA]));
static LONG_COLLISION: Flag<'static> = Flag { longs: &["help"], ..Flag::BOOL };
static ALIAS_COLLISION: Flag<'static> = Flag { aliases: &["help"], ..Flag::BOOL };
static SHORT_COLLISION: Flag<'static> = Flag { shorts: b"h", ..Flag::BOOL };
assert!(!action_flag_spellings_disjoint(&[&HELP_ACTION], &[&LONG_COLLISION]));
assert!(!action_flag_spellings_disjoint(&[&HELP_ACTION], &[&ALIAS_COLLISION]));
assert!(!action_flag_spellings_disjoint(&[&HELP_ACTION], &[&SHORT_COLLISION]));
}
#[test]
fn positional_layout_rejects_required_or_anything_after_open_ended_values() {
static OPTIONAL: Arg<'static> =
Arg { key: 4, name: "optional", required: false, ..Arg::REQUIRED };
static REQUIRED: Arg<'static> = Arg { key: 5, name: "required", ..Arg::REQUIRED };
static VARIADIC: Arg<'static> =
Arg { key: 6, name: "variadic", required: false, variadic: true, ..Arg::REQUIRED };
assert!(positional_layout_valid(&[&REQUIRED, &OPTIONAL]));
assert!(!positional_layout_valid(&[&OPTIONAL, &REQUIRED]));
assert!(!positional_layout_valid(&[&VARIADIC, &OPTIONAL]));
}
}