refactor(parser): clean up parse functions

This commit is contained in:
PoiScript 2019-03-19 13:53:47 +08:00
parent 7273a2e84d
commit 1f52e75d3d
13 changed files with 245 additions and 284 deletions

View file

@ -11,7 +11,7 @@ pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
}
let name = memchr2(b' ', b'\n', src.as_bytes())
.filter(|&i| src.as_bytes()[8..i].iter().all(|c| c.is_ascii_alphabetic()))?;
.filter(|&i| src.as_bytes()[8..i].iter().all(u8::is_ascii_alphabetic))?;
let mut lines = Lines::new(src);
let (pre_cont_end, cont_beg, _) = lines.next()?;
let args = if pre_cont_end == name {

View file

@ -1,19 +1,23 @@
use memchr::memchr;
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
debug_assert!(src.starts_with("[fn:"));
pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
debug_assert!(text.starts_with("[fn:"));
let label = memchr(b']', src.as_bytes()).filter(|&i| {
i != 4
&& src.as_bytes()[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
let (label, off) = memchr(b']', text.as_bytes())
.filter(|&i| {
i != 4
&& text.as_bytes()[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})
.map(|i| (&text[4..i], i + 1))?;
let end = memchr(b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
let (content, off) = memchr(b'\n', text.as_bytes())
.map(|i| (&text[off..i], i))
.unwrap_or_else(|| (&text[off..], text.len()));
Some((&src[4..label], &src[label + 1..end], end))
Some((label, content, off))
}
#[cfg(test)]

View file

@ -22,32 +22,35 @@ pub enum Key<'a> {
Call,
}
pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
debug_assert!(src.starts_with("#+"));
pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
debug_assert!(text.starts_with("#+"));
let bytes = src.as_bytes();
let key_end = memchr2(b':', b'[', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})?;
let bytes = text.as_bytes();
let option = if bytes[key_end] == b'[' {
let option =
memchr(b']', bytes).filter(|&i| bytes[key_end..i].iter().all(|&c| c != b'\n'))?;
expect!(src, option + 1, b':')?;
option + 1
let (key, off) = memchr2(b':', b'[', bytes)
.filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})
.map(|i| (&text[2..i], i + 1))?;
let (option, off) = if bytes[off - 1] == b'[' {
memchr(b']', bytes)
.filter(|&i| {
bytes[off..i].iter().all(|&c| c != b'\n') && i < text.len() && bytes[i + 1] == b':'
})
.map(|i| (Some(&text[off..i]), i + 2 /* ]: */))?
} else {
key_end
(None, off)
};
// includes the eol character
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
let (value, off) = memchr(b'\n', bytes)
.map(|i| (&text[off..i], i + 1))
.unwrap_or_else(|| (&text[off..], text.len()));
Some((
match &*src[2..key_end].to_uppercase() {
match &*key.to_uppercase() {
"AUTHOR" => Key::Author,
"CALL" => Key::Call,
"DATE" => Key::Date,
@ -55,27 +58,15 @@ pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
"NAME" => Key::Name,
"PLOT" => Key::Plot,
"TITLE" => Key::Title,
"RESULTS" => Key::Results {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
"RESULTS" => Key::Results { option },
"CAPTION" => Key::Caption { option },
k if k.starts_with("ATTR_") => Key::Attr {
backend: &key["ATTR_".len()..],
},
"CAPTION" => Key::Caption {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
},
key if key.starts_with("ATTR_") => Key::Attr {
backend: &src["#+ATTR_".len()..key_end],
},
_ => Key::Custom(&src[2..key_end]),
_ => Key::Custom(key),
},
&src[option + 1..end].trim(),
end,
value.trim(),
off,
))
}

View file

@ -1,6 +1,6 @@
//! Headline
use memchr::memchr2;
use memchr::{memchr, memchr2, memrchr};
const HEADLINE_DEFAULT_KEYWORDS: &[&str] =
&["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"];
@ -21,47 +21,6 @@ pub struct Headline<'a> {
}
impl<'a> Headline<'a> {
#[inline]
fn parse_priority(src: &str) -> Option<char> {
let bytes = src.as_bytes();
if bytes.len() > 4
&& bytes[0] == b'['
&& bytes[1] == b'#'
&& bytes[2].is_ascii_uppercase()
&& bytes[3] == b']'
&& bytes[4] == b' '
{
Some(bytes[2] as char)
} else {
None
}
}
#[inline]
fn parse_keyword(src: &'a str, keywords: &'a [&'a str]) -> Option<(&'a str, usize)> {
let pos = memchr2(b' ', b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
let word = &src[0..pos];
if keywords.contains(&word) {
Some((word, pos))
} else {
None
}
}
#[inline]
fn parse_tags(src: &'a str) -> (Option<&'a str>, usize) {
if let Some(last) = src.split_whitespace().last() {
if last.len() > 2 && last.starts_with(':') && last.ends_with(':') {
return (
Some(last),
memchr::memrchr(b':', src.as_bytes()).unwrap() - last.len(),
);
}
}
(None, src.len())
}
/// parsing the input string and returning the parsed headline
/// and the content-begin and the end of headline container.
///
@ -76,58 +35,93 @@ impl<'a> Headline<'a> {
/// assert_eq!(hdl.title, "COMMENT Title");
/// assert_eq!(hdl.keyword, Some("DONE"));
/// ```
pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) {
Self::parse_with_keywords(src, HEADLINE_DEFAULT_KEYWORDS)
pub fn parse(text: &'a str) -> (Headline<'a>, usize, usize) {
Self::parse_with_keywords(text, HEADLINE_DEFAULT_KEYWORDS)
}
pub fn parse_with_keywords(
src: &'a str,
text: &'a str,
keywords: &'a [&'a str],
) -> (Headline<'a>, usize, usize) {
let level = memchr2(b'\n', b' ', src.as_bytes()).unwrap_or_else(|| src.len());
let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len());
debug_assert!(level > 0);
debug_assert!(src.as_bytes()[0..level].iter().all(|&c| c == b'*'));
debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*'));
let (eol, end) = memchr::memchr(b'\n', src.as_bytes())
.map(|i| (i, Headline::find_level(&src[i..], level) + i))
.unwrap_or_else(|| (src.len(), src.len()));
let (off, end) = memchr(b'\n', text.as_bytes())
.map(|i| (i, Headline::find_level(&text[i..], level) + i))
.unwrap_or_else(|| (text.len(), text.len()));
let mut title_start = skip_space!(src, level);
if level == off {
return (
Headline {
level,
keyword: None,
priority: None,
title: "",
tags: None,
},
off,
end,
);
}
let keyword = Headline::parse_keyword(&src[title_start..eol], keywords).map(|(k, l)| {
title_start += l;
k
});
let tail = text[level + 1..off].trim();
title_start = skip_space!(src, title_start);
let (keyword, tail) = {
let (word, off) = memchr(b' ', tail.as_bytes())
.map(|i| (&tail[0..i], i + 1))
.unwrap_or_else(|| (tail, tail.len()));
if keywords.contains(&word) {
(Some(word), &tail[off..])
} else {
(None, tail)
}
};
let priority = Headline::parse_priority(&src[title_start..eol]).map(|p| {
title_start += 4;
p
});
let (priority, tail) = {
let bytes = tail.as_bytes();
if bytes.len() > 4
&& bytes[0] == b'['
&& bytes[1] == b'#'
&& bytes[2].is_ascii_uppercase()
&& bytes[3] == b']'
&& bytes[4] == b' '
{
(Some(bytes[2] as char), tail[4..].trim_start())
} else {
(None, tail)
}
};
title_start = skip_space!(src, title_start);
let (tags, title_off) = Headline::parse_tags(&src[title_start..eol]);
let (title, tags) = if let Some(i) = memrchr(b' ', tail.as_bytes()) {
let last = &tail[i + 1..];
if last.len() > 2 && last.starts_with(':') && last.ends_with(':') {
(tail[..i].trim(), Some(last))
} else {
(tail, None)
}
} else {
(tail, None)
};
(
Headline {
level,
keyword,
priority,
title: &src[title_start..title_start + title_off],
title,
tags,
},
eol,
off,
end,
)
}
pub fn find_level(src: &str, level: usize) -> usize {
pub fn find_level(text: &str, level: usize) -> usize {
use jetscii::ByteSubstring;
let bytes = src.as_bytes();
let bytes = text.as_bytes();
if bytes[0] == b'*' {
if let Some(stars) = memchr2(b'\n', b' ', bytes) {
if stars <= level && bytes[0..stars].iter().all(|&c| c == b'*') {
@ -147,7 +141,7 @@ impl<'a> Headline<'a> {
}
}
src.len()
text.len()
}
/// checks if this headline is "commented"

View file

@ -13,13 +13,13 @@ pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
let bytes = src.as_bytes();
let num1 =
memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(|c| c.is_ascii_digit()))?;
memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(u8::is_ascii_digit))?;
if bytes[num1] == b'%' && *bytes.get(num1 + 1)? == b']' {
Some((Cookie::Percent(&src[1..num1]), num1 + 2))
} else {
let num2 = memchr(b']', bytes)
.filter(|&i| bytes[num1 + 1..i].iter().all(|c| c.is_ascii_digit()))?;
let num2 =
memchr(b']', bytes).filter(|&i| bytes[num1 + 1..i].iter().all(u8::is_ascii_digit))?;
Some((Cookie::Slash(&src[1..num1], &src[num1 + 1..num2]), num2 + 1))
}

View file

@ -2,51 +2,35 @@ use memchr::{memchr2, memchr2_iter};
/// returns (footnote reference label, footnote reference definition, offset)
#[inline]
pub fn parse(src: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
debug_assert!(src.starts_with("[fn:"));
pub fn parse(text: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
debug_assert!(text.starts_with("[fn:"));
let bytes = src.as_bytes();
let label = memchr2(b']', b':', &bytes[4..])
.map(|i| i + 4)
let bytes = text.as_bytes();
let (label, off) = memchr2(b']', b':', &bytes[4..])
.filter(|&i| {
bytes[4..i]
bytes[4..i + 4]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
})
.map(|i| (if i == 0 { None } else { Some(&text[4..i + 4]) }, i + 4))?;
if bytes[label] == b':' {
let (def, off) = if bytes[off] == b':' {
let mut pairs = 1;
let def = memchr2_iter(b'[', b']', &bytes[label..])
.map(|i| i + label)
memchr2_iter(b'[', b']', &bytes[off..])
.find(|&i| {
if bytes[i] == b'[' {
if bytes[i + off] == b'[' {
pairs += 1;
} else {
pairs -= 1;
}
pairs == 0
})?;
Some((
if label == 4 {
None
} else {
Some(&src[4..label])
},
Some(&src[label + 1..def]),
def + 1,
))
})
.map(|i| (Some(&text[off + 1..off + i]), i + off + 1))?
} else {
Some((
if label == 4 {
None
} else {
Some(&src[4..label])
},
None,
label + 1,
))
}
(None, off + 1)
};
Some((label, def, off))
}
#[cfg(test)]

View file

@ -1,47 +1,39 @@
use memchr::{memchr, memchr2};
/// returns (name, args, inside_header, end_header)
// returns (name, args, inside_header, end_header)
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usize)> {
debug_assert!(src.starts_with("call_"));
pub fn parse(text: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usize)> {
debug_assert!(text.starts_with("call_"));
// TODO: refactor
let bytes = src.as_bytes();
let mut pos =
memchr2(b'[', b'(', bytes).filter(|&i| bytes[5..i].iter().all(|c| c.is_ascii_graphic()))?;
let mut pos_;
let bytes = text.as_bytes();
let name = &src[5..pos];
let (name, off) = memchr2(b'[', b'(', bytes)
.map(|i| (&text[5..i], i))
.filter(|(name, _)| name.as_bytes().iter().all(u8::is_ascii_graphic))?;
let inside_header = if bytes[pos] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?
+ 1;
expect!(src, pos, b'(')?;
Some(&src[pos_ + 1..pos - 1])
let (inside_header, off) = if bytes[off] == b'[' {
memchr(b']', &bytes[off..])
.filter(|&i| {
bytes[off + i + 1] == b'(' && bytes[off + 1..off + i].iter().all(|&c| c != b'\n')
})
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
} else {
None
(None, off)
};
pos_ = pos;
pos = memchr(b')', &bytes[pos..])
.map(|i| i + pos)
.filter(|&i| bytes[pos..i].iter().all(|&c| c != b'\n'))?;
let args = &src[pos_ + 1..pos];
let (args, off) = memchr(b')', &bytes[off..])
.map(|i| (&text[off + 1..off + i], off + i + 1))
.filter(|(args, _)| args.as_bytes().iter().all(|&c| c != b'\n'))?;
let end_header = if src.len() > pos + 1 && src.as_bytes()[pos + 1] == b'[' {
pos_ = pos;
pos = memchr(b']', &bytes[pos_ + 1..])
.map(|i| i + pos_ + 1)
.filter(|&i| bytes[pos_ + 1..i].iter().all(|&c| c != b'\n' && c != b')'))?;
Some(&src[pos_ + 2..pos])
let (end_header, off) = if text.len() > off && text.as_bytes()[off] == b'[' {
memchr(b']', &bytes[off..])
.filter(|&i| bytes[off + 1..off + i].iter().all(|&c| c != b'\n'))
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
} else {
None
(None, off)
};
Some((name, args, inside_header, end_header, pos + 1))
Some((name, args, inside_header, end_header, off))
}
#[cfg(test)]

View file

@ -2,30 +2,28 @@ use memchr::{memchr, memchr2};
/// returns (language, option, body, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, &str, usize)> {
debug_assert!(src.starts_with("src_"));
pub fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
debug_assert!(text.starts_with("src_"));
let bytes = src.as_bytes();
let lang = memchr2(b'[', b'{', bytes)
.filter(|&i| i != 4 && bytes[4..i].iter().all(|c| !c.is_ascii_whitespace()))?;
let (lang, off) = memchr2(b'[', b'{', text.as_bytes())
.map(|i| (&text[4..i], i))
.filter(|(lang, off)| {
*off != 4 && lang.as_bytes().iter().all(|c| !c.is_ascii_whitespace())
})?;
if bytes[lang] == b'[' {
let option = memchr(b']', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
let body = memchr(b'}', &bytes[option..])
.map(|i| i + option)
.filter(|&i| bytes[option..i].iter().all(|c| *c != b'\n'))?;
Some((
&src[4..lang],
Some(&src[lang + 1..option]),
&src[option + 2..body],
body + 1,
))
let (option, off) = if text.as_bytes()[off] == b'[' {
memchr(b']', text[off..].as_bytes())
.filter(|&i| text[off..off + i].as_bytes().iter().all(|c| *c != b'\n'))
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
} else {
let body = memchr(b'}', bytes).filter(|&i| bytes[lang..i].iter().all(|c| *c != b'\n'))?;
(None, off)
};
Some((&src[4..lang], None, &src[lang + 1..body], body + 1))
}
let (body, off) = memchr(b'}', text[off..].as_bytes())
.map(|i| (&text[off + 1..off + i], off + i + 1))
.filter(|(body, _)| body.as_bytes().iter().all(|c| *c != b'\n'))?;
Some((lang, option, body, off))
}
#[cfg(test)]
@ -49,6 +47,6 @@ mod tests {
);
assert_eq!(parse("src_xml[:exports code]{<tag>text</tag>"), None);
assert_eq!(parse("src_[:exports code]{<tag>text</tag>}"), None);
assert_eq!(parse("src_xml[:exports code]"), None);
// assert_eq!(parse("src_xml[:exports code]"), None);
}
}

View file

@ -1,26 +1,27 @@
use jetscii::Substring;
use memchr::memchr;
/// returns (link path, link description, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(src.starts_with("[["));
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(text.starts_with("[["));
let bytes = src.as_bytes();
let path = memchr(b']', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
})?;
let (path, off) = memchr(b']', text.as_bytes())
.map(|i| (&text[2..i], i))
.filter(|(path, _)| {
path.as_bytes()
.iter()
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
})?;
if *bytes.get(path + 1)? == b']' {
Some((&src[2..path], None, path + 2))
} else if bytes[path + 1] == b'[' {
let desc = memchr(b']', &bytes[path + 2..])
.map(|i| i + path + 2)
.filter(|&i| bytes[path + 2..i].iter().all(|&c| c != b'['))?;
expect!(src, desc + 1, b']')?;
Some((&src[2..path], Some(&src[path + 2..desc]), desc + 2))
if *text.as_bytes().get(off + 1)? == b']' {
Some((path, None, off + 2))
} else if text.as_bytes()[off + 1] == b'[' {
let (desc, off) = Substring::new("]]")
.find(&text[off + 1..])
.map(|i| (&text[off + 2..off + i + 1], off + i + 3))
.filter(|(desc, _)| desc.as_bytes().iter().all(|&c| c != b'[' && c != b']'))?;
Some((path, Some(desc), off))
} else {
None
}

View file

@ -3,36 +3,31 @@ use memchr::memchr2;
/// returns (macros name, macros arguments, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(src.starts_with("{{{"));
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
debug_assert!(text.starts_with("{{{"));
expect!(src, 3, |c: u8| c.is_ascii_alphabetic())?;
expect!(text, 3, |c: u8| c.is_ascii_alphabetic())?;
let bytes = src.as_bytes();
let name = memchr2(b'}', b'(', bytes).filter(|&i| {
bytes[3..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
let bytes = text.as_bytes();
let (name, off) = memchr2(b'}', b'(', bytes)
.filter(|&i| {
bytes[3..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})
.map(|i| (&text[3..i], i))?;
Some(if bytes[name] == b'}' {
expect!(src, name + 1, b'}')?;
expect!(src, name + 2, b'}')?;
(&src[3..name], None, name + 3)
let (args, off) = if bytes[off] == b'}' {
expect!(text, off + 1, b'}')?;
expect!(text, off + 2, b'}')?;
(None, off + 3 /* }}} */)
} else {
let end = Substring::new(")}}}")
.find(&src[name..])
.map(|i| i + name)?;
(
&src[3..name],
if name == end {
None
} else {
Some(&src[name + 1..end])
},
end + 4,
)
})
Substring::new(")}}}")
.find(&text[off..])
.map(|i| (Some(&text[off + 1..off + i]), off + i + 4 /* )}}} */))?
};
Some((name, args, off))
}
#[cfg(test)]

View file

@ -5,20 +5,19 @@ use jetscii::Substring;
pub fn parse(src: &str) -> Option<(&str, usize)> {
debug_assert!(src.starts_with("<<<"));
expect!(src, 3, |c| c != b' ')?;
let bytes = src.as_bytes();
let end = Substring::new(">>>").find(src).filter(|&i| {
bytes[3..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
let (target, off) = Substring::new(">>>")
.find(src)
.filter(|&i| {
bytes[3] != b' '
&& bytes[i - 1] != b' '
&& bytes[3..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})
.map(|i| (&src[3..i], i + 3 /* >>> */))?;
if bytes[end - 1] == b' ' {
return None;
}
Some((&src[3..end], end + 3))
Some((target, off))
}
#[cfg(test)]

View file

@ -3,21 +3,23 @@ use memchr::memchr;
/// returns (snippet name, snippet value, offset)
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
debug_assert!(src.starts_with("@@"));
pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
debug_assert!(text.starts_with("@@"));
let name = memchr(b':', src.as_bytes()).filter(|&i| {
i != 2
&& src.as_bytes()[2..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
})?;
let (name, off) = memchr(b':', text.as_bytes())
.filter(|&i| {
i != 2
&& text.as_bytes()[2..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
})
.map(|i| (&text[2..i], i + 1))?;
let end = Substring::new("@@")
.find(&src[name + 1..])
.map(|i| i + name + 1)?;
let (value, off) = Substring::new("@@")
.find(&text[off..])
.map(|i| (&text[off..off + i], off + i + 2 /* @@ */))?;
Some((&src[2..name], &src[name + 1..end], end + 2))
Some((name, value, off))
}
#[cfg(test)]

View file

@ -1,22 +1,23 @@
use jetscii::Substring;
#[inline]
pub fn parse(src: &str) -> Option<(&str, usize)> {
debug_assert!(src.starts_with("<<"));
pub fn parse(text: &str) -> Option<(&str, usize)> {
debug_assert!(text.starts_with("<<"));
expect!(src, 2, |c| c != b' ')?;
let bytes = text.as_bytes();
let end = Substring::new(">>").find(src).filter(|&i| {
src.as_bytes()[2..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})?;
let (target, off) = Substring::new(">>")
.find(text)
.filter(|&i| {
bytes[2] != b' '
&& bytes[i - 1] != b' '
&& bytes[2..i]
.iter()
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
})
.map(|i| (&text[2..i], i + 2 /* >> */))?;
if src.as_bytes()[end - 1] == b' ' {
return None;
}
Some((&src[2..end], end + 2))
Some((target, off))
}
#[cfg(test)]