mirror of
https://github.com/outbackdingo/hyprcosmic.git
synced 2026-08-25 14:53:21 +00:00
Refuse arguments apply does not understand
`cosmic-conf apply --diff-only ~/.config/hyprcosmic/cosmic.conf` did exactly the wrong thing twice over: the misspelt flag was ignored, so it wrote instead of diffing, and the path was ignored too, so it wrote to whatever the *default* config compiles to. It reported success either way. Both arguments are now errors with exit 2. An argument parser that silently skips the unknown is a bad fit for a command whose job is to overwrite settings; the surface here is six flags, so the check is fifteen lines rather than a dependency.
This commit is contained in:
@@ -42,6 +42,36 @@ fn find_source_dir(theme_dir: &std::path::Path) -> Option<PathBuf> {
|
|||||||
.find(|c| c.is_dir())
|
.find(|c| c.is_dir())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Refuse arguments the caller does not understand.
|
||||||
|
///
|
||||||
|
/// `apply` writes to the config tree, so an argument it does not recognise has
|
||||||
|
/// to stop it rather than be skipped: `--diff-only` instead of `--diff`, or a
|
||||||
|
/// config path given positionally, would otherwise apply to the *default*
|
||||||
|
/// config while looking like it had done what was asked. This is a
|
||||||
|
/// hand-rolled check rather than a dependency because the whole surface is six
|
||||||
|
/// flags, and an argument parser that silently ignores the unknown is exactly
|
||||||
|
/// the behaviour being removed.
|
||||||
|
///
|
||||||
|
/// `flags` take no value; `valued` consume the argument after them.
|
||||||
|
fn reject_unknown(args: &[String], flags: &[&str], valued: &[&str]) -> Result<(), String> {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < args.len() {
|
||||||
|
let a = &args[i];
|
||||||
|
if valued.contains(&a.as_str()) {
|
||||||
|
i += 2;
|
||||||
|
} else if flags.contains(&a.as_str()) {
|
||||||
|
i += 1;
|
||||||
|
} else if a == "-h" || a == "--help" {
|
||||||
|
i += 1;
|
||||||
|
} else if let Some(name) = a.strip_prefix("--") {
|
||||||
|
return Err(format!("error: unknown option `--{name}`"));
|
||||||
|
} else {
|
||||||
|
return Err(format!("error: unexpected argument `{a}`"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn default_config_path() -> Option<PathBuf> {
|
fn default_config_path() -> Option<PathBuf> {
|
||||||
let base = match std::env::var_os("XDG_CONFIG_HOME") {
|
let base = match std::env::var_os("XDG_CONFIG_HOME") {
|
||||||
Some(x) if !x.is_empty() => PathBuf::from(x),
|
Some(x) if !x.is_empty() => PathBuf::from(x),
|
||||||
@@ -74,6 +104,11 @@ fn main() -> ExitCode {
|
|||||||
return ExitCode::from(2);
|
return ExitCode::from(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(msg) = reject_unknown(&args[1..], &["--diff"], &["--config"]) {
|
||||||
|
eprintln!("{msg}\n\n{USAGE}");
|
||||||
|
return ExitCode::from(2);
|
||||||
|
}
|
||||||
|
|
||||||
let diff_only = args.iter().any(|a| a == "--diff");
|
let diff_only = args.iter().any(|a| a == "--diff");
|
||||||
let config_path = match args.iter().position(|a| a == "--config") {
|
let config_path = match args.iter().position(|a| a == "--config") {
|
||||||
Some(i) => match args.get(i + 1) {
|
Some(i) => match args.get(i + 1) {
|
||||||
@@ -151,6 +186,12 @@ fn run_import(args: &[String]) -> Result<String, String> {
|
|||||||
let Some(src_path) = args.first().filter(|a| !a.starts_with("--")) else {
|
let Some(src_path) = args.first().filter(|a| !a.starts_with("--")) else {
|
||||||
return Err(format!("error: import-theme needs a path\n\n{USAGE}"));
|
return Err(format!("error: import-theme needs a path\n\n{USAGE}"));
|
||||||
};
|
};
|
||||||
|
reject_unknown(
|
||||||
|
&args[1..],
|
||||||
|
&["--report", "--assets", "--overwrite", "--dry-run"],
|
||||||
|
&["--out", "--source"],
|
||||||
|
)
|
||||||
|
.map_err(|msg| format!("{msg}\n\n{USAGE}"))?;
|
||||||
let out_path = args
|
let out_path = args
|
||||||
.iter()
|
.iter()
|
||||||
.position(|a| a == "--out")
|
.position(|a| a == "--out")
|
||||||
@@ -288,3 +329,49 @@ fn install_assets(src_path: &str, name: &str, args: &[String]) -> Result<String,
|
|||||||
.map_err(|e| format!("error: {e}\n"))?;
|
.map_err(|e| format!("error: {e}\n"))?;
|
||||||
Ok(assets::render_report(&plan, &report))
|
Ok(assets::render_report(&plan, &report))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn args(s: &[&str]) -> Vec<String> {
|
||||||
|
s.iter().map(|a| a.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_flags_and_their_values_are_accepted() {
|
||||||
|
let a = args(&["--diff", "--config", "/etc/cosmic.conf"]);
|
||||||
|
assert!(reject_unknown(&a, &["--diff"], &["--config"]).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_misspelt_flag_is_refused_rather_than_skipped() {
|
||||||
|
// The bug this exists to prevent: `--diff-only` used to be ignored, so
|
||||||
|
// `apply` wrote for real while the caller believed it was a dry run.
|
||||||
|
let a = args(&["--diff-only"]);
|
||||||
|
let err = reject_unknown(&a, &["--diff"], &["--config"]).unwrap_err();
|
||||||
|
assert!(err.contains("--diff-only"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_positional_path_is_refused_because_it_would_be_ignored() {
|
||||||
|
let a = args(&["/home/me/.config/hyprcosmic/cosmic.conf"]);
|
||||||
|
let err = reject_unknown(&a, &["--diff"], &["--config"]).unwrap_err();
|
||||||
|
assert!(err.contains("unexpected argument"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_value_that_looks_like_a_flag_is_still_a_value() {
|
||||||
|
// `--config --diff` is a user error, but it is the *next* argument's
|
||||||
|
// job to be a path; consuming it here keeps the rule simple and
|
||||||
|
// matches what the position-based lookup below actually does.
|
||||||
|
let a = args(&["--config", "--diff"]);
|
||||||
|
assert!(reject_unknown(&a, &["--diff"], &["--config"]).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_trailing_valued_flag_with_no_value_is_not_a_panic() {
|
||||||
|
let a = args(&["--config"]);
|
||||||
|
assert!(reject_unknown(&a, &["--diff"], &["--config"]).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user