mod episode;
mod episode_title;
mod file_checksum;
mod file_extension;
mod keywords;
mod part;
mod release_group;
mod season;
mod title;
mod video_resolution;
mod volume;
mod year;
use super::token::Token;
use crate::element::{Element, ElementKind};
use crate::options::Options;
pub(crate) fn parse(mut tokens: Vec<Token>, options: &Options) -> Vec<Element> {
let mut elements: Vec<Element> = Vec::new();
let contains =
|elements: &[Element], kind: ElementKind| elements.iter().any(|e| e.kind == kind);
if options.parse_file_extension {
elements.extend(file_extension::parse_file_extension(&mut tokens));
}
elements.extend(keywords::parse_keywords(&mut tokens, options));
if options.parse_file_checksum {
elements.extend(file_checksum::parse_file_checksum(&mut tokens));
}
if options.parse_video_resolution {
elements.extend(video_resolution::parse_video_resolution(&mut tokens));
}
if options.parse_year {
elements.extend(year::parse_year(&mut tokens));
}
if options.parse_season {
elements.extend(season::parse_season(&mut tokens));
}
if options.parse_part {
elements.extend(part::parse_part(&mut tokens));
}
if options.parse_episode {
elements.extend(volume::parse_volume(&mut tokens));
elements.extend(episode::parse_episode(&mut tokens));
}
if options.parse_title {
elements.extend(title::parse_title(&mut tokens));
}
if options.parse_release_group && !contains(&elements, ElementKind::ReleaseGroup) {
elements.extend(release_group::parse_release_group(&mut tokens));
}
if options.parse_episode_title && contains(&elements, ElementKind::Episode) {
elements.extend(episode_title::parse_episode_title(&mut tokens));
}
if options.parse_episode
&& !contains(&elements, ElementKind::Episode)
&& contains(&elements, ElementKind::Title)
{
let unenclosed_at = |pos: usize| tokens.iter().any(|t| t.position == pos && !t.is_enclosed);
if let Some(e) = elements.iter_mut().find(|e| {
e.kind == ElementKind::VideoResolution
&& e.value.chars().all(|c| c.is_ascii_digit())
&& unenclosed_at(e.position)
}) {
e.kind = ElementKind::Episode;
}
}
dedupe_zero_padded(&mut elements, ElementKind::Episode);
dedupe_zero_padded(&mut elements, ElementKind::Season);
detect_content_bundle(&tokens, &mut elements);
elements.sort_by_key(|e| e.position);
elements
}
fn detect_content_bundle(tokens: &[Token], elements: &mut Vec<Element>) {
use super::token::{is_delimiter_token, is_open_bracket_token};
let is_content = |kind: Option<ElementKind>| {
matches!(
kind,
Some(ElementKind::Season) | Some(ElementKind::Episode) | Some(ElementKind::Type)
)
};
let content_side = |range: &mut dyn Iterator<Item = usize>| {
for j in range {
match tokens.get(j) {
Some(t) if is_open_bracket_token(t) || is_delimiter_token(t) => {
if is_open_bracket_token(t) {
return false;
}
}
Some(t) => return is_content(t.element_kind),
None => return false,
}
}
false
};
let mut batch_position: Option<usize> = None;
for (i, plus) in tokens.iter().enumerate() {
if !(is_delimiter_token(plus) && plus.value == "+" && plus.is_enclosed) {
continue;
}
if content_side(&mut (0..i).rev()) && content_side(&mut (i + 1..tokens.len())) {
batch_position = Some(plus.position);
break;
}
}
let Some(position) = batch_position else {
return;
};
let has_batch = elements.iter().any(|e| {
e.kind == ElementKind::ReleaseInformation && e.value.eq_ignore_ascii_case("Batch")
});
if !has_batch {
elements.push(Element {
kind: ElementKind::ReleaseInformation,
value: "Batch".to_string(),
position,
});
}
}
fn canonical_int(value: &str) -> Option<u64> {
if value.is_empty() || !value.chars().all(|c| c.is_ascii_digit()) {
return None;
}
value.parse().ok()
}
fn dedupe_zero_padded(elements: &mut Vec<Element>, kind: ElementKind) {
use std::collections::HashMap;
let mut best: HashMap<u64, usize> = HashMap::new();
let mut drop: Vec<usize> = Vec::new();
for (i, e) in elements.iter().enumerate() {
if e.kind != kind {
continue;
}
let Some(n) = canonical_int(&e.value) else {
continue;
};
match best.get(&n).copied() {
None => {
best.insert(n, i);
}
Some(prev) => {
let keep_new = elements.get(prev).is_some_and(|prev_e| {
(e.value.len(), e.position) < (prev_e.value.len(), prev_e.position)
});
if keep_new {
drop.push(prev);
best.insert(n, i);
} else {
drop.push(i);
}
}
}
}
if drop.is_empty() {
return;
}
let mut i = 0;
elements.retain(|_| {
let keep = !drop.contains(&i);
i += 1;
keep
});
}