mirror of
https://github.com/outbackdingo/hyprcosmic.git
synced 2026-08-25 07:10:09 +00:00
Expose the watch subcommand
`watch::watch` has been written, tested and unreachable from the CLI since it landed. It now has a command: `cosmic-conf watch [--config <path>]`, sharing `--config` with `apply` and refusing `--diff`, which means nothing for a daemon whose whole job is to notice a change and write it. Exposing it made an existing wart user-visible: a single bad save reported itself three or four times. One write arrives as several inotify events -- modify, close_write, and a rename when the editor writes atomically -- and they do not all land inside one 250ms debounce window, so each produced its own compile and its own copy of the same diagnostic. Consecutive identical errors are now printed once, reset on any successful compile so the same error after a good one is still news. Verified against an isolated XDG_CONFIG_HOME, driving a real daemon rather than calling `compile` directly, since none of this is reachable from the unit tests: applies at startup, recompiles on edit, notices edits to sourced files, picks up a `source` line added at runtime, survives a malformed edit with the last good value intact, reports it exactly once, does not suppress a *different* error, and resumes after a fix. Ten checks, all passing.
This commit is contained in:
+40
-3
@@ -12,9 +12,17 @@ cosmic-conf — compile cosmic.conf into the cosmic-config tree
|
||||
|
||||
USAGE:
|
||||
cosmic-conf apply [--diff] [--config <path>]
|
||||
cosmic-conf watch [--config <path>]
|
||||
cosmic-conf import-theme <hypr.theme> [--out <path>] [--report]
|
||||
[--assets [--source <dir>] [--overwrite] [--dry-run]]
|
||||
|
||||
COMMANDS:
|
||||
apply Compile the config once and exit
|
||||
watch Stay running and recompile on every edit, to the config
|
||||
and to anything it sources. A malformed edit is reported
|
||||
and waited past, not fatal.
|
||||
import-theme Translate a HyDE theme into config keys
|
||||
|
||||
OPTIONS:
|
||||
--diff Show what would change without writing anything
|
||||
--config <path> Config file (default: $XDG_CONFIG_HOME/hyprcosmic/cosmic.conf)
|
||||
@@ -99,12 +107,18 @@ fn main() -> ExitCode {
|
||||
}
|
||||
};
|
||||
}
|
||||
if args[0] != "apply" {
|
||||
eprintln!("error: unknown command `{}`\n\n{USAGE}", args[0]);
|
||||
let command = args[0].as_str();
|
||||
if !matches!(command, "apply" | "watch") {
|
||||
eprintln!("error: unknown command `{command}`\n\n{USAGE}");
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
|
||||
if let Err(msg) = reject_unknown(&args[1..], &["--diff"], &["--config"]) {
|
||||
// `--diff` belongs to `apply` alone: a daemon whose whole job is to notice
|
||||
// a change and write it has nothing to do with a mode that declines to
|
||||
// write. Passing it to `watch` is an error rather than a no-op, for the
|
||||
// same reason `--diff-only` is.
|
||||
let flags: &[&str] = if command == "apply" { &["--diff"] } else { &[] };
|
||||
if let Err(msg) = reject_unknown(&args[1..], flags, &["--config"]) {
|
||||
eprintln!("{msg}\n\n{USAGE}");
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
@@ -127,6 +141,16 @@ fn main() -> ExitCode {
|
||||
},
|
||||
};
|
||||
|
||||
if command == "watch" {
|
||||
return match run_watch(&config_path) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(msg) => {
|
||||
eprint!("{msg}");
|
||||
ExitCode::from(1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match run(&config_path, diff_only) {
|
||||
Ok(msg) => {
|
||||
println!("{msg}");
|
||||
@@ -184,6 +208,19 @@ fn run(config_path: &Path, diff_only: bool) -> Result<String, String> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Block, recompiling on every edit, until the watcher itself stops.
|
||||
///
|
||||
/// Returns nothing to print on success because there is no success to report
|
||||
/// until it is over: progress goes to stderr as it happens, from inside the
|
||||
/// loop. A config error is not an error here either -- `watch` reports a
|
||||
/// malformed edit and waits for the next one, which is the whole point of
|
||||
/// leaving it running -- so the only failure that reaches this function is the
|
||||
/// notify machinery failing to start.
|
||||
fn run_watch(config_path: &Path) -> Result<(), String> {
|
||||
let emitter = Emitter::from_env().map_err(|e| format!("error: {e}\n"))?;
|
||||
watch::watch(config_path, &emitter).map_err(|e| format!("{e}\n"))
|
||||
}
|
||||
|
||||
fn run_import(args: &[String]) -> Result<String, String> {
|
||||
let Some(src_path) = args.first().filter(|a| !a.starts_with("--")) else {
|
||||
return Err(format!("error: import-theme needs a path\n\n{USAGE}"));
|
||||
|
||||
@@ -344,6 +344,15 @@ pub fn watch(config: &Path, emitter: &Emitter) -> Result<(), WatchError> {
|
||||
let mut watcher: RecommendedWatcher = notify::recommended_watcher(tx)?;
|
||||
let mut watched: HashSet<PathBuf> = HashSet::new();
|
||||
|
||||
// The last diagnostic printed, so an unchanged one is not printed again.
|
||||
// A single save arrives as several inotify events -- modify, then
|
||||
// close_write, sometimes a rename when the editor writes atomically -- and
|
||||
// they do not all land inside one debounce window, so a broken config
|
||||
// otherwise reports itself three or four times per keystroke-save. Cleared
|
||||
// on every successful compile, so the same error reappearing after a good
|
||||
// one is still news and still printed.
|
||||
let mut last_error: Option<String> = None;
|
||||
|
||||
// Compile once up front: the desktop should reflect the config the
|
||||
// moment the daemon starts, and this also tells us the initial watch
|
||||
// set. If it fails, fall back to watching just `config` — that is the
|
||||
@@ -357,7 +366,9 @@ pub fn watch(config: &Path, emitter: &Emitter) -> Result<(), WatchError> {
|
||||
sync_watches(&mut watcher, &mut watched, &compiled.sources);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
let text = e.to_string();
|
||||
eprintln!("{text}");
|
||||
last_error = Some(text);
|
||||
sync_watches(
|
||||
&mut watcher,
|
||||
&mut watched,
|
||||
@@ -381,6 +392,7 @@ pub fn watch(config: &Path, emitter: &Emitter) -> Result<(), WatchError> {
|
||||
|
||||
match compile(config, emitter) {
|
||||
Ok(compiled) => {
|
||||
last_error = None;
|
||||
if let Err(e) = emitter.apply(&compiled.planned) {
|
||||
eprintln!("{}", CompileError::Emit(vec![e]));
|
||||
}
|
||||
@@ -390,7 +402,11 @@ pub fn watch(config: &Path, emitter: &Emitter) -> Result<(), WatchError> {
|
||||
// Leave `watched` alone: the fix for a bad edit might land in
|
||||
// an already-sourced file, and dropping back to watching
|
||||
// only `config` would miss that.
|
||||
eprintln!("{e}");
|
||||
let text = e.to_string();
|
||||
if last_error.as_deref() != Some(text.as_str()) {
|
||||
eprintln!("{text}");
|
||||
last_error = Some(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user