From 6c4323b50b53d74411c0fd852b0d6225399023c9 Mon Sep 17 00:00:00 2001 From: Dan Schafer Date: Tue, 28 Jul 2026 19:13:39 -0700 Subject: [PATCH] Harden CLI input and terminal handling Use image::ImageFormat::from_path for automatic input dispatch and restore the terminal cursor if animated output fails. --- CHANGELOG.md | 5 +++++ src/main.rs | 15 ++++++++++++--- tests/command_line_runs.rs | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e634ff..4158332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#15](https://github.com/dschafer/qsolve/issues/15) to be solved without forcing chains. +### Changed + +- Make `--file-type=auto` use recognized image extensions to select image parsing. + ### Fixed - Give out-of-range values passed to bitset-backed sets consistent, descriptive panics. - Return an error instead of panicking when an image contains no detectable grid. - Calculate queen and X pixel ratios against the inspected image area. - Preserve spaces used as blank markers at the edges of partial-state grids. +- Restore the terminal cursor when animation exits with an error. ## [1.0.0] - 2025-04-23 diff --git a/src/main.rs b/src/main.rs index 89647fa..46ae672 100644 --- a/src/main.rs +++ b/src/main.rs @@ -157,9 +157,9 @@ fn queens_file_from_path(path_args: &PathCli) -> Result { #[cfg(feature = "image")] FileType::Image => QueensFile::try_from_image_file(&path_args.path), #[cfg(feature = "image")] - FileType::Auto => QueensFile::try_from_text_file(&path_args.path) - .or_else(|_| QueensFile::try_from_image_file(&path_args.path)), - #[cfg(not(feature = "image"))] + FileType::Auto if image::ImageFormat::from_path(&path_args.path).is_ok() => { + QueensFile::try_from_image_file(&path_args.path) + } FileType::Auto => QueensFile::try_from_text_file(&path_args.path), }?; if path_args.clear { @@ -245,6 +245,14 @@ fn print_animated_iter_item( Ok(()) } +struct CursorGuard; + +impl Drop for CursorGuard { + fn drop(&mut self) { + let _ = execute!(std::io::stdout(), Show); + } +} + /// Top-level entry point for the animate subcommand. fn animate( path_args: &PathCli, @@ -262,6 +270,7 @@ fn animate( let _ = execute!(std::io::stdout(), Show); std::process::exit(130); })?; + let _cursor_guard = CursorGuard; execute!(stdout, Hide)?; for solve_iter_item in solve_iter(solve_state, solve_args.strategy, &heuristics) { diff --git a/tests/command_line_runs.rs b/tests/command_line_runs.rs index bb8899d..da221ad 100644 --- a/tests/command_line_runs.rs +++ b/tests/command_line_runs.rs @@ -1,4 +1,6 @@ use assert_cmd::prelude::*; +#[cfg(feature = "image")] +use std::fs; use std::process::Command; #[test] @@ -44,6 +46,23 @@ fn print_fails_on_bad_file() -> Result<(), Box> { Ok(()) } +#[cfg(feature = "image")] +#[test] +fn auto_uses_extension_to_select_parser() -> Result<(), Box> { + let path = std::env::temp_dir().join(format!( + "qsolve-text-with-image-extension-{}.png", + std::process::id() + )); + fs::write(&path, "wwww\nwkkk\nrrrr\nbbbb")?; + + let mut cmd = Command::cargo_bin("qsolve")?; + cmd.arg("print").arg(&path); + cmd.assert().failure(); + + fs::remove_file(path)?; + Ok(()) +} + #[cfg(feature = "image")] #[test] fn print_accepts_image_file_type() -> Result<(), Box> {