Image sets by their numbered name, digits kept
Test and deploy / test (push) Successful in 44s
Test and deploy / edge (push) Successful in 1m52s
Test and deploy / deploy (push) Successful in 2m1s

Without a file counter a subject-named image release was bounded by the
image's name with all numbers removed:
- STIRILING SILVER's sets stp250x…stp465x became one 44k-image release
  (all "stp x"); picture threads of many sets (keira_s04, keira_s05,
  perla_s01, apneatic-7, -8 …) one release each.
- Free-text names without numbers ("JimAndJoey Kindly Husband-to-Be.JPG")
  made each image a set: one thread was 1,177 releases.
The bound is now the name up to its last number with its digits
("stp250x", "keira_s05_l keira_s05_", "apneatic-7-"), and nothing for a
name without numbers. models fixture: 172 -> 754 releases, by set. 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 00:45:52 +02:00
1 parent 0577b16e5e
commit c4f48a6d4c
3 files changed
+36 -2

No files matched your search

+23 -1
View File
@@ -414,6 +414,18 @@ 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) {
Some(last) => {
let start = b[..=last].iter().rposition(|c| !c.is_ascii_digit()).map_or(0, |i| i + 1);
stem[..start].to_string()
}
None => String::new(),
}
}
fn image_prefix(stem: &str) -> String {
let b = stem.as_bytes();
let cut = match b.iter().rposition(u8::is_ascii_digit) {
@@ -599,7 +611,8 @@ pub fn release_identity(poster: &str, subject: &str, name: &str) -> Release {
// An image set's subject may be one word, the model's name: /^[A-Za-z]{3,}$/.
let one_word = image && remainder.len() >= 3 && remainder.bytes().all(|c| c.is_ascii_alphabetic());
if !tag && (names_release(&remainder) || one_word) {
let bound = counter_total(subject).unwrap_or_else(|| prefix.clone());
// Without a counter an image's set: its name up to its last number, digits kept; none without numbers.
let bound = counter_total(subject).unwrap_or_else(|| if image { image_set(&stem) } else { String::new() });
return Release { key: hash(&["subject", poster, &remainder, &bound]), name: remainder, source: "subject", file_total };
}
if let Some(total) = post_total.filter(|&n| n != 0 && !tag) {
@@ -1117,6 +1130,15 @@ mod image_set_tests {
assert!(!random_poster("w.merklein@freenet.de (muffin)"));
}
#[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);
assert_eq!(silver("stp250x001.jpg").key, silver("stp250x002.jpg").key);
assert_ne!(silver("stp250x001.jpg").key, silver("stp305x001.jpg").key);
let mint = |n: &str| release_identity("Cert", &format!(r#"I'm not a breath mint, but I am not all there either. [{n}" yEnc]"#), n);
assert_eq!(mint("JimAndJoey Kindly Husband-to-Be.JPG").key, mint("Oops OnlyOneMore.JPG").key);
}
#[test]
fn dates_are_no_counters() {
use super::counter_total;