use clap::builder::{RangedI64ValueParser, RangedU64ValueParser, ValueParser};
use clap::parser::ValueSource;
use clap::{Arg, ArgMatches, Command, ValueHint};
use xilem_html::{elements as el, Adapt, MessageResult, View, ViewExt, ViewMarker, ViewSequence};
use crate::AppState;
enum ValueType {
Path,
Command,
Name,
Url,
Email,
String,
Range,
Bool,
Enum,
Map,
Unknown,
}
impl ValueType {
fn name(&self) -> &str {
match self {
Self::Path => "path",
Self::Command => "command",
Self::Name => "name",
Self::Url => "url",
Self::Email => "email",
Self::String => "string",
Self::Range => "range",
Self::Bool => "bool",
Self::Enum => "enum",
Self::Map => "map",
Self::Unknown => "unknown",
}
}
}
impl From<&Arg> for ValueType {
fn from(value: &Arg) -> Self {
match value.get_value_hint() {
ValueHint::AnyPath => Self::Path,
ValueHint::FilePath => Self::Path,
ValueHint::DirPath => Self::Path,
ValueHint::ExecutablePath => Self::Path,
ValueHint::CommandName => Self::Command,
ValueHint::CommandString => Self::Command,
ValueHint::CommandWithArguments => Self::Command,
ValueHint::Username => Self::Name,
ValueHint::Hostname => Self::Name,
ValueHint::Url => Self::Url,
ValueHint::EmailAddress => Self::Email,
_ => {
let type_id = value.get_value_parser().type_id();
if type_id == ValueParser::new(RangedU64ValueParser::<u8>::new()).type_id() {
Self::Range
} else if type_id == ValueParser::new(RangedU64ValueParser::<u16>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::new(RangedU64ValueParser::<u32>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::new(RangedU64ValueParser::<u64>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::new(RangedI64ValueParser::<i8>::new()).type_id() {
Self::Range
} else if type_id == ValueParser::new(RangedI64ValueParser::<i16>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::new(RangedI64ValueParser::<i32>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::new(RangedI64ValueParser::<i64>::new()).type_id()
{
Self::Range
} else if type_id == ValueParser::string().type_id() {
Self::String
} else {
Self::Unknown
}
}
}
}
}
fn type_description(arg: &Arg) -> impl View<Arg> + ViewMarker {
let value_type = ValueType::from(arg);
el::details(el::summary(value_type.name().to_string()))
}
fn aliases(arg: &Arg) -> impl ViewSequence<Arg> {
let mut aliases = Vec::new();
if let Some(long_aliases) = arg.get_short_and_visible_aliases() {
aliases.extend(
long_aliases
.iter()
.map(|alias| alias.to_string())
.filter(|alias| alias != arg.get_id()),
);
}
if let Some(long_aliases) = arg.get_long_and_visible_aliases() {
aliases.extend(
long_aliases
.iter()
.map(|alias| alias.to_string())
.filter(|alias| alias != arg.get_id()),
);
}
if let Some(env_var) = arg.get_env() {
aliases.push(env_var.to_string_lossy().to_string())
}
if aliases.is_empty() {
return None;
}
Some(el::section((
el::h4("Also known as:"),
el::ul(
aliases
.into_iter()
.map(el::samp)
.map(el::li)
.collect::<Vec<_>>(),
),
)))
}
fn source(arg_matches: Option<&ArgMatches>, arg: &Arg) -> impl View<Arg> + ViewMarker {
el::span(if let Some(arg_matches) = arg_matches.as_ref() {
if let Some(source) = arg_matches.value_source(arg.get_id().as_str()) {
match source {
ValueSource::DefaultValue => "default",
ValueSource::EnvVariable => "environment",
ValueSource::CommandLine => "cmdline",
_ => "todo",
}
} else {
"none"
}
} else if !arg.get_default_values().is_empty() {
"Error, potential defaults:"
} else {
"Error parsing argument"
})
}
fn matched_values(arg_matches: Option<&ArgMatches>, arg: &Arg) -> impl View<Arg> + ViewMarker {
let default_matches = ArgMatches::default();
let arg_matches = arg_matches.unwrap_or(&default_matches);
el::ul(
if let Ok(raw_occurences) = arg_matches.try_get_raw_occurrences(arg.get_id().as_str()) {
raw_occurences
.unwrap_or_default()
.map(|os_strings| {
el::li(el::kbd(
os_strings
.map(|s| s.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(" "),
))
})
.collect::<Vec<_>>()
} else {
arg.get_default_values()
.into_iter()
.map(|os_str| os_str.to_string_lossy().to_string())
.map(el::kbd)
.map(el::li)
.collect::<Vec<_>>()
},
)
}
fn item(arg_matches: Option<&ArgMatches>, arg: &Arg) -> impl View<Arg> + ViewMarker {
el::section((
el::h3(arg.get_id().to_string().to_uppercase()),
crate::util::collapsible_docs(arg.get_help(), arg.get_long_help()),
type_description(arg),
arg.get_num_args().map(|arg_range| arg_range.to_string()),
source(arg_matches, arg),
matched_values(arg_matches, arg),
aliases(arg),
))
.class("arg_item")
}
pub fn args_view(
command: &Command,
arg_matches: Option<&ArgMatches>,
) -> impl ViewSequence<AppState> {
command
.get_opts()
.map(|arg| {
Adapt::new(
|_state, _thunk| {
MessageResult::Nop
},
item(arg_matches, arg),
)
})
.collect::<Vec<_>>()
}