mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 07:16:52 +00:00
Merge pull request #5304 from storytold/fix/sec-export-name-linear
Export for Screens: name patterns and taken names take linear work
This commit is contained in:
@@ -70,9 +70,11 @@ pub fn render(template: &str, p: &NameParts<'_>) -> String {
|
||||
let mut out = String::with_capacity(t.len() + 16);
|
||||
let mut rest = t;
|
||||
while let Some(c) = rest.chars().next() {
|
||||
// Bracketed tokens; an unknown one stays as written, with `-` for its brackets.
|
||||
// Bracketed tokens; an unknown one stays as written, with `-` for its brackets. A token's
|
||||
// name has no `<`, so the search for its `>` stops at the next `<`: each part of the
|
||||
// pattern is searched once, not once for every `<` before it.
|
||||
if c == '<'
|
||||
&& let Some(end) = rest.find('>')
|
||||
&& let Some(end) = rest.get(1..).and_then(|r| r.find(['<', '>'])).map(|i| i + 1).filter(|&i| rest.as_bytes().get(i) == Some(&b'>'))
|
||||
&& let Some(value) = rest.get(1..end).and_then(|name| token(name, p))
|
||||
{
|
||||
out.push_str(&value);
|
||||
@@ -155,6 +157,16 @@ mod tests {
|
||||
assert_eq!(render("#", &NameParts { counter: 12345, ..parts() }), "12345");
|
||||
}
|
||||
|
||||
/// Many `<` without a `>`, or before one far away, are each looked at once.
|
||||
#[test]
|
||||
fn unmatched_brackets_are_read_in_one_pass() {
|
||||
let p = parts();
|
||||
let n = 200_000;
|
||||
assert_eq!(render(&"<".repeat(n), &p), "-".repeat(n));
|
||||
assert_eq!(render(&format!("{}<name>", "<".repeat(n)), &p), format!("{}Dark", "-".repeat(n)));
|
||||
assert_eq!(render("<a<name>>", &p), "-aDark-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dates_are_strict_about_hyphens_and_days() {
|
||||
for bad in ["2026-1-011", "2-0-2-6-1-0-1-6", "2026--10--16", "2026-02-31", "2026-10-16-", "202610-16"] {
|
||||
|
||||
@@ -521,6 +521,11 @@ struct Output {
|
||||
folders: std::collections::HashMap<String, String>,
|
||||
/// Sub-folders already made, by their [`fold`]ed names.
|
||||
made: HashSet<String>,
|
||||
/// For each [`fold`]ed name asked for, the number to try next: many files given one name
|
||||
/// would otherwise try `-2`, `-3`… from the start each time.
|
||||
next: std::collections::HashMap<String, u64>,
|
||||
/// Each [`fold`]ed name asked for with the file (`key`) that got a name from it.
|
||||
claimed: HashSet<(String, String)>,
|
||||
/// `(relative name, bytes)` kept to return or zip (empty when written to a folder).
|
||||
kept: Vec<(String, Vec<u8>)>,
|
||||
/// The paths written.
|
||||
@@ -540,8 +545,14 @@ impl Output {
|
||||
}
|
||||
None => name.to_string(),
|
||||
};
|
||||
let asked = (fold(&name), key.to_string());
|
||||
// The same file under the same name again: it has its name already.
|
||||
if self.claimed.contains(&asked) {
|
||||
return None;
|
||||
}
|
||||
let (base, ext) = name.rsplit_once('.').map_or((name.as_str(), ""), |(b, e)| (b, e));
|
||||
for n in 1.. {
|
||||
let start = self.next.get(&asked.0).copied().unwrap_or(1);
|
||||
for n in start.. {
|
||||
let candidate = if n == 1 {
|
||||
name.to_string()
|
||||
} else if ext.is_empty() {
|
||||
@@ -554,6 +565,8 @@ impl Output {
|
||||
Some(_) => continue,
|
||||
None => {
|
||||
self.taken.insert(fold(&candidate), key.to_string());
|
||||
self.next.insert(asked.0.clone(), n + 1);
|
||||
self.claimed.insert(asked);
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
@@ -1280,7 +1293,26 @@ pub(super) fn export_settings(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScreenSize, safe_file_name};
|
||||
use super::{Output, ScreenSize, safe_file_name};
|
||||
|
||||
/// Files given one name are numbered in turn, each numbering going on from the last one (so
|
||||
/// thousands of them don't each try every number used before), and a file asking again for
|
||||
/// the name it got is told it has it.
|
||||
#[test]
|
||||
fn taken_names_are_numbered_from_the_last_number() {
|
||||
let mut out = Output::default();
|
||||
assert_eq!(out.claim("a.png", "1").as_deref(), Some("a.png"));
|
||||
assert_eq!(out.claim("A.png", "2").as_deref(), Some("A-2.png"));
|
||||
assert_eq!(out.claim("a-3.png", "x").as_deref(), Some("a-3.png"));
|
||||
assert_eq!(out.claim("a.png", "3").as_deref(), Some("a-4.png"));
|
||||
assert_eq!(out.claim("a.png", "2"), None);
|
||||
assert_eq!(out.claim("a.png", "1"), None);
|
||||
assert_eq!(out.claim("sub/a.png", "1").as_deref(), Some("sub/a.png"));
|
||||
for k in 0..20_000 {
|
||||
assert!(out.claim("same.png", &k.to_string()).is_some());
|
||||
}
|
||||
assert_eq!(out.claim("same.png", "last").as_deref(), Some("same-20001.png"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_parse_refuses_signs_and_exponents() {
|
||||
|
||||
Reference in New Issue
Block a user