mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-27 11:57:36 -06:00
* A file name that isn't valid Unicode no longer crashes wordcraft-cli or the app (#1440) Both binaries read std::env::args(), which panics on a non-Unicode argument such as a Latin-1 file name. The CLI now reads args_os(): the file a command opens keeps its real name and opens (convert, info, text, inspect, render, zotero, run --file), and any other such argument is an error naming it, exit 1. The app leaves such a file out with a warning, as it does a file that fails to open, since file.open takes its path as text; a --control port that isn't valid Unicode exits 2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * The app opens a file whose name isn't valid Unicode too (#1440) file.open takes its path as text but also accepts the file's bytes under a name, as dropped files use. The app reads such a file, opens its bytes under the lossy name and keeps the real path on the session, so Save writes back to it. A unit test covers the open and the save. The CLI test now checks that every command in OPENS_FILE reaches its open with the file, and no longer pins `pos` counting a `--page` value. ROADMAP says which saves go back to the file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * #1440: skip non-UTF-8 file name tests on macOS APFS refuses file names that aren't valid UTF-8, so the fixtures fail with "Illegal byte sequence" there, and the app-start test keeps running because macOS opens a window without a display variable. The unit test for the CLI's argument checks and the control-port test still run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
77 lines
3.4 KiB
Rust
77 lines
3.4 KiB
Rust
//! A file name that isn't valid Unicode (`caf\xe9.txt`, Latin-1) never panics the CLI (#1440): the
|
|
//! file a command opens opens, and any other such argument is an error naming it. Not on macOS:
|
|
//! APFS refuses file names that aren't valid UTF-8, so the fixture can't be made there.
|
|
#![cfg(all(unix, not(target_os = "macos")))]
|
|
|
|
use std::ffi::OsString;
|
|
use std::os::unix::ffi::OsStringExt;
|
|
use std::path::PathBuf;
|
|
use std::process::{Command, Output};
|
|
|
|
fn cli(args: &[OsString]) -> Output {
|
|
Command::new(env!("CARGO_BIN_EXE_wordcraft-cli")).args(args).output().unwrap()
|
|
}
|
|
|
|
/// A temp folder holding `caf\xe9.txt`, whose text is "Bonjour".
|
|
fn folder(name: &str) -> (PathBuf, OsString) {
|
|
let dir = std::env::temp_dir().join(format!("wc-cli-non-utf8-{name}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let file = dir.join(OsString::from_vec(b"caf\xe9.txt".to_vec()));
|
|
std::fs::write(&file, "Bonjour\n").unwrap();
|
|
(dir, file.into_os_string())
|
|
}
|
|
|
|
fn ok(out: &Output) -> String {
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(out.status.success(), "status {:?}, stderr: {stderr}", out.status);
|
|
String::from_utf8_lossy(&out.stdout).into_owned()
|
|
}
|
|
|
|
#[test]
|
|
fn the_file_a_command_opens_may_have_any_name() {
|
|
let (dir, file) = folder("open");
|
|
assert_eq!(ok(&cli(&["text".into(), file.clone()])).trim(), "Bonjour");
|
|
assert!(ok(&cli(&["info".into(), file.clone()])).contains("\"words\""));
|
|
assert!(ok(&cli(&["inspect".into(), file.clone()])).contains("Bonjour"));
|
|
|
|
let md = dir.join("out.md");
|
|
ok(&cli(&["convert".into(), file.clone(), md.clone().into()]));
|
|
assert!(std::fs::read_to_string(&md).unwrap().contains("Bonjour"));
|
|
|
|
let saved = dir.join("run.txt");
|
|
ok(&cli(&["run".into(), "--file".into(), file.clone(), "--save".into(), saved.clone().into()]));
|
|
assert!(std::fs::read_to_string(&saved).unwrap().contains("Bonjour"));
|
|
|
|
// Saving without a path writes back to the file under its real name, not a lossy one.
|
|
let mut args: Vec<OsString> = vec!["run".into(), "--file".into(), file.clone()];
|
|
for cmd in ["caret.docEnd", "text.insert={\"text\":\" encore\"}", "file.save"] {
|
|
args.extend(["--cmd".into(), cmd.into()]);
|
|
}
|
|
ok(&cli(&args));
|
|
assert!(std::fs::read_to_string(&file).unwrap().contains("encore"));
|
|
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 3, "a second file was written");
|
|
let _ = std::fs::remove_dir_all(dir);
|
|
}
|
|
|
|
#[test]
|
|
fn any_other_such_argument_is_an_error_naming_it() {
|
|
let (dir, file) = folder("error");
|
|
let out_name = dir.join(OsString::from_vec(b"caf\xe9.md".to_vec())).into_os_string();
|
|
let cmd = OsString::from_vec(b"text.insert={\"text\":\"\xe9\"}".to_vec());
|
|
for (args, n) in [
|
|
(vec!["convert".into(), file.clone(), out_name.clone()], 3),
|
|
(vec!["run".into(), "--file".into(), file.clone(), "--cmd".into(), cmd], 5),
|
|
(vec![OsString::from_vec(b"t\xe9xt".to_vec()), file.clone()], 1),
|
|
] {
|
|
let out = cli(&args);
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
let shown = args[n - 1].to_string_lossy();
|
|
assert!(shown.contains('\u{FFFD}'));
|
|
assert_eq!(out.status.code(), Some(1), "{args:?}: stderr: {stderr}");
|
|
assert_eq!(stderr, format!("wordcraft-cli: argument {n} is not valid Unicode: {shown}\n"), "{args:?}");
|
|
}
|
|
assert!(!std::path::Path::new(&out_name).exists());
|
|
let _ = std::fs::remove_dir_all(dir);
|
|
}
|