use crate::element::{Element, ElementKind};
use crate::options::Options;
pub(super) fn parse_one(input: &str, options: Options) -> Vec<Element> {
let chars: Vec<char> = input.chars().collect();
let Some(dir_end) = directory_boundary(&chars, options) else {
return crate::parse(input, options);
};
let Some(tail) = chars.get(dir_end..) else {
return crate::parse(input, options);
};
let filename: String = tail.iter().collect();
let mut elements = crate::parse(&filename, options);
for element in &mut elements {
element.position = element.position.saturating_add(dir_end);
}
if !elements.iter().any(|e| e.kind == ElementKind::Title) {
let parent_start = parent_component_start(&chars, dir_end);
if let Some(parent) = chars.get(parent_start..dir_end.saturating_sub(1)) {
let parent_input: String = parent.iter().collect();
if let Some(title) = crate::parse(&parent_input, options)
.into_iter()
.find(|e| e.kind == ElementKind::Title)
{
elements.push(Element {
position: title.position.saturating_add(parent_start),
..title
});
elements.sort_by_key(|e| e.position);
}
}
}
elements
}
fn directory_boundary(chars: &[char], options: Options) -> Option<usize> {
if has_absolute_windows_prefix(chars) {
return chars
.iter()
.rposition(|&c| is_path_separator(c))
.map(|i| i.saturating_add(1));
}
for i in (0..chars.len()).rev() {
if !chars.get(i).is_some_and(|&c| is_path_separator(c)) {
continue;
}
let prefix = chars.get(..i).unwrap_or_default();
let tail = chars.get(i.saturating_add(1)..).unwrap_or_default();
if looks_like_filename(prefix, tail, options) {
return Some(i.saturating_add(1));
}
}
None
}
fn looks_like_filename(prefix: &[char], tail: &[char], options: Options) -> bool {
let tail_input: String = tail.iter().collect();
let elements = crate::parse(&tail_input, options);
if elements.iter().any(|e| is_release_descriptor(e.kind)) {
return true;
}
let Some(tail_title) = elements.iter().find(|e| e.kind == ElementKind::Title) else {
return true;
};
let parent_input: String = prefix.iter().collect();
crate::parse(&parent_input, options)
.iter()
.any(|e| e.kind == ElementKind::Title && e.value == tail_title.value)
}
fn is_release_descriptor(kind: ElementKind) -> bool {
!matches!(
kind,
ElementKind::Title
| ElementKind::Episode
| ElementKind::EpisodeTitle
| ElementKind::FileExtension
)
}
fn parent_component_start(chars: &[char], dir_end: usize) -> usize {
let boundary_sep = dir_end.saturating_sub(1);
let mut start = 0;
for i in 0..boundary_sep {
if chars.get(i).is_some_and(|&c| is_path_separator(c)) {
start = i.saturating_add(1);
}
}
start
}
fn is_path_separator(c: char) -> bool {
matches!(c, '/' | '\\')
}
fn has_absolute_windows_prefix(chars: &[char]) -> bool {
let unc = matches!((chars.first(), chars.get(1)), (Some('\\'), Some('\\')));
let drive = matches!(
(chars.first(), chars.get(1), chars.get(2)),
(Some(c), Some(':'), Some(sep)) if c.is_ascii_alphabetic() && is_path_separator(*sep)
);
unc || drive
}