pub type Key = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Constraint {
pub kind: ConstraintKind,
pub source: Key,
pub target: Key,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintKind {
Requires,
Conflicts,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArgumentState {
pub diagnostic: &'static str,
pub given: bool,
pub satisfied: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Action<'a> {
pub name: &'a str,
pub diagnostic: &'a str,
pub help: &'a str,
pub longs: &'a [&'a str],
pub shorts: &'a [u8],
pub kind: ActionKind<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActionKind<'a> {
Help,
Schema,
Version {
short: &'a str,
long: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HelpSection<'a> {
pub heading: &'a str,
pub body: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HelpGroup<'a> {
pub heading: &'a str,
pub flags: &'a [&'a Flag<'a>],
pub args: &'a [&'a Arg<'a>],
}
impl HelpGroup<'static> {
pub const EMPTY: Self = Self { heading: "", flags: &[], args: &[] };
}
pub static HELP_ACTION: Action<'static> = Action {
name: "help",
diagnostic: "--help",
help: "Print help",
longs: &["help"],
shorts: b"h",
kind: ActionKind::Help,
};
pub static SCHEMA_ACTION: Action<'static> = Action {
name: "schema",
diagnostic: "--schema",
help: "Print machine-readable schema",
longs: &["schema"],
shorts: b"S",
kind: ActionKind::Schema,
};
pub static OUTPUT_FLAG: Flag<'static> = Flag {
name: "output",
diagnostic: "--output",
help: Some("Select output format: text or json"),
longs: &["output"],
shorts: b"O",
global: true,
..Flag::VALUE
};
pub static FIELDS_FLAG: Flag<'static> = Flag {
name: "fields",
diagnostic: "--fields",
help: Some("Select output fields (comma-separated)"),
longs: &["fields"],
shorts: b"F",
global: true,
repeatable: true,
..Flag::VALUE
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Command<'a> {
pub name: &'a str,
pub about: Option<&'a str>,
pub description: Option<&'a str>,
pub help_sections: &'a [HelpSection<'a>],
pub help_groups: &'a [&'a HelpGroup<'a>],
pub aliases: &'a [&'a str],
pub actions: &'a [&'a Action<'a>],
pub flags: &'a [&'a Flag<'a>],
pub args: &'a [&'a Arg<'a>],
pub constraints: &'a [Constraint],
pub subcommands: &'a [&'a Self],
pub key: Key,
}
impl Command<'static> {
pub const EMPTY: Self = Self {
name: "",
about: None,
description: None,
help_sections: &[],
help_groups: &[],
aliases: &[],
actions: &[&HELP_ACTION],
flags: &[],
args: &[],
constraints: &[],
subcommands: &[],
key: 0,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flag<'a> {
pub key: Key,
pub name: &'a str,
pub diagnostic: &'a str,
pub help: Option<&'a str>,
pub longs: &'a [&'a str],
pub aliases: &'a [&'a str],
pub shorts: &'a [u8],
pub global: bool,
pub takes_value: bool,
pub accepted_values: &'a [&'a str],
pub repeatable: bool,
pub required: bool,
pub has_default: bool,
pub allow_hyphen_values: bool,
pub allow_negative_numbers: bool,
}
impl Flag<'static> {
pub const BOOL: Self = Self {
key: 0,
name: "",
diagnostic: "",
help: None,
longs: &[],
aliases: &[],
shorts: &[],
global: false,
takes_value: false,
accepted_values: &[],
repeatable: false,
required: false,
has_default: false,
allow_hyphen_values: false,
allow_negative_numbers: false,
};
pub const VALUE: Self = Self { takes_value: true, ..Self::BOOL };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Arg<'a> {
pub key: Key,
pub name: &'a str,
pub help: Option<&'a str>,
pub required: bool,
pub variadic: bool,
pub accepted_values: &'a [&'a str],
pub allow_negative_numbers: bool,
}
impl Arg<'static> {
pub const REQUIRED: Self = Self {
key: 0,
name: "",
help: None,
required: true,
variadic: false,
accepted_values: &[],
allow_negative_numbers: false,
};
}
pub const fn key_base(module: &str, declaration: u32) -> Key {
let bytes = module.as_bytes();
let mut state = declaration;
let mut index = 0;
while index < bytes.len() {
state = (state ^ bytes[index] as u32).wrapping_mul(0x0100_0193);
index += 1;
}
(state as Key) << 32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Named<'a> {
Action(&'a Action<'a>),
Flag {
flag: &'a Flag<'a>,
scope: usize,
},
}
pub(crate) fn long<'a>(
command: &'a Command<'a>,
ancestors: &[&'a Command<'a>],
name: &[u8],
) -> Option<Named<'a>> {
command
.actions
.iter()
.copied()
.find(|action| action.longs.iter().any(|long| long.as_bytes() == name))
.map(Named::Action)
.or_else(|| {
command
.flags
.iter()
.copied()
.find(|flag| {
flag.longs.iter().chain(flag.aliases).any(|long| long.as_bytes() == name)
})
.map(|flag| Named::Flag { flag, scope: ancestors.len() })
})
.or_else(|| {
ancestors.iter().enumerate().rev().find_map(|(scope, command)| {
command
.flags
.iter()
.copied()
.find(|flag| {
flag.global
&& flag
.longs
.iter()
.chain(flag.aliases)
.any(|long| long.as_bytes() == name)
})
.map(|flag| Named::Flag { flag, scope })
})
})
}
pub(crate) fn short<'a>(
command: &'a Command<'a>,
ancestors: &[&'a Command<'a>],
spelling: u8,
) -> Option<Named<'a>> {
command
.actions
.iter()
.copied()
.find(|action| action.shorts.contains(&spelling))
.map(Named::Action)
.or_else(|| {
command
.flags
.iter()
.copied()
.find(|flag| flag.shorts.contains(&spelling))
.map(|flag| Named::Flag { flag, scope: ancestors.len() })
})
.or_else(|| {
ancestors.iter().enumerate().rev().find_map(|(scope, command)| {
command
.flags
.iter()
.copied()
.find(|flag| flag.global && flag.shorts.contains(&spelling))
.map(|flag| Named::Flag { flag, scope })
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_base_is_stable() {
assert_eq!(key_base("argx::tests", 0x1234_5678), 0x6570_cc45_0000_0000);
}
#[test]
fn module_path_contributes_to_key_base() {
assert_ne!(key_base("argx::add", 42), key_base("argx::remove", 42));
}
}