-
So I have a very complicated and I guess niche problem... Here are the prerequisites:
The reason:I want to be able to create a config file for the user that contains every single field set to a default value automatically. What I've tried
#[serde(default)]
#[merge(strategy = utils::merge::if_some)]
#[arg(long, action = ArgAction::Set, default_missing_value = "false", required = false, verbatim_doc_comment)]
pub exit_on_close: Option<bool>,
If you have any questions about further solutions (I have tried so many, I won't list them all xd, just ask if I did do it) then ask away.
#[derive(Debug, Clone, Subcommand, Serialize, Deserialize)]
pub enum LaunchdCommands {
Open(OpenCommand),
GenerateConfig(GenerateConfigCommand),
}
impl Default for LaunchdCommands {
fn default() -> Self {
LaunchdCommands::Open(OpenCommand::default())
}
}
/// Opens the launcher
///
/// This command gets run when no other command is used.
/// Also starts the launcher daemon if it hasn't already been started.
#[derive(Debug, Clone, Merge, Args, Serialize, Deserialize)]
#[command(verbatim_doc_comment)]
pub struct OpenCommand {
[...]
/// Terminates Launchd when closed
///
/// Not recommended as this incures a startup cost (load times) every time.
/// Launchd normally keep running even after closing it,
/// so that it can be opened immediately when needed.
#[serde(default)]
#[merge(strategy = utils::merge::if_some)]
#[arg(long, action = ArgAction::Set, default_missing_value = "false", required = false, verbatim_doc_comment)]
pub exit_on_close: Option<bool>,
}
impl Default for OpenCommand {
fn default() -> Self {
OpenCommand {
daemonize: false.into(),
exit_on_close: Some(false),
}
}
}
/// Generates the default config file
///
/// This is stored at "$XDG_CONFIG_HOME/Launchd".
/// Also requires the --force flag whenever a config file is already present.
///
/// Whenever combined with --config, creates a new default config
/// at the specified location.
#[derive(Debug, Clone, Merge, Args, Serialize, Deserialize)]
#[command(alias = "gen-def-conf", verbatim_doc_comment)]
pub struct GenerateConfigCommand {
/// Define a custom config creation path
///
/// This can also be used on a directory containing the config file.
#[serde(skip)]
#[merge(strategy = utils::merge::always)]
#[arg(default_value = app_dirs::CONFIG.deref().as_os_str())]
pub path: Option<std::path::PathBuf>,
/// Forces the generation of the config file
///
/// This is required if a config file is already present.
/// Make sure to backup your previous config file in this case!
#[merge(strategy = utils::merge::if_some)]
#[arg(long, verbatim_doc_comment)]
pub force: Option<bool>,
}
impl Default for GenerateConfigCommand {
fn default() -> Self {
GenerateConfigCommand {
path: Some(app_dirs::CONFIG.deref().to_path_buf()),
force: Some(false),
}
}
} If there is a solution where I get to keep a single subcommand enum, instead of splitting it up into multiple structs, that would be incredible! Desired OutputConfig: [open]
daemonize = true
exit_on_close = false
[config_generation]
force = false CLI (I don't want this. I want this to NOT happen):
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
Oh and here is the parsed Argument for clap: #[derive(Debug, Default, Args)]
pub struct LaunchdArgs {
#[command(subcommand)]
subcommands: Option<LaunchdCommands>,
/// Define a custom config file path
///
/// This can also be used on a directory containing the config file.
#[arg(short, long = "config", global = true, default_value = app_dirs::CONFIG.deref().as_os_str())]
config_path: std::path::PathBuf,
/// Print more detailed debug messages
///
/// Using this flag multiple times, results in even more verbose logs.
/// Here are the different stages (-v, -vv, -vvv):
/// None : Only show errors
/// -v : Also show warnings
/// -vv : Also show info
/// -vvv : Also show debug info
/// -vvvv: Also show program trace
#[arg(short, long, global = true, action = clap::ArgAction::Count, verbatim_doc_comment)]
verbose: u8,
/// Silences any stdout output
///
/// The logs are still stored at "$XDG_LOCAL_STATE/Launchd" for convenience.
/// If you want to also disable the log files, use --disable-logs.
#[arg(short, long, global = true, conflicts_with("verbose"), verbatim_doc_comment)]
quiet: bool,
/// Disables writing log files
///
/// These files are normally located at "$XDG_LOCAL_STATE/Launchd".
/// Disabling log files is HEAVILY discouraged as this helps immensily with crash data
/// whenever opening an issue on the Github.
#[arg(long, global = true, verbatim_doc_comment)]
disable_logs: bool,
}
impl LaunchdArgs {
pub fn get_verbosity_level(&self) -> log::LevelFilter {
use log::LevelFilter;
if self.quiet {
return LevelFilter::Off
}
match self.verbose {
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
}
pub fn get_disable_logs(&self) -> bool {
self.disable_logs
}
pub fn get_subcommand(&self) -> LaunchdCommands {
self.subcommands.clone().unwrap_or_default()
}
pub fn get_config_path(&self) -> &PathBuf {
&self.config_path
}
}
pub fn parse() -> LaunchdArgs {
let cli = Command::new("Launchd")
.name("Launchd")
.about("")
.long_about("")
.disable_help_flag(true)
.disable_help_subcommand(true);
let cli = LaunchdArgs::augment_args(cli);
let cli = cli.arg(
Arg::new("Help")
.action(ArgAction::Help)
.global(true)
.short('h')
.short_alias('?')
.long("help")
.help("Try --help for more details")
.long_help(
"You used the longer help :D\n\nYou can use this --help anywhere to see more info!\nIf you feel like something \
is unclear, open an issue at our Github!",
),
);
let matches = cli.get_matches();
let args = LaunchdArgs::from_arg_matches(&matches);
let args = match args {
Ok(v) => v,
Err(e) => {
println!("Arguments couldn't be parsed, using the default arguments instead: {}", e);
LaunchdArgs::default()
},
};
args
}
Here is the config itself:
```rust
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub struct LaunchdConfig {
pub open: OpenCommand,
pub config_generation: GenerateConfigCommand,
} And here is the code that merges/generates the config: pub fn merge_config_with_cli_args(mut config: LaunchdConfig, commands: &LaunchdCommands) -> LaunchdConfig {
dbg!(&config, &commands);
match commands.clone() {
LaunchdCommands::Open(command) => config.open.merge(command),
LaunchdCommands::GenerateConfig(command) => config.config_generation.merge(command),
}
config
}
pub fn generate_default_config(path: PathBuf, force: bool) -> io::Result<()> {
let default = LaunchdConfig::default();
let toml = toml::to_string(&default).expect("TOML serialization failed!");
println!("{}", &toml);
fs::create_dir_all(path.parent().unwrap())
.inspect_err(|e| error!("Couldn't create the necessary directories for the supplied path: {}", e))?;
let create_fn = if !force { File::create_new } else { File::create };
if let Ok(mut f) = create_fn(path) {
f.write_all(toml.as_bytes())?;
} else {
// File::create only errors for not created directories, yet we created them recursively previously.
error!("The supplied path contains a config file! Use --force to overwrite.")
}
Ok(())
} |
Beta Was this translation helpful? Give feedback.
-
You had #[arg(long, action = ArgAction::Set, default_missing_value = "false", required = false, verbatim_doc_comment)]
pub exit_on_close: Option<bool>,
So what you probably want is #[arg(long, num_args=0, default_missing_value = "true", verbatim_doc_comment)]
pub exit_on_close: Option<bool>, |
Beta Was this translation helpful? Give feedback.
You had
default_missing_value
says what to fill in if the flag is seen but no value followsnum_args
says how many values can follow itSo what you probably want is