Refactor argument handling into a separate file

finchie
Nov 18, 2023, 2:23 PM
MIY7QPYK3EZTOGZF5PWU2ZKM7NG5ANWKNHS56FVTOY7PPATWLSSAC

Dependencies

  • [2] G54GZBS4 Remove dependency on clap patches
  • [3] JFJVY57R List `clap` argument IDs
  • [4] REI53XR4 Render argument state as HTML
  • [5] ZYNEMGAZ Use generated Typst code from Rust
  • [6] BMG4FSHN Add basic `clap` support
  • [7] C73UJ7ZY Create simple `xilem_html` demo
  • [8] BSJYWOYS Implement MVP Typst embedding
  • [9] 4MMVEN5Y Move generated docs inside of parent `docs` module
  • [10] A4E5KLI2 Turn arguments into sidebar

Change contents

  • edit in src/main.rs at line 1
    [3.49]
    [2.0]
    pub mod arg;
  • replacement in src/main.rs at line 4
    [3.70][2.14:99]()
    use clap::parser::ValueSource;
    use clap::{value_parser, ArgMatches, CommandFactory};
    [3.70]
    [3.123]
    use clap::{ArgMatches, Command, CommandFactory};
  • replacement in src/main.rs at line 6
    [3.141][2.100:181]()
    use xilem_html::{document_body, elements as el, App, View, ViewExt, ViewMarker};
    [3.141]
    [3.0]
    use xilem_html::{document_body, elements as el, App, View, ViewExt};
  • replacement in src/main.rs at line 10
    [3.209][3.209:248]()
    struct AppState {
    command: String,
    [3.190]
    [2.182]
    pub struct AppState {
    input: String,
    command: Command,
  • replacement in src/main.rs at line 19
    [2.303][2.303:339]()
    command: String::new(),
    [2.303]
    [2.339]
    input: String::new(),
    command: MockApp::command(),
  • replacement in src/main.rs at line 28
    [3.323][3.323:359]()
    self.command = new_command;
    [3.323]
    [3.367]
    self.input = new_command;
  • replacement in src/main.rs at line 31
    [3.243][3.243:292]()
    let args_iter = self.command.split(' ');
    [3.243]
    [3.109]
    let args_iter = self.input.split(' ');
  • edit in src/main.rs at line 37
    [3.577][3.577:579](),[3.685][3.280:281](),[3.280][3.280:281](),[3.281][2.576:1061](),[2.1061][3.631:632](),[3.631][3.631:632](),[3.632][2.1062:1257](),[2.1257][3.1347:1354](),[3.1347][3.1347:1354](),[3.1354][2.1258:1259](),[2.1259][3.1354:1368](),[3.1354][3.1354:1368](),[3.1368][2.1260:1378](),[2.1378][3.1570:1611](),[3.1570][3.1570:1611](),[3.1611][2.1379:1802](),[2.1802][3.1710:1766](),[3.1710][3.1710:1766]()
    }
    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<_>>(),
    ),
    ))
  • replacement in src/main.rs at line 40
    [3.692][3.1767:1797]()
    let input = el::input("")
    [3.692]
    [3.722]
    let input = el::input(())
  • replacement in src/main.rs at line 50
    [3.921][2.1973:2286](),[2.2286][3.172:209](),[3.172][3.172:209]()
    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"),
    [3.921]
    [3.1003]
    el::br(()),
    arg::args_view(state),
  • replacement in src/cli.rs at line 8
    [3.729][3.729:753]()
    #[arg(short, long)]
    [3.729]
    [3.753]
    ///
    /// A longer explanation.
    /// This explanation takes up multiple lines!
    /// Wow!
    #[arg(short = 'n', long, alias = "nom", env = "NAME_ENV_VAR")]
  • file addition: arg.rs (----------)
    [3.15]
    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")
    }
  • replacement in Cargo.toml at line 10
    [3.1345][2.2287:2364]()
    clap = { version = "4.4.8", features = ["derive"] }
    # clap_builder = "4.4.8"
    [3.1345]
    [3.1345]
    clap = { version = "4.4.8", features = ["derive", "env"] }