anitomy_ng/together/mod.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Parse a set of related filenames together, using what is invariant across
6//! the set to disambiguate what a single filename cannot.
7//!
8//! Each input is parsed on its own, then two passes run: [`segment`] suppresses
9//! per-file directory noise, and [`diff`] reconciles each parse against what
10//! varies across the set.
11
12mod diff;
13mod segment;
14
15use crate::element::Element;
16use crate::options::Options;
17
18/// Parse a single filename that may carry a directory prefix, so the result
19/// describes the file rather than the folder.
20///
21/// [`parse`](crate::parse) treats its input as one flat string, matching
22/// upstream, so a path leaves separators and duplicated folder text in the
23/// title. This strips a real directory prefix first and recovers a title that
24/// lives only in the parent folder. Both `/` and `\` work on every platform,
25/// including UNC and drive-letter roots.
26///
27/// Without a directory prefix this is exactly [`parse`](crate::parse), and a
28/// separator inside a title (`Fate/stay night`) is left alone.
29///
30/// ```
31/// let elements = anitomy_ng::parse_path(
32/// "Frieren (01-12) [Batch]/Frieren - 05 [1080p].mkv",
33/// anitomy_ng::Options::default(),
34/// );
35/// let title = elements.iter().find(|e| e.kind == anitomy_ng::ElementKind::Title);
36/// assert_eq!(title.map(|e| e.value.as_str()), Some("Frieren"));
37/// ```
38///
39/// For a set of related files, prefer [`parse_together`], which additionally
40/// uses what varies across the set.
41pub fn parse_path(input: &str, options: Options) -> Vec<Element> {
42 segment::parse_one(input, options)
43}
44
45/// Parse related filenames together, order-preserving (result `i` is for
46/// `inputs[i]`); an unrelated or single-item list is left as its per-file parse.
47pub fn parse_together(inputs: &[&str], options: Options) -> Vec<Vec<Element>> {
48 let mut results: Vec<Vec<Element>> = inputs
49 .iter()
50 .map(|s| segment::parse_one(s, options))
51 .collect();
52
53 // The cross-file differential needs at least two members to have any signal.
54 if inputs.len() < 2 {
55 return results;
56 }
57
58 // `char`s so positions line up with the codepoint-based ones the parser emits.
59 let chars: Vec<Vec<char>> = inputs.iter().map(|s| s.chars().collect()).collect();
60 diff::reconcile(&mut results, &chars);
61
62 results
63}