mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-18 18:39:45 -07:00
add modifiers.rs
This commit is contained in:
committed by
Will Greenberg
parent
30c4cb0e0c
commit
3b3532d3fd
@@ -1,54 +1,96 @@
|
||||
//! Combines the values we care about from the clap CLI and the modifiers module into something
|
||||
//! serializable by serde.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
use crate::modifiers;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Command<'a> {
|
||||
subcommands: Vec<Subcommand<'a>>,
|
||||
}
|
||||
|
||||
impl Command<'_> {
|
||||
pub fn new(clap_command: &clap::Command) -> Command<'_> {
|
||||
pub fn new(command: &clap::Command) -> Command<'_> {
|
||||
let subcommand_map: HashMap<&str, &clap::Command> = command
|
||||
.get_subcommands()
|
||||
.map(|s| (s.get_name(), s))
|
||||
.collect();
|
||||
|
||||
Command {
|
||||
subcommands: clap_command
|
||||
.get_subcommands()
|
||||
// at least for now, we filter out subcommands that themselves have subcommands like
|
||||
// "util" as supporting these would require additional changes to both the frontend
|
||||
// and this module
|
||||
.filter(|c| !c.has_subcommands())
|
||||
.map(|c| Subcommand::new(c))
|
||||
// this resulting vector contains the subcommands that are found in both
|
||||
// command.get_subcommands() and modifiers::subcommand_modifiers() in the order defined
|
||||
// by subcommand_modifiers()
|
||||
subcommands: modifiers::subcommand_modifiers()
|
||||
.iter()
|
||||
.filter_map(|modifier| {
|
||||
subcommand_map
|
||||
.get(modifier.command)
|
||||
.map(|subcommand| Subcommand::new(subcommand, modifier))
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Argument<'a> {
|
||||
name: &'a str,
|
||||
advanced: bool,
|
||||
flag: String,
|
||||
label: &'a str,
|
||||
takes_values: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Subcommand<'a> {
|
||||
arguments: Vec<Argument<'a>>,
|
||||
name: &'a str,
|
||||
command: &'a str,
|
||||
label: &'a str,
|
||||
}
|
||||
|
||||
impl Argument<'_> {
|
||||
fn new(clap_argument: &clap::Arg) -> Argument<'_> {
|
||||
Argument {
|
||||
name: clap_argument.get_id().as_str(),
|
||||
takes_values: clap_argument.get_action().takes_values(),
|
||||
}
|
||||
fn new<'a>(
|
||||
argument: &'a clap::Arg,
|
||||
modifier: &modifiers::ArgumentModifier<'static>,
|
||||
) -> Option<Argument<'a>> {
|
||||
// if an argument doesn't have the data we need, it's silently dropped from the GUI, however,
|
||||
// tests should prevent this from happening and we could add logging messages about this in
|
||||
// the future if desired
|
||||
Some(Argument {
|
||||
advanced: modifier.advanced,
|
||||
flag: format!("--{}", argument.get_long()?),
|
||||
label: modifier.gui_label,
|
||||
takes_values: argument.get_action().takes_values(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Subcommand<'_> {
|
||||
fn new(clap_command: &clap::Command) -> Subcommand<'_> {
|
||||
fn new<'a>(
|
||||
command: &'a clap::Command,
|
||||
modifier: &modifiers::SubcommandModifier<'static>,
|
||||
) -> Subcommand<'a> {
|
||||
let argument_map: HashMap<&str, &clap::Arg> = command
|
||||
.get_arguments()
|
||||
.map(|a| (a.get_id().as_str(), a))
|
||||
.collect();
|
||||
|
||||
Subcommand {
|
||||
arguments: clap_command
|
||||
.get_arguments()
|
||||
.map(|a| Argument::new(a))
|
||||
// this resulting vector contains the arguments that are found in both
|
||||
// command.get_arguments() and modifier.arg_modifiers in the order defined by by
|
||||
// arg_modifiers
|
||||
arguments: modifier
|
||||
.arg_modifiers
|
||||
.iter()
|
||||
.filter_map(|arg_modifier| {
|
||||
argument_map
|
||||
.get(arg_modifier.clap_id)
|
||||
.and_then(|arg| Argument::new(arg, arg_modifier))
|
||||
})
|
||||
.collect(),
|
||||
name: clap_command.get_name(),
|
||||
command: modifier.command,
|
||||
label: modifier.gui_label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use clap::CommandFactory;
|
||||
use tauri::Emitter;
|
||||
|
||||
mod introspect;
|
||||
mod modifiers;
|
||||
|
||||
static INSTALLER_COMMAND: LazyLock<clap::Command> = LazyLock::new(installer::Args::command);
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
//! Adds or "modifies" installer CLI attributes for use in the GUI.
|
||||
//!
|
||||
//! This module contains little logic (outside of tests) and instead just provides additional
|
||||
//! metadata about CLI commands and options for the GUI installer.
|
||||
//!
|
||||
//! If we like this approach, I think we should consider renaming this file something like
|
||||
//! gui_modifiers.rs and moving it into the crate for the CLI installer. I think this would simplify
|
||||
//! development as any breaking changes to the CLI installer interface would cause tests to fail in
|
||||
//! its own crate instead of installer-gui and it'd help to keep the two interfaces to the installer
|
||||
//! in sync.
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ArgumentModifier<'a> {
|
||||
/// The name or "ID" of the argument as defined in clap. This will usually be the name of the
|
||||
/// field in the struct the argument is derived from.
|
||||
pub clap_id: &'a str,
|
||||
/// The text for displaying this argument in the GUI.
|
||||
pub gui_label: &'a str,
|
||||
/// Whether this argument should be hidden behind a menu for "advanced" options.
|
||||
pub advanced: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SubcommandModifier<'a> {
|
||||
/// The name of the subcommand on the CLI.
|
||||
pub command: &'a str,
|
||||
/// The text for displaying this subcommand in the GUI.
|
||||
pub gui_label: &'a str,
|
||||
/// Modifications to the arguments of this subcommand. The order arguments are defined in this
|
||||
/// vector will match the order the arguments are displayed in the GUI.
|
||||
pub arg_modifiers: Vec<ArgumentModifier<'a>>,
|
||||
}
|
||||
|
||||
/// Provides "modifiers" or additional metadata about each subcommand.
|
||||
///
|
||||
/// The order of the subcommands in the returned vector is the same order that subcommands will be
|
||||
/// shown in the GUI.
|
||||
pub fn subcommand_modifiers() -> Vec<SubcommandModifier<'static>> {
|
||||
let admin_ip = ArgumentModifier {
|
||||
clap_id: "admin_ip",
|
||||
gui_label: "Admin IP",
|
||||
advanced: true,
|
||||
};
|
||||
let admin_username = ArgumentModifier {
|
||||
clap_id: "admin_username",
|
||||
gui_label: "Admin Username",
|
||||
advanced: true,
|
||||
};
|
||||
let admin_password = ArgumentModifier {
|
||||
clap_id: "admin_password",
|
||||
gui_label: "Admin Password",
|
||||
advanced: false,
|
||||
};
|
||||
let data_dir = ArgumentModifier {
|
||||
clap_id: "data_dir",
|
||||
gui_label: "Data Directory",
|
||||
advanced: true,
|
||||
};
|
||||
let reset_config = ArgumentModifier {
|
||||
clap_id: "reset_config",
|
||||
gui_label: "Reset config.toml",
|
||||
advanced: true,
|
||||
};
|
||||
let orbic_and_moxee_args = vec![
|
||||
admin_password,
|
||||
admin_ip,
|
||||
admin_username,
|
||||
reset_config,
|
||||
data_dir,
|
||||
];
|
||||
|
||||
vec![
|
||||
SubcommandModifier {
|
||||
command: "orbic",
|
||||
gui_label: "Orbic/Kajeet (via network)",
|
||||
arg_modifiers: orbic_and_moxee_args.clone(),
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "orbic-usb",
|
||||
gui_label: "Orbic/Kajeet (via legacy USB+ADB installer)",
|
||||
arg_modifiers: vec![reset_config],
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "tplink",
|
||||
gui_label: "TP-Link",
|
||||
arg_modifiers: vec![
|
||||
admin_ip,
|
||||
reset_config,
|
||||
data_dir,
|
||||
ArgumentModifier {
|
||||
clap_id: "skip_sdcard",
|
||||
gui_label: "Skip SD Card",
|
||||
advanced: true,
|
||||
},
|
||||
ArgumentModifier {
|
||||
clap_id: "sdcard_path",
|
||||
gui_label: "SD Card Path",
|
||||
advanced: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "moxee",
|
||||
gui_label: "Moxee",
|
||||
arg_modifiers: orbic_and_moxee_args,
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "pinephone",
|
||||
gui_label: "PinePhone",
|
||||
arg_modifiers: vec![],
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "tmobile",
|
||||
gui_label: "TMobile",
|
||||
arg_modifiers: vec![admin_password, admin_ip],
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "uz801",
|
||||
gui_label: "UZ801",
|
||||
arg_modifiers: vec![admin_ip],
|
||||
},
|
||||
SubcommandModifier {
|
||||
command: "wingtech",
|
||||
gui_label: "Wingtech",
|
||||
arg_modifiers: vec![admin_password, admin_ip],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//! Subcommands and arguments not returned from subcommand_modifiers() will be excluded from the
|
||||
//! GUI. This is by design as it allows us to exclude things like some or all of the installer
|
||||
//! utils from the GUI. The tests below help ensure that exclusions were done deliberately
|
||||
//! rather than on accident.
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Lists the subcommands that are purposefully excluded from subcommand_modifiers().
|
||||
fn excluded_subcommands() -> Vec<&'static str> {
|
||||
vec!["util"]
|
||||
}
|
||||
|
||||
/// Lists the arguments that are purposefully excluded from subcommand_modifiers(). Items in the
|
||||
/// list take the form of (subcommand, argument_id) tuples.
|
||||
fn excluded_arguments() -> Vec<(&'static str, &'static str)> {
|
||||
// if for example we wanted to exclude the "--admin-password" argument for "orbic", we'd
|
||||
// return vec![("orbic", "admin_password")] here
|
||||
vec![]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subcommands_excluded_or_modified() {
|
||||
let mut all_subcommands: Vec<&str> = crate::INSTALLER_COMMAND
|
||||
.get_subcommands()
|
||||
.map(|c| c.get_name())
|
||||
.collect();
|
||||
let mut excluded_or_modified_subcommands: Vec<&str> = subcommand_modifiers()
|
||||
.into_iter()
|
||||
.map(|m| m.command)
|
||||
.chain(excluded_subcommands())
|
||||
.collect();
|
||||
|
||||
all_subcommands.sort_unstable();
|
||||
excluded_or_modified_subcommands.sort_unstable();
|
||||
|
||||
assert_eq!(
|
||||
all_subcommands, excluded_or_modified_subcommands,
|
||||
"Every subcommand must be included exactly once in subcommand_modifiers() or excluded_subcommands()."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arguments_excluded_or_modified() {
|
||||
// create maps of subcommand name to lists of argument names
|
||||
let all_args_for_nonexcluded_subcommands: HashMap<&str, Vec<&str>> =
|
||||
nonexcluded_subcommand_objects()
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
(
|
||||
c.get_name(),
|
||||
c.get_arguments().map(|a| a.get_id().as_str()).collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let modified_args: HashMap<&str, Vec<&str>> = subcommand_modifiers()
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
(
|
||||
m.command,
|
||||
m.arg_modifiers
|
||||
.into_iter()
|
||||
.map(|arg_m| arg_m.clap_id)
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// add excluded_arguments to modified_args
|
||||
let mut excluded_or_modified_args = modified_args;
|
||||
for (subcommand_name, arg_name) in excluded_arguments() {
|
||||
excluded_or_modified_args
|
||||
.entry(subcommand_name)
|
||||
.or_default()
|
||||
.push(arg_name);
|
||||
}
|
||||
|
||||
// assert that all arguments are excluded or modified
|
||||
for (subcommand_name, mut expected_args) in all_args_for_nonexcluded_subcommands {
|
||||
let mut found_args = excluded_or_modified_args
|
||||
.remove(subcommand_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
expected_args.sort_unstable();
|
||||
found_args.sort_unstable();
|
||||
|
||||
assert_eq!(
|
||||
expected_args, found_args,
|
||||
"Excluded and modified arguments differ from expected arguments for {subcommand_name}."
|
||||
)
|
||||
}
|
||||
assert!(
|
||||
excluded_or_modified_args.is_empty(),
|
||||
"Excluded or modified arguments found for unexpected subcommands. Map of unexpected arguments is {:?}",
|
||||
excluded_or_modified_args
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arguments_have_long_flag() {
|
||||
// any arguments without a long form command line flag will be excluded from the GUI so
|
||||
// let's test for it here to avoid surprises
|
||||
|
||||
let excluded_args = excluded_arguments();
|
||||
let nonexcluded_args: Vec<(&str, &clap::Arg)> = nonexcluded_subcommand_objects()
|
||||
.into_iter()
|
||||
.flat_map(|c| {
|
||||
c.get_arguments().filter_map(|a| {
|
||||
let subcommand_name = c.get_name();
|
||||
|
||||
if excluded_args.contains(&(subcommand_name, a.get_id().as_str())) {
|
||||
None
|
||||
} else {
|
||||
Some((subcommand_name, a))
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (subcommand_name, arg) in nonexcluded_args {
|
||||
assert!(
|
||||
arg.get_long().is_some(),
|
||||
"The {} argument for {subcommand_name} is missing a long form command line flag.",
|
||||
arg.get_id().as_str()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn nonexcluded_subcommand_objects() -> Vec<&'static clap::Command> {
|
||||
let excluded_subcommands = excluded_subcommands();
|
||||
|
||||
crate::INSTALLER_COMMAND
|
||||
.get_subcommands()
|
||||
.filter(|s| !excluded_subcommands.contains(&s.get_name()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user