Image sets: suffixes that don't count the images go first
Test and deploy / test (push) Successful in 44s
Test and deploy / edge (push) Successful in 2m0s
Test and deploy / deploy (push) Successful in 2m1s

The set bound cut at an image name's last number, but in many threads
that's a suffix, so every image was a set ("It does my heart good to see
Red Dog dancing again": 4,683 releases of 4,730 files): a repeat of the
index (katelg010_10), a year (hgrscans1097_2009), a size
(pj02kristina_24of40_1600), an "of" total (24of40). imageSet drops
those, then cuts at the last number (cutie-071-032 stays cutie-071-).
Both implementations; tests; fixtures identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-12 01:45:57 +02:00
1 parent c4f48a6d4c
commit 392ff09910
3 files changed
+93 -7

No files matched your search

+55 -6
View File
@@ -414,18 +414,48 @@ static AUDIO: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(mp3|flac|m4a|m4b|og
static DOCUMENT: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)\.(pdf|epub|mobi|azw3?|djvu|cbr|cbz|cb7|chm|lit|docx?)$"));
/// An image set's name without a counter: up to the last number, without numbers.
/// `/\d/.test(stem) ? stem.replace(/\d+\D*$/, '') : ''`: an image's set, digits kept.
fn image_set(stem: &str) -> String {
let b = stem.as_bytes();
match b.iter().rposition(u8::is_ascii_digit) {
/// imageSet (lib/release/identity.ts): the name up to the number that counts the images, digits kept.
pub fn image_set(stem: &str) -> String {
// .replace(/(\d+)\s*of\s*\d+/gi, '$1')
let mut s = OF_TOTAL.replace_all(stem.as_bytes(), &b"$1"[..]).into_owned();
loop {
// /[_-](\d+)$/
let digits = s.iter().rev().take_while(|c| c.is_ascii_digit()).count();
if digits == 0 || s.len() <= digits || !matches!(s[s.len() - digits - 1], b'_' | b'-') {
break;
}
let n = &s[s.len() - digits..];
let before = &s[..s.len() - digits - 1];
// /(\d+)\D*$/: the last digit run before
let Some(last) = before.iter().rposition(u8::is_ascii_digit) else { break };
let start = before[..=last].iter().rposition(|c| !c.is_ascii_digit()).map_or(0, |i| i + 1);
let previous = &before[start..=last];
let same = trim_zeros(n) == trim_zeros(previous);
let year = n.len() == 4 && (n.starts_with(b"19") || n.starts_with(b"20"));
if same || year || n.len() >= 4 {
let cut = before.len();
s.truncate(cut);
} else {
break;
}
}
match s.iter().rposition(u8::is_ascii_digit) {
Some(last) => {
let start = b[..=last].iter().rposition(|c| !c.is_ascii_digit()).map_or(0, |i| i + 1);
stem[..start].to_string()
let start = s[..=last].iter().rposition(|c| !c.is_ascii_digit()).map_or(0, |i| i + 1);
String::from_utf8_lossy(&s[..start]).into_owned()
}
None => String::new(),
}
}
/// Number(digits) equality for digit strings: without leading zeros (all zeros: "0").
fn trim_zeros(d: &[u8]) -> &[u8] {
let t = d.iter().position(|&c| c != b'0').map_or(&d[d.len().saturating_sub(1)..], |i| &d[i..]);
t
}
static OF_TOTAL: LazyLock<Regex> = LazyLock::new(|| re(r"(?i)([0-9]+)SP*ofSP*[0-9]+"));
fn image_prefix(stem: &str) -> String {
let b = stem.as_bytes();
let cut = match b.iter().rposition(u8::is_ascii_digit) {
@@ -1130,6 +1160,25 @@ mod image_set_tests {
assert!(!random_poster("w.merklein@freenet.de (muffin)"));
}
#[test]
fn image_sets_without_suffixes() {
use super::image_set;
for (stem, want) in [
("stp250x001", "stp250x"),
("cutie-071-032", "cutie-071-"),
("katelg010_10", "katelg"),
("kate112lg035_35", "kate112lg"),
("!!hgrscans1097_2009", "!!hgrscans"),
("pj02kristina_24of40_1600", "pj02kristina_"),
("keira_s04_l keira_s04_0040_l", "keira_s04_l keira_s04_"),
("IMG_2676", "IMG_"),
("2877-041", "2877-"),
("JimAndJoey Kindly Husband-to-Be", ""),
] {
assert_eq!(image_set(stem), want, "{stem}");
}
}
#[test]
fn image_sets_by_their_numbered_name() {
let silver = |n: &str| release_identity("p", &format!(r#"The excellent STIRILING SILVER - Always popular - "{n}" yEnc 320794 bytes"#), n);