diff --git a/crates/engine/src/cmd/swatchlib.rs b/crates/engine/src/cmd/swatchlib.rs index d3652aa4e..50033eff7 100644 --- a/crates/engine/src/cmd/swatchlib.rs +++ b/crates/engine/src/cmd/swatchlib.rs @@ -332,14 +332,17 @@ fn library_tab_name(file: &str, lib_name: &str) -> String { /// The file name of library `name` with extension `ext`: characters file systems reject become /// `-`, and dots and spaces at either end are dropped. A name with nothing left is `Library`, and -/// one that is a Windows device name (`CON`, `NUL`, `COM1`…) gets a `-` after it. +/// one that is a Windows device name (`CON`, `NUL`, `COM1`…, also with an extension) gets a `-` +/// after its first part (`CON-.json`, `CON-.txt.json`). pub fn library_file_name(name: &str, ext: &str) -> String { let base: String = name.chars().map(|c| if "/\\:*?\"<>|".contains(c) || c.is_control() { '-' } else { c }).collect(); let base = base.trim_matches(['.', ' ']); if base.is_empty() { format!("Library.{ext}") } else if crate::file_access::is_device_name(base) { - format!("{base}-.{ext}") + // The part before the first dot is what makes it a device, so the `-` goes there. + let (stem, rest) = base.split_once('.').map_or((base, String::new()), |(stem, rest)| (stem, format!(".{rest}"))); + format!("{stem}-{rest}.{ext}") } else { format!("{base}.{ext}") } diff --git a/crates/engine/src/tests/swatchlib.rs b/crates/engine/src/tests/swatchlib.rs index d6c9f8452..7337d6b74 100644 --- a/crates/engine/src/tests/swatchlib.rs +++ b/crates/engine/src/tests/swatchlib.rs @@ -642,11 +642,14 @@ fn gradient_libraries_are_listed_and_gradient_swatches_rename_and_take_new_gradi #[test] fn a_library_file_name_is_never_a_windows_device_name() { use crate::cmd::swatchlib::library_file_name; - for (title, ext) in [("CON", "json"), ("nul", "json"), ("Com1", "acb"), ("LPT9", "ase"), ("aux.", "json")] { + for (title, ext) in + [("CON", "json"), ("nul", "json"), ("Com1", "acb"), ("LPT9", "ase"), ("aux.", "json"), ("CON.txt", "json"), ("nul.tar.gz", "acb")] + { let file = library_file_name(title, ext); assert!(!crate::file_access::is_device_name(&file), "{title}: {file}"); assert!( - file.ends_with(&format!(".{ext}")) && file.to_ascii_lowercase().contains(&title.trim_end_matches('.').to_ascii_lowercase()), + file.ends_with(&format!(".{ext}")) + && file.to_ascii_lowercase().starts_with(&title.split('.').next().unwrap_or_default().to_ascii_lowercase()), "{title}: {file}" ); }