From 4b5f44689d2cdfb8c493ffc56348e29331653141 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 29 Oct 2026 15:11:35 -0600 Subject: [PATCH] feat(cli): a launch with nothing to say and no terminal is the viewer (P11a step 3) An AppImage's launcher runs the desktop file's Exec line and appends the arguments it was given, so with `Exec=wanderbooks gui` the AppImage's `update` ran `wanderbooks gui update`: the program's own commands were unreachable through it. The desktop files now say plain `Exec=wanderbooks` and a bare launch with stdin not a terminal (a menu entry, a double-click) is the viewer; at a terminal it is the usage, as before. The AppImage is thereby also the command line (`Wanderbooks.AppImage update`). Test: bare_launch_tests::only_nothing_at_all_with_no_terminal_is_the_viewer (the launch itself cannot run under test: the viewer finds the account's real data root from the password database, not from the environment). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp --- crates/ledger-cli/src/main.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/ledger-cli/src/main.rs b/crates/ledger-cli/src/main.rs index e308782..cdb39b6 100644 --- a/crates/ledger-cli/src/main.rs +++ b/crates/ledger-cli/src/main.rs @@ -389,6 +389,15 @@ fn run_server(_args: Vec) -> i32 { 2 } +/// Whether the program was launched with nothing to say and no terminal +/// to say it to: a menu entry, a double-click, an AppImage (whose launcher +/// runs the desktop file's command and appends what it was given, so the +/// desktop file says plain `wanderbooks`). At a terminal it is the usage, +/// as ever. +fn is_bare_launch(words: &[String], stdin_is_terminal: bool) -> bool { + words.is_empty() && !stdin_is_terminal +} + fn main() { // A file's path may be any name the system takes: the file commands // get the words as given. Everything else is text. @@ -405,6 +414,10 @@ fn main() { eprintln!("error: an argument isn't UTF-8 text"); std::process::exit(2); }; + // A bare launch is the viewer. + if is_bare_launch(&words, std::io::IsTerminal::is_terminal(&std::io::stdin())) { + std::process::exit(run_gui(Vec::new())); + } // The modes run in this process and take their own flags. let mut all = words.clone().into_iter(); match all.next().as_deref() { @@ -479,3 +492,18 @@ mod gui_args_tests { } } } + +#[cfg(test)] +mod bare_launch_tests { + use super::is_bare_launch; + + #[test] + fn only_nothing_at_all_with_no_terminal_is_the_viewer() { + assert!(is_bare_launch(&[], false)); + // At a terminal it is the usage, as ever. + assert!(!is_bare_launch(&[], true)); + // Anything said is a mode or a mistake, never the viewer. + assert!(!is_bare_launch(&["--help".to_string()], false)); + assert!(!is_bare_launch(&["update".to_string()], false)); + } +}