use std::ops::Not;
pub fn find(src: &str, begin: usize, tag: &str) -> Option<usize> {
let start = begin;
if let Some(block_start) = src[start..].find(tag).map(|e| e + start) {
Some(block_start)
} else {
None
}
}
pub fn find_block(
src: &str,
index: usize,
start_tag: &str,
end_tag: &str,
) -> Option<(usize, Option<usize>)> {
let start = find(src, index, start_tag)?;
Some((
start,
find(src, start + start_tag.len(), end_tag).map(|e| e + end_tag.len()),
))
}
pub fn find_block_skip_ignore(
src: &str,
index: usize,
start_tag: &str,
end_tag: &str,
ignore_block: &Vec<(&str, &str)>,
) -> Option<(usize, usize)> {
let (start, mut end) = find_block(src, index, start_tag, end_tag)
.filter(|e| e.1.is_some())
.map(|e| (e.0, e.1.unwrap()))?;
loop {
let text_block = &src[start + start_tag.len()..end - end_tag.len()];
let mut ignore_range = ignore_block
.iter()
.map(|e| find_block(text_block, 0, e.0, e.1))
.filter(|e| e.is_some())
.map(|e| e.unwrap())
.collect::<Vec<(usize, Option<usize>)>>();
ignore_range.sort_by_key(|e| e.0);
let mut current_range_index = 0;
loop {
if let Some((current_start, current_end)) = ignore_range
.get(current_range_index)
.map(|e| (e.0, e.1.unwrap_or(text_block.len())))
{
let mut remove = ignore_range
.iter()
.enumerate()
.filter(|(_, (start, _))| current_start < *start && current_end > *start)
.map(|e| e.0)
.collect::<Vec<usize>>();
remove.sort();
remove.reverse();
for id in remove {
ignore_range.remove(id);
}
} else {
break;
}
current_range_index += 1;
}
if ignore_range.iter().any(|e| e.1.is_none()) {
if end + 1 >= src.len() {
return None;
}
end = find(src, end + 1, end_tag).map(|e| e + end_tag.len())?;
} else {
break;
}
}
Some((start, end))
}
#[derive(Debug, Eq, PartialEq)]
pub enum Block<'a> {
Dynamic(&'a str),
Static(&'a str),
}
impl Block<'static> {
pub fn new_group<'a>(
src: &'a str,
start_tag: &str,
end_tag: &str,
ignore_blocks: &Vec<(&str, &str)>,
) -> Vec<Block<'a>> {
let mut result = vec![];
let mut index = 0;
loop {
if let Some((current_start, current_end)) =
find_block_skip_ignore(src, index, start_tag, end_tag, ignore_blocks)
{
if current_start > index {
result.push(Block::Static(&src[index..current_start]));
}
let dyn_src = &src[current_start + start_tag.len()..current_end - end_tag.len()];
result.push(Block::Dynamic(dyn_src));
index = current_end
} else {
if index < src.len() {
result.push(Block::Static(&src[index..]));
}
break;
}
}
result
}
}
pub fn is_expr(src: &str) -> bool {
let chars = src.chars().collect::<Vec<char>>();
let first = chars[0];
if (('A'..='Z').contains(&first) || ('a'..='z').contains(&first) || first == '_').not() {
return false;
}
for item in chars.iter() {
if (('A'..='Z').contains(&item)
|| ('a'..='z').contains(&item)
|| ('0'..='9').contains(&item)
|| *item == '_')
.not()
{
return false;
}
}
true
}
pub fn next_expr(src: &str, skip_unknown: bool) -> Option<(usize, usize)> {
let mut start = None;
for (index, item) in src.chars().enumerate() {
if item == ' ' {
if let Some(start) = start {
return Some((start, index));
} else {
continue;
}
}
if ('A'..='Z').contains(&item)
|| ('a'..='z').contains(&item)
|| ('0'..='9').contains(&item)
|| item == '_'
{
if start == None {
start = Some(index)
}
} else {
if let Some(start) = start {
return Some((start, index));
} else {
if skip_unknown {
continue;
} else {
return None;
}
}
}
}
None
}
#[cfg(test)]
mod test {
use crate::utils::str::{find, find_block, find_block_skip_ignore};
#[test]
fn test_find() {
assert_eq!(find("hello world", 1, "world"), Some(6));
assert_eq!(find("hello \\world", 1, "world"), Some(7));
assert_eq!(find("hello \\world world", 1, "world"), Some(7));
}
#[test]
fn test_find_block() {
assert_eq!(
find_block("Hello {{world}}", 0, "{{", "}}"),
Some((6, Some(15)))
);
assert_eq!(find_block("Hello {{world", 0, "{{", "}}"), Some((6, None)));
assert_eq!(find_block("Hello world}}", 0, "{{", "}}"), None);
}
#[test]
fn test_find_nesting_block() {
fn get_expr(src: &str) -> Option<&str> {
find_block_skip_ignore(src, 0, "{{", "}}", &vec![("\"", "\""), ("'", "'")])
.map(|e| &src[e.0..e.1])
}
assert_eq!(
get_expr(r#"hello {{ self.data + current.context + 'item}}' + "" }}"#),
Some(r#"{{ self.data + current.context + 'item}}' + "" }}"#)
);
assert_eq!(
get_expr(r#"hello {{ self.data + current.context + 'item}}' + " }}"#),
None
);
assert_eq!(get_expr(r#"hello world"#), None);
assert_eq!(get_expr(r#"hello {{world}}"#), Some("{{world}}"));
assert_eq!(get_expr(r#"hello {{wo}}rld}}"#), Some("{{wo}}"));
assert_eq!(get_expr("hello {{wo\nrld}}"), Some("{{wo\nrld}}"));
assert_eq!(get_expr("hello好{{wo\nrld}}"), Some("{{wo\nrld}}"));
}
}