use crate::{CowStr, ParsedEnv, introspection::ProgramOptionMeta};
use std::borrow::Cow;
use std::fmt;
#[doc(hidden)]
#[derive(Clone, Copy, Debug)]
pub struct DisplayFn(pub fn(&mut fmt::Formatter) -> fmt::Result);
impl fmt::Display for DisplayFn {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self.0)(f)
}
}
impl From<fn(&mut fmt::Formatter) -> fmt::Result> for DisplayFn {
fn from(f: fn(&mut fmt::Formatter) -> fmt::Result) -> Self {
DisplayFn(f)
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ParseType {
Flag,
Parameter,
Repeat,
}
impl fmt::Display for ParseType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Flag => write!(f, "Flag"),
Self::Parameter => write!(f, "Parameter"),
Self::Repeat => write!(f, "Repeat"),
}
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct ProgramOption {
pub id: CowStr,
pub parse_type: ParseType,
pub description: Option<CowStr>,
pub short_form: Option<char>,
pub long_form: Option<CowStr>,
pub aliases: Cow<'static, [CowStr]>,
pub env_form: Option<CowStr>,
pub env_aliases: Cow<'static, [CowStr]>,
pub default_help_str: Option<DisplayFn>,
pub is_required: bool,
pub allow_hyphen_values: bool,
pub allow_negative_numbers: bool,
pub default_if_missing: Option<CowStr>,
pub secret: Option<bool>,
pub is_positional: bool,
pub has_serde_source: bool,
}
impl PartialEq for ProgramOption {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.parse_type == other.parse_type
}
}
impl Eq for ProgramOption {}
impl PartialOrd for ProgramOption {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ProgramOption {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id
.cmp(&other.id)
.then_with(|| self.parse_type.cmp(&other.parse_type))
}
}
impl ProgramOption {
pub fn apply_flatten_prefixes(
self,
id_prefix: &str,
long_prefix: &str,
env_prefix: &str,
description_prefix: &str,
) -> ProgramOption {
let ProgramOption {
mut id,
parse_type,
mut description,
short_form,
mut long_form,
mut aliases,
mut env_form,
mut env_aliases,
default_help_str,
is_required,
allow_hyphen_values,
allow_negative_numbers,
default_if_missing,
secret,
is_positional,
has_serde_source,
} = self;
id.to_mut().insert_str(0, id_prefix);
if let Some(long_form) = long_form.as_mut() {
if !long_prefix.is_empty() {
long_form.to_mut().insert_str(0, long_prefix);
}
}
if !long_prefix.is_empty() {
for alias in aliases.to_mut().iter_mut() {
alias.to_mut().insert_str(0, long_prefix);
}
}
if let Some(env_form) = env_form.as_mut() {
if !env_prefix.is_empty() {
env_form.to_mut().insert_str(0, env_prefix);
}
}
if !env_prefix.is_empty() {
for env_alias in env_aliases.to_mut().iter_mut() {
env_alias.to_mut().insert_str(0, env_prefix);
}
}
if let Some(desc) = description.as_mut() {
if !description_prefix.is_empty() {
let desc = desc.to_mut();
let ws = if description_prefix.contains('\n') || desc.contains('\n') {
'\n'
} else {
' '
};
desc.insert(0, ws);
desc.insert_str(0, description_prefix);
}
}
ProgramOption {
id,
parse_type,
description,
short_form,
long_form,
aliases,
env_form,
env_aliases,
default_help_str,
is_required,
allow_hyphen_values,
allow_negative_numbers,
default_if_missing,
secret,
is_positional,
has_serde_source,
}
}
#[inline]
pub fn skip_short_forms(mut self, skip_these: &[char], was_skipped: &mut [bool]) -> Self {
if let Some(short) = self.short_form {
if let Some(pos) = skip_these.iter().position(|to_skip| *to_skip == short) {
self.short_form = None;
was_skipped[pos] = true;
}
}
self
}
#[inline]
pub fn make_optional(mut self) -> Self {
self.is_required = false;
self
}
#[inline]
pub fn is_secret(&self) -> bool {
self.secret.unwrap_or(false)
}
#[inline]
pub fn has_args_source(&self) -> bool {
self.short_form.is_some() || self.long_form.is_some() || self.is_positional
}
pub fn print(
&self,
stream: &mut impl std::fmt::Write,
env: Option<&ParsedEnv>,
) -> Result<(), std::fmt::Error> {
let dash = if self.short_form.is_some() { '-' } else { ' ' };
let short = self.short_form.unwrap_or(' ');
let comma = if self.short_form.is_some() && self.long_form.is_some() {
','
} else {
' '
};
write!(stream, " {dash}{short}{comma} ")?;
if let Some(long) = self.long_form.as_ref() {
write!(stream, "--{long} ")?;
}
if matches!(self.parse_type, ParseType::Parameter | ParseType::Repeat) {
write!(stream, "<{}>", self.id)?;
}
writeln!(stream)?;
if let Some(desc) = self.description.as_ref() {
writeln!(stream, " {}", desc.replace('\n', "\n "))?;
}
if let Some(name) = self.env_form.as_deref() {
if let Some(env) = env.filter(|_| !self.is_secret()) {
let cur_val = env.get_lossy_or_default(name);
writeln!(stream, " [env: {name}={cur_val}]")?;
} else {
writeln!(stream, " [env: {name}]")?;
}
}
for name in self.env_aliases.iter() {
if let Some(env) = env.filter(|_| !self.is_secret()) {
let cur_val = env.get_lossy_or_default(name);
writeln!(stream, " [env: {name}={cur_val}]")?;
} else {
writeln!(stream, " [env: {name}]")?;
}
}
if let Some(fmt_fn) = self.default_help_str {
writeln!(stream, " [default: {}]", fmt_fn)?;
}
if self.is_secret() {
writeln!(stream, " [secret]")?;
}
Ok(())
}
}
impl crate::introspection::Sealed for ProgramOption {}
impl ProgramOptionMeta for ProgramOption {
fn id(&self) -> &dyn fmt::Display {
&self.id
}
fn description(&self) -> Option<&dyn fmt::Display> {
self.description.as_ref().map(|d| d as &dyn fmt::Display)
}
fn short_form(&self) -> Option<char> {
self.short_form
}
fn long_form(&self) -> Option<&dyn fmt::Display> {
self.long_form.as_ref().map(|l| l as &dyn fmt::Display)
}
fn is_positional(&self) -> bool {
self.is_positional
}
fn env_form(&self) -> Option<&dyn fmt::Display> {
self.env_form.as_ref().map(|e| e as &dyn fmt::Display)
}
fn has_serde_source(&self) -> bool {
self.has_serde_source
}
fn is_required(&self) -> bool {
self.is_required
}
fn default_help_str(&self) -> Option<&dyn fmt::Display> {
self.default_help_str
.as_ref()
.map(|d| d as &dyn fmt::Display)
}
}