mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
The desktop app's argument loop took `--help` as a view setting named `help` (consuming the next argument as its value) and `-h` as a file to open, so both started the window and a scripted call never returned. `-h` and `--help` now print a short usage, which points scripts to pdfcraft-cli, and exit 0 before any window or log file is created. `--version` and `--help` write through a plain `writeln!` instead of `println!`, which panicked (exit 101) when standard output was full or closed, e.g. `pdfcraft --version > /dev/full`. Release builds on Windows are GUI-subsystem programs, so, as with `--version` already, the text shows when the output is redirected or piped but not in a plain console window. Fixes #4076
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
//! PdfCraft desktop app.
|
//! PdfCraft desktop app.
|
||||||
//!
|
//!
|
||||||
//! Usage: `pdfcraft [options] [files…]`
|
//! Usage: `pdfcraft [options] [files…]` (`-h` / `--help` prints a short version of this, [`USAGE`]).
|
||||||
//! `--create-images [images…]` stages the images in one PDF and asks for the page DPI.
|
//! `--create-images [images…]` stages the images in one PDF and asks for the page DPI.
|
||||||
//!
|
//!
|
||||||
//! View options (applied after the files open; also the seed of the UI control channel):
|
//! View options (applied after the files open; also the seed of the UI control channel):
|
||||||
@@ -56,6 +56,32 @@ const APP_ICON_PNG: &[u8] = include_bytes!("../../../assets/app-icon/pdfcraft-10
|
|||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
const APP_ICON_PNG: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png");
|
const APP_ICON_PNG: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.pdfcraft.png");
|
||||||
|
|
||||||
|
/// What `-h` / `--help` prints (#4076): a hint, not the full option list (that is this file's
|
||||||
|
/// doc comment and the README). Scripts want `pdfcraft-cli`, which never opens a window.
|
||||||
|
const USAGE: &str = "\
|
||||||
|
Usage: pdfcraft [options] [files…]
|
||||||
|
|
||||||
|
Opens the files in the PdfCraft desktop app.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--new-window open a window of its own instead of passing the files to a running PdfCraft
|
||||||
|
--create-images [images…] put the images in one PDF and ask for the page DPI
|
||||||
|
--control <file> enable the UI control channel (pdfcraft-cli ui --control <file> …)
|
||||||
|
--page N, --zoom 150, --layout continuous, --theme dark, …
|
||||||
|
view settings applied once the files are open
|
||||||
|
-h, --help print this and exit
|
||||||
|
--version print the version and exit
|
||||||
|
|
||||||
|
To inspect, convert or edit PDFs from a script, use pdfcraft-cli (pdfcraft-cli --help).";
|
||||||
|
|
||||||
|
/// Print `text` for `--help` and `--version`. `println!` would panic on a full or closed
|
||||||
|
/// standard output; this exits normally instead.
|
||||||
|
fn print_and_exit(text: &str) -> eframe::Result {
|
||||||
|
use std::io::Write;
|
||||||
|
let _ = writeln!(std::io::stdout().lock(), "{text}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// The app was called PrintCraft before; settings saved then are under this key.
|
/// The app was called PrintCraft before; settings saved then are under this key.
|
||||||
const LEGACY_STORAGE_KEY: &str = "printcraft";
|
const LEGACY_STORAGE_KEY: &str = "printcraft";
|
||||||
|
|
||||||
@@ -196,10 +222,9 @@ fn main() -> eframe::Result {
|
|||||||
let mut args = std::env::args().skip(1);
|
let mut args = std::env::args().skip(1);
|
||||||
while let Some(a) = args.next() {
|
while let Some(a) = args.next() {
|
||||||
match a.as_str() {
|
match a.as_str() {
|
||||||
"--version" => {
|
"--version" => return print_and_exit(&format!("pdfcraft {}", pdfcraft_engine::build_info::version())),
|
||||||
println!("pdfcraft {}", pdfcraft_engine::build_info::version());
|
// Before any window or log file, so a scripted call returns (#4076).
|
||||||
return Ok(());
|
"-h" | "--help" => return print_and_exit(USAGE),
|
||||||
}
|
|
||||||
"--control" => control_file = args.next(),
|
"--control" => control_file = args.next(),
|
||||||
"--create-images" => create_images = true,
|
"--create-images" => create_images = true,
|
||||||
"--new-window" => new_window = true,
|
"--new-window" => new_window = true,
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! `--help`, `-h` and `--version` print and exit without opening a window (#4076), so a script that
|
||||||
|
//! calls them returns.
|
||||||
|
|
||||||
|
use std::process::{Command, Output, Stdio};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
const BIN: &str = env!("CARGO_BIN_EXE_pdfcraft");
|
||||||
|
|
||||||
|
/// Run the app with `args`, failing if it is still running after 60 s (it opened its window).
|
||||||
|
fn run(args: &[&str], stdout: Stdio) -> Output {
|
||||||
|
let mut child = Command::new(BIN).args(args).stdin(Stdio::null()).stdout(stdout).stderr(Stdio::piped()).spawn().unwrap();
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(60);
|
||||||
|
while child.try_wait().unwrap().is_none() {
|
||||||
|
if Instant::now() > deadline {
|
||||||
|
let _ = child.kill();
|
||||||
|
panic!("pdfcraft {args:?} did not exit: it started the app instead");
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(50));
|
||||||
|
}
|
||||||
|
child.wait_with_output().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_prints_usage_and_exits() {
|
||||||
|
for flag in ["--help", "-h"] {
|
||||||
|
let out = run(&[flag], Stdio::piped());
|
||||||
|
assert!(out.status.success(), "{flag}: {:?} {}", out.status, String::from_utf8_lossy(&out.stderr));
|
||||||
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
|
assert!(stdout.starts_with("Usage: pdfcraft [options] [files…]"), "{flag}: {stdout}");
|
||||||
|
assert!(stdout.contains("pdfcraft-cli"), "{flag}: points scripts to the CLI: {stdout}");
|
||||||
|
}
|
||||||
|
// Wherever it is on the command line, and with files named.
|
||||||
|
let out = run(&["missing.pdf", "--help"], Stdio::piped());
|
||||||
|
assert!(out.status.success());
|
||||||
|
assert!(String::from_utf8_lossy(&out.stdout).starts_with("Usage:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_prints_the_version_and_exits() {
|
||||||
|
let out = run(&["--version"], Stdio::piped());
|
||||||
|
assert!(out.status.success(), "{:?} {}", out.status, String::from_utf8_lossy(&out.stderr));
|
||||||
|
assert!(String::from_utf8_lossy(&out.stdout).starts_with("pdfcraft "), "{}", String::from_utf8_lossy(&out.stdout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A standard output that can't be written (here a full device) ends the run normally: `println!`
|
||||||
|
/// would panic and exit 101.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn help_and_version_survive_an_unwritable_stdout() {
|
||||||
|
for flag in ["--help", "--version"] {
|
||||||
|
let full = std::fs::OpenOptions::new().write(true).open("/dev/full").unwrap();
|
||||||
|
let out = run(&[flag], Stdio::from(full));
|
||||||
|
assert!(out.status.success(), "{flag}: {:?} {}", out.status, String::from_utf8_lossy(&out.stderr));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user