use std::collections::HashSet;
use super::delimiter::is_space;
use super::token::{is_delimiter_token, is_not_delimiter_token, Token};
use crate::element::{Element, ElementKind};
pub(crate) fn element_from_token(
kind: ElementKind,
token: &Token,
value: Option<&str>,
position: Option<usize>,
) -> Element {
Element {
kind,
value: value
.map(str::to_string)
.unwrap_or_else(|| token.value.clone()),
position: position.unwrap_or(token.position),
}
}
fn first_char(token: &Token) -> Option<char> {
token.value.chars().next()
}
pub(crate) fn underscore_is_separator(tokens: &[Token]) -> bool {
let mut spaces = 0usize;
let mut underscores = 0usize;
for ch in tokens
.iter()
.filter(|t| is_delimiter_token(t))
.filter_map(first_char)
{
if is_space(ch) {
spaces += 1;
} else if ch == '_' {
underscores += 1;
}
}
underscores >= spaces
}
pub(crate) fn build_element_value(
tokens: &[Token],
keep_delimiters: bool,
underscore_separator: bool,
) -> String {
let delimiters: HashSet<char> = tokens
.iter()
.filter(|t| is_delimiter_token(t))
.filter_map(first_char)
.collect();
let has_single_delimiter = delimiters.len() == 1;
let has_spaces = delimiters.iter().copied().any(is_space);
let has_underscores = delimiters.contains(&'_');
let has_ampersand = delimiters.contains(&'&');
let is_transformable_delimiter = |token: &Token| -> bool {
if is_not_delimiter_token(token) {
return false;
}
let Some(ch) = first_char(token) else {
return false;
};
if ch == '_' {
return underscore_separator && (!keep_delimiters || has_ampersand);
}
if keep_delimiters {
return false;
}
if ch == ',' || ch == '&' || ch == '~' {
return false;
}
if is_space(ch) {
return true;
}
if has_spaces || has_underscores {
return false;
}
if ch == '.' {
return true;
}
has_single_delimiter
};
let mut end = tokens.len();
if !keep_delimiters {
let mut prev_delimiter: Option<char> = None;
while end > 0 {
let Some(last) = tokens.get(end - 1) else {
break;
};
if !is_delimiter_token(last) {
break;
}
let Some(delim) = first_char(last) else { break };
if delim == '~' {
break;
}
if delim == '.' && prev_delimiter.is_some_and(is_space) {
break;
}
prev_delimiter = Some(delim);
end -= 1;
}
}
let trailing_ellipsis = !keep_delimiters
&& tokens.get(end..).map_or(0, |s| {
s.iter().take_while(|t| first_char(t) == Some('.')).count()
}) >= 3;
let mut value = String::new();
for token in tokens.get(..end).unwrap_or(&[]) {
if is_transformable_delimiter(token) {
value.push(' ');
} else {
value.push_str(&token.value);
}
}
if trailing_ellipsis {
value.push_str("...");
}
value
}