diff --git a/crates/catalog/src/dates.rs b/crates/catalog/src/dates.rs index 200d80be..06d5c2c0 100644 --- a/crates/catalog/src/dates.rs +++ b/crates/catalog/src/dates.rs @@ -128,13 +128,15 @@ pub fn display_time(iso: &str) -> String { /// Header text for a group key. pub fn group_label(key: &str) -> String { - let month = |k: &str| k.get(5..7).and_then(|m| m.parse::().ok()).filter(|m| (1..=12).contains(m)).map(|m| MONTHS[m - 1]); + let month = |k: &str| k.get(5..7).and_then(|m| m.parse::().ok()).and_then(|m| MONTHS.get(m.wrapping_sub(1))); + // `get` rather than `[..]`: a non-ASCII key must not split a character + let year = key.get(..4).unwrap_or(key); match key.len() { - 10 => match (weekday(key), month(key), key[8..10].parse::().ok()) { - (Some(w), Some(m), Some(d)) => format!("{w}, {d} {m} {}", &key[..4]), + 10 => match (weekday(key), month(key), key.get(8..10).and_then(|d| d.parse::().ok())) { + (Some(w), Some(m), Some(d)) => format!("{w}, {d} {m} {year}"), _ => key.to_string(), }, - 7 => month(key).map(|m| format!("{m} {}", &key[..4])).unwrap_or_else(|| key.to_string()), + 7 => month(key).map(|m| format!("{m} {year}")).unwrap_or_else(|| key.to_string()), 4 => key.to_string(), _ => "Unknown Date".into(), } @@ -196,6 +198,9 @@ mod tests { assert_eq!(group_label("2026-02"), "February 2026"); assert_eq!(group_label("2026"), "2026"); assert_eq!(group_label(""), "Unknown Date"); + // non-ASCII keys of a date's length used to be sliced mid-character + assert_eq!(group_label("2024-01é0"), "2024-01é0"); + assert_eq!(group_label("202é01"), "January 202é01"); assert_eq!(shift_iso("2026-12-31T23:30:00", 3600).as_deref(), Some("2027-01-01T00:30:00")); assert_eq!(shift_iso("2026-03-01T00:00:00.25+02:00", -1).as_deref(), Some("2026-02-28T23:59:59.25+02:00")); assert_eq!(shift_iso("nope", 5), None); diff --git a/crates/catalog/src/lib.rs b/crates/catalog/src/lib.rs index 1828399b..8241372e 100644 --- a/crates/catalog/src/lib.rs +++ b/crates/catalog/src/lib.rs @@ -6,6 +6,7 @@ //! - **undo/redo**: the engine keeps inverse ops; //! - **determinism**: replaying the log reproduces the state exactly (property-tested). #![forbid(unsafe_code)] +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] pub mod dates; pub mod journal; diff --git a/crates/catalog/src/stacks.rs b/crates/catalog/src/stacks.rs index ecc97a82..304106ae 100644 --- a/crates/catalog/src/stacks.rs +++ b/crates/catalog/src/stacks.rs @@ -277,7 +277,7 @@ mod tests { #[test] fn group_merge_set_top_remove_and_arrange() { let (mut c, ids) = cat(6, &[]); - let [a, b, x, d, e, f] = ids[..] else { unreachable!() }; + let [a, b, x, d, e, f] = ids[..] else { panic!("six ids") }; let op = c.group_ops(b, &[a, b, x], false).unwrap(); let inv = c.apply(op).unwrap(); let s = c.stack_of(a).unwrap().clone(); diff --git a/crates/catalog/src/store.rs b/crates/catalog/src/store.rs index 98e690b9..6207173a 100644 --- a/crates/catalog/src/store.rs +++ b/crates/catalog/src/store.rs @@ -87,7 +87,7 @@ impl Store for FsStore { } self.appender = Some((name.to_string(), f)); } - let (_, f) = self.appender.as_mut().expect("appender just opened"); + let Some((_, f)) = self.appender.as_mut() else { return Err(io::Error::other("appender not open")) }; f.write_all(data)?; f.sync_data() } diff --git a/crates/codecs/src/jpeg_par.rs b/crates/codecs/src/jpeg_par.rs index 1dfe5ecd..6ec9b6a8 100644 --- a/crates/codecs/src/jpeg_par.rs +++ b/crates/codecs/src/jpeg_par.rs @@ -274,7 +274,7 @@ pub fn encode( { let rows: Vec<(usize, Vec<&mut [f32]>)> = { let mut its: Vec<_> = full.iter_mut().map(|p| p.chunks_mut(width)).collect(); - (0..height).map(|y| (y, its.iter_mut().map(|it| it.next().unwrap()).collect())).collect() + (0..height).map_while(|y| Some((y, its.iter_mut().map(|it| it.next()).collect::>()?))).collect() }; let conv = |(y, mut out): (usize, Vec<&mut [f32]>)| { let src = &data[y * width * channels..(y + 1) * width * channels]; diff --git a/crates/codecs/src/lib.rs b/crates/codecs/src/lib.rs index 2c98ea7c..3e8929e1 100644 --- a/crates/codecs/src/lib.rs +++ b/crates/codecs/src/lib.rs @@ -12,6 +12,7 @@ //! //! No decoder panics on malformed input (property-tested). #![forbid(unsafe_code)] +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] mod convert; pub mod encode;