This is almost a complete re-write of the argument handling code, with everything being much more robust than before. This version is also much more idiomatic Xilem code than before, and hopefully that will continue to improve over time, with message-handling being the next obvious step.
MIY7QPYK3EZTOGZF5PWU2ZKM7NG5ANWKNHS56FVTOY7PPATWLSSAC
G54GZBS464DFT2224SUTRWPRRSLUQKWDXY2YA7USW7UZ7VPXIHEAC
C73UJ7ZYG4EE3YTK3N66GXPNWJHEBSRE4PDQBWMN6SKQ3U6ZYKXAC
BMG4FSHNV54VXDHNUVGZOMXQJWLFSUF3M5NCN7GJETNIF3QTHELQC
ZYNEMGAZXWHIWGNPB2RTYG3JWTH5Y5XY4JWJ3TTPANIOORTCLISAC
JFJVY57RWKT6YJ62MWWOHXSLASLWPORFUGF67TVTW4FW7XBZEAUQC
REI53XR4WDH5EKLTXTFVTOVWCCFRWTFH4GQS6DSNM5LSRFTX7HJQC
A4E5KLI2CEHJLO6WUME2VIXPK4C2DXHCBVEK2TJTLHGCRCZ2ZC7QC
}
fn arg_item(matches: &ArgMatches, arg: &clap::Arg) -> impl View<AppState> + ViewMarker {
tracing::debug!("`{:?}`", matches.contains_id("name"));
let source = if let Some(source) = matches.value_source(arg.get_id().as_str()) {
match source {
ValueSource::DefaultValue => "default",
ValueSource::EnvVariable => "environment",
ValueSource::CommandLine => "cmdline",
_ => "todo",
}
} else {
"none"
};
let type_id = arg.get_value_parser().type_id();
let type_name = if type_id == value_parser!(String).type_id() {
"string".to_string()
} else {
format!("{:?}", type_id)
};
el::div((
el::div((type_name, el::div("test").attr("class", "hover_content")))
.attr("class", "hoverable"),
el::div(source),
el::ul(
matches
.get_raw_occurrences(&arg.get_id().as_str())
.unwrap_or_default()
.map(|os_strings| {
el::li({
os_strings
.map(|s| s.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(" ")
})
})
.collect::<Vec<_>>(),
),
))
el::div((el::div(
MockApp::command()
.get_opts()
.filter(|arg| arg_matches.try_contains_id(arg.get_id().as_str()).is_ok())
.map(|arg| arg_item(&arg_matches, arg))
.collect::<Vec<_>>(),
)
.attr("id", "sidebar"),))
.attr("class", "container"),
el::br(()),
arg::args_view(state),
use clap::parser::ValueSource;
use clap::{value_parser, Arg};
use xilem_html::{elements as el, Adapt, MessageResult, View, ViewMarker, ViewSequence};
use crate::AppState;
#[non_exhaustive]
enum ArgType {
String,
Other,
}
impl From<&Arg> for ArgType {
fn from(value: &Arg) -> Self {
let type_id = value.get_value_parser().type_id();
if type_id == value_parser!(String).type_id() {
Self::String
} else {
Self::Other
}
}
}
fn type_description(arg: &Arg) -> impl View<Arg> + ViewMarker {
let supported_type_description = ArgType::from(arg);
el::div((
match supported_type_description {
ArgType::String => "string",
ArgType::Other => "other",
},
el::div("placeholder content").attr("class", "hover_content"),
))
.attr("class", "hoverable")
}
fn env(arg: &Arg) -> impl ViewSequence<Arg> {
arg.get_env()
.map(|variable| variable.to_string_lossy().to_string())
}
fn aliases(arg: &Arg) -> impl ViewSequence<Arg> {
let aliases = if let Some(aliases) = arg.get_all_aliases() {
aliases
.iter()
.map(|alias| alias.to_string())
.filter(|alias| alias != arg.get_id())
.collect::<Vec<String>>()
} else {
// Ignore if there's no aliases
return None;
};
// Ignore if all aliases have been filtered out.
// This will usually happen when there is 1 alias returned by get_all_aliases,
// which is the long argument version, which is equivalent to the argument id
// and is therefore redundant
if aliases.is_empty() {
return None;
}
Some(el::div((
el::h4("Also known as:"),
el::ul(aliases.into_iter().map(el::li).collect::<Vec<el::Li<_>>>()),
)))
}
fn source(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
String::from(if let Ok(arg_matches) = state.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 {
"error"
})
}
fn matched_values(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
el::ul(
state
.arg_matches
.as_ref()
.iter()
.map(|arg_matches| {
arg_matches
.get_raw_occurrences(&arg.get_id().as_str())
.unwrap_or_default()
.map(|os_strings| {
el::li(
os_strings
.map(|s| s.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(" "),
)
})
})
.flatten()
.collect::<Vec<el::Li<_>>>(),
)
}
fn item(state: &AppState, arg: &Arg) -> impl View<Arg> + ViewMarker {
el::div((
// Name of the argument
el::h3(arg.get_id().to_string().to_uppercase()),
// The argument's type info
type_description(arg),
el::br(()),
// Alternate sources
el::div((env(arg), aliases(arg))),
// Hand-written documentation
el::div((
// One-line short help
arg.get_help()
.map(|short_help| el::i(short_help.to_string())),
el::br(()),
// Long-form help
arg.get_long_help()
.map(|long_help| el::q(long_help.to_string())),
)),
// Source location (defaults, command line etc)
source(state, arg),
// Number of allowed arguments
arg.get_num_args().map(|arg_range| arg_range.to_string()),
// Matched values
matched_values(state, arg),
))
}
pub fn args_view(state: &mut AppState) -> impl View<AppState> + ViewMarker {
el::div(if state.arg_matches.is_ok() {
state
.command
.get_opts()
.map(|arg| {
Adapt::new(
|_state, _thunk| {
// TODO: actually re-compute this value
MessageResult::Nop
},
item(state, arg),
)
})
.collect::<Vec<_>>()
} else {
Vec::new()
})
.attr("id", "sidebar")
}