pub type Key = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetadataEntry<'a> {
pub key: &'a str,
pub value: MetadataValue<'a>,
}
#[derive(Debug, Clone, Copy)]
pub enum MetadataValue<'a> {
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(&'a str),
Array(&'a [Self]),
Object(&'a [MetadataEntry<'a>]),
}
impl PartialEq for MetadataValue<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Null, Self::Null) => true,
(Self::Bool(left), Self::Bool(right)) => left == right,
(Self::Integer(left), Self::Integer(right)) => left == right,
(Self::Float(left), Self::Float(right)) => left.to_bits() == right.to_bits(),
(Self::String(left), Self::String(right)) => left == right,
(Self::Array(left), Self::Array(right)) => left == right,
(Self::Object(left), Self::Object(right)) => left == right,
_ => false,
}
}
}
impl Eq for MetadataValue<'_> {}
#[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 OneOf<'a> {
pub members: &'a [Key],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnyOf<'a> {
pub members: &'a [Key],
}
#[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,
};
#[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 metadata: &'a [MetadataEntry<'a>],
pub actions: &'a [&'a Action<'a>],
pub flags: &'a [&'a Flag<'a>],
pub args: &'a [&'a Arg<'a>],
pub constraints: &'a [Constraint],
pub one_of: &'a [OneOf<'a>],
pub any_of: &'a [AnyOf<'a>],
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: &[],
metadata: &[],
actions: &[&HELP_ACTION],
flags: &[],
args: &[],
constraints: &[],
one_of: &[],
any_of: &[],
subcommands: &[],
key: 0,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueSchema {
Lexical,
Boolean,
I8,
I16,
I32,
I64,
I128,
Isize,
U8,
U16,
U32,
U64,
U128,
Usize,
Number,
Date,
DateTime,
Uuid,
Url,
}
impl ValueSchema {
#[must_use]
pub(crate) const fn json_type(self) -> &'static str {
match self {
Self::Boolean => "boolean",
Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::I128
| Self::Isize
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::U128
| Self::Usize => "integer",
Self::Number => "number",
Self::Lexical | Self::Date | Self::DateTime | Self::Uuid | Self::Url => "string",
}
}
#[must_use]
pub(crate) const fn format(self) -> Option<&'static str> {
match self {
Self::Date if cfg!(feature = "chrono") => Some("date"),
Self::DateTime if cfg!(feature = "chrono") => Some("date-time"),
Self::Uuid if cfg!(feature = "uuid") => Some("uuid"),
Self::Url if cfg!(feature = "url") => Some("uri"),
Self::Lexical
| Self::Boolean
| Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::I128
| Self::Isize
| Self::U8
| Self::U16
| Self::U32
| Self::U64
| Self::U128
| Self::Usize
| Self::Number
| Self::Date
| Self::DateTime
| Self::Uuid
| Self::Url => None,
}
}
}
#[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 long_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 value_schema: ValueSchema,
pub repeatable: bool,
pub required: bool,
pub has_default: bool,
pub default_value: Option<&'a str>,
pub allow_hyphen_values: bool,
pub allow_negative_numbers: bool,
}
impl Flag<'static> {
pub const BOOL: Self = Self {
key: 0,
name: "",
diagnostic: "",
help: None,
long_help: None,
longs: &[],
aliases: &[],
shorts: &[],
global: false,
takes_value: false,
accepted_values: &[],
value_schema: ValueSchema::Lexical,
repeatable: false,
required: false,
has_default: false,
default_value: None,
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 long_help: Option<&'a str>,
pub required: bool,
pub variadic: bool,
pub accepted_values: &'a [&'a str],
pub value_schema: ValueSchema,
pub allow_negative_numbers: bool,
}
impl Arg<'static> {
pub const REQUIRED: Self = Self {
key: 0,
name: "",
help: None,
long_help: None,
required: true,
variadic: false,
accepted_values: &[],
value_schema: ValueSchema::Lexical,
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 module_path_contributes_to_key_base() {
assert_ne!(key_base("argx::add", 42), key_base("argx::remove", 42));
}
}