use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum NameNamespace {
Short,
Long,
Termcap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CapabilityVersion {
SystemV,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CapabilityMetadata {
long: &'static str,
short: &'static str,
termcap: Option<&'static str>,
parameters: &'static [ParameterType],
introduced: CapabilityVersion,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParameterType {
Number,
Bytes,
}
impl CapabilityMetadata {
pub const fn short_name(&self) -> &'static str {
self.short
}
pub const fn long_name(&self) -> &'static str {
self.long
}
pub const fn termcap_name(&self) -> Option<&'static str> {
self.termcap
}
pub const fn parameters(&self) -> &'static [ParameterType] {
self.parameters
}
pub const fn introduced(&self) -> CapabilityVersion {
self.introduced
}
pub const fn name(&self, namespace: NameNamespace) -> Option<&'static str> {
match namespace {
NameNamespace::Short => Some(self.short),
NameNamespace::Long => Some(self.long),
NameNamespace::Termcap => self.termcap,
}
}
pub(crate) fn matches_any(&self, name: &str) -> bool {
self.short == name || self.long == name || self.termcap == Some(name)
}
}
include!(concat!(env!("OUT_DIR"), "/caps_generated.rs"));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityKind {
Boolean,
Number,
String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CapabilityId {
Boolean(BooleanCap),
Number(NumericCap),
String(StringCap),
}
impl CapabilityId {
pub const fn kind(self) -> CapabilityKind {
match self {
Self::Boolean(_) => CapabilityKind::Boolean,
Self::Number(_) => CapabilityKind::Number,
Self::String(_) => CapabilityKind::String,
}
}
pub fn metadata(self) -> &'static CapabilityMetadata {
match self {
Self::Boolean(cap) => cap.metadata(),
Self::Number(cap) => cap.metadata(),
Self::String(cap) => cap.metadata(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Lookup {
NotFound,
Found(CapabilityId),
Ambiguous(Vec<CapabilityId>),
}
pub fn lookup_all(namespace: NameNamespace, name: &str) -> Vec<CapabilityId> {
let mut found = Vec::new();
found.extend(
BooleanCap::ALL
.iter()
.copied()
.filter(|cap| cap.metadata().name(namespace) == Some(name))
.map(CapabilityId::Boolean),
);
found.extend(
NumericCap::ALL
.iter()
.copied()
.filter(|cap| cap.metadata().name(namespace) == Some(name))
.map(CapabilityId::Number),
);
found.extend(
StringCap::ALL
.iter()
.copied()
.filter(|cap| cap.metadata().name(namespace) == Some(name))
.map(CapabilityId::String),
);
found
}
pub fn lookup(namespace: NameNamespace, name: &str) -> Lookup {
let found = lookup_all(namespace, name);
match found.len() {
0 => Lookup::NotFound,
1 => Lookup::Found(found[0]),
_ => Lookup::Ambiguous(found),
}
}
pub fn all_capabilities() -> impl Iterator<Item = CapabilityId> + Clone {
BooleanCap::ALL
.iter()
.copied()
.map(CapabilityId::Boolean)
.chain(NumericCap::ALL.iter().copied().map(CapabilityId::Number))
.chain(StringCap::ALL.iter().copied().map(CapabilityId::String))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_table_matches_ncurses_fixed_counts() {
assert_eq!(BooleanCap::COUNT, 44);
assert_eq!(NumericCap::COUNT, 39);
assert_eq!(StringCap::COUNT, 414);
assert_eq!(
StringCap::CURSOR_ADDRESS.metadata().parameters(),
&[ParameterType::Number, ParameterType::Number]
);
assert_eq!(all_capabilities().count(), 497);
assert!(matches!(
lookup(NameNamespace::Short, "cup"),
Lookup::Found(CapabilityId::String(StringCap::CURSOR_ADDRESS))
));
assert_eq!(lookup_all(NameNamespace::Termcap, "ML").len(), 2);
assert!(matches!(
lookup(NameNamespace::Termcap, "ML"),
Lookup::Ambiguous(candidates) if candidates.len() == 2
));
}
}