use std::{fmt::Display, hash::Hash};
#[inline]
pub fn short<S: Into<char>>(s: S) -> Full {
Full::from(Cli::Short(s.into()))
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn long<L: Into<String>>(l: L) -> Full {
Full::from(Cli::Long(l.into()))
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn both<S: Into<char>, L: Into<String>>(s: S, l: L) -> Full {
Full::from(Cli::Both(s.into(), l.into()))
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn env<E: Into<String>>(e: E) -> Full {
Full {
cli: None,
env: Some(e.into()),
#[cfg(feature = "help")]
doc: None,
}
}
#[derive(Debug, Clone)]
pub struct Full {
pub(crate) cli: Option<Cli>,
pub(crate) env: Option<String>,
#[cfg(feature = "help")]
pub doc: Option<String>,
}
impl Full {
#[must_use]
pub fn cli(mut self, tag: Cli) -> Self {
self.cli = Some(tag);
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn env<S: Into<String>>(mut self, name: S) -> Self {
self.env = Some(name.into());
self
}
#[must_use]
#[cfg(feature = "help")]
pub fn doc<S: Into<String>>(mut self, doc: S) -> Self {
let doc = doc.into();
if doc.is_empty() {
self.doc = None;
self
} else {
self.doc = Some(doc);
self
}
}
pub fn has_cli(&self) -> bool {
self.cli.is_some()
}
pub fn has_env(&self) -> bool {
self.env.is_some()
}
pub fn matches_cli(&self, tag: &str) -> bool {
self.cli.as_ref().is_some_and(|t| t.matches(tag))
}
pub fn matches_long(&self, long: &str) -> bool {
self.cli.as_ref().is_some_and(|tag| tag.matches_long(long))
}
pub fn matches_short(&self, short: char) -> bool {
self.cli
.as_ref()
.is_some_and(|tag| tag.matches_short(short))
}
pub fn matches_env(&self, env: &str) -> bool {
self.env.as_ref().is_some_and(|arg| arg == env)
}
}
impl From<Cli> for Full {
fn from(tag: Cli) -> Self {
Self {
cli: Some(tag),
env: None,
#[cfg(feature = "help")]
doc: None,
}
}
}
impl Hash for Full {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
if let Some(tag) = &self.cli {
core::mem::discriminant(tag).hash(state);
}
if let Some(arg) = &self.env {
arg.hash(state);
}
}
}
#[derive(Debug, Clone)]
pub enum Cli {
Short(char),
Long(String),
Both(char, String),
}
impl Cli {
pub fn env(self, env: String) -> Full {
Full {
cli: Some(self),
env: Some(env),
#[cfg(feature = "help")]
doc: None,
}
}
pub fn matches(&self, tag: &str) -> bool {
if let Some(tag) = tag.strip_prefix("--") {
self.matches_long(tag)
} else if let Some(tag) = tag.strip_prefix('-') {
if let Some(ch) = tag.chars().next() {
self.matches_short(ch)
} else {
false
}
} else {
false
}
}
pub fn matches_long(&self, long: &str) -> bool {
match self {
Cli::Short(_) => false,
Cli::Long(l) | Cli::Both(_, l) => l == long,
}
}
pub fn matches_short(&self, short: char) -> bool {
match self {
Cli::Long(_) => false,
Cli::Short(s) | Cli::Both(s, _) => *s == short,
}
}
}
impl PartialEq for Cli {
fn eq(&self, other: &Self) -> bool {
match self {
Self::Short(s) => match other {
Self::Short(o) | Self::Both(o, _) => s == o,
Self::Long(_) => false,
},
Self::Long(s) => match other {
Self::Long(o) | Self::Both(_, o) => s == o,
Self::Short(_) => false,
},
Self::Both(s1, s2) => match other {
Self::Short(o) => s1 == o,
Self::Long(o) => s2 == o,
Self::Both(o1, o2) => (s1 == o1) || (s2 == o2),
},
}
}
}
impl Display for Cli {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Short(ch) => write!(f, "-{ch}"),
Self::Long(s) => write!(f, "--{s}"),
Self::Both(ch, s) => write!(f, "-{ch} / --{s}"),
}
}
}