pub(in crate::event_iter) fn is_implicit_mapping_line(trimmed: &str) -> bool {
find_value_indicator_offset(trimmed).is_some()
}
pub(in crate::event_iter) fn is_tab_indented_block_indicator(s: &str) -> bool {
s.strip_prefix(['-', '?']).map_or_else(
|| is_implicit_mapping_line(s),
|after| after.is_empty() || after.starts_with([' ', '\t']),
)
}
pub(in crate::event_iter) fn inline_contains_mapping_key(inline: &str) -> bool {
if find_value_indicator_offset(inline).is_some() {
return true;
}
let mut s = inline;
loop {
let trimmed = s.trim_start_matches([' ', '\t']);
if let Some(after_amp) = trimmed.strip_prefix('&') {
let name_end = after_amp.find([' ', '\t']).unwrap_or(after_amp.len());
s = &after_amp[name_end..];
} else if trimmed.starts_with('!') {
let tag_end = trimmed.find([' ', '\t']).unwrap_or(trimmed.len());
s = &trimmed[tag_end..];
} else {
break;
}
if find_value_indicator_offset(s.trim_start_matches([' ', '\t'])).is_some() {
return true;
}
}
false
}
pub(in crate::event_iter) fn find_value_indicator_offset(trimmed: &str) -> Option<usize> {
if matches!(
trimmed.as_bytes().first().copied(),
Some(
b'\t'
| b'%'
| b'@'
| b'`'
| b','
| b'['
| b']'
| b'{'
| b'}'
| b'#'
| b'&'
| b'*'
| b'!'
| b'|'
| b'>'
)
) {
return None;
}
let bytes = trimmed.as_bytes();
let mut i = 0;
let mut prev_was_space = false; while let Some(&ch) = bytes.get(i) {
if ch == b'#' && (i == 0 || prev_was_space) {
return None;
}
if ch == b'"' && i == 0 {
i += 1; while let Some(&inner) = bytes.get(i) {
match inner {
b'\\' => i += 2, b'"' => {
i += 1; break;
}
_ => i += 1,
}
}
prev_was_space = false;
continue;
}
if ch == b'\'' && i == 0 {
i += 1; while let Some(&inner) = bytes.get(i) {
i += 1;
if inner == b'\'' {
if bytes.get(i).copied() == Some(b'\'') {
i += 1; } else {
break; }
}
}
prev_was_space = false;
continue;
}
if ch == b':' {
match bytes.get(i + 1).copied() {
None | Some(b' ' | b'\t' | b'\n' | b'\r') => return Some(i),
_ => {}
}
}
prev_was_space = ch == b' ' || ch == b'\t';
i += if ch < 0x80 {
1
} else if ch & 0xE0 == 0xC0 {
2
} else if ch & 0xF0 == 0xE0 {
3
} else {
4
};
}
None
}
#[cfg(test)]
mod tests {
use super::{find_value_indicator_offset, is_implicit_mapping_line};
#[test]
fn find_value_indicator_agrees_with_is_implicit_mapping_line() {
let accepted = [
"key:",
"key: value",
"key:\t",
"key: multiple spaces",
"\"quoted key\": val",
"'single quoted': val",
"key with spaces: val",
"k:",
"longer-key-with-dashes: v",
"unicode_\u{00e9}: v",
];
for line in accepted {
assert!(
is_implicit_mapping_line(line),
"expected is_implicit_mapping_line to accept: {line:?}"
);
assert!(
find_value_indicator_offset(line).is_some(),
"find_value_indicator_offset must return Some for accepted line: {line:?}"
);
}
let rejected = [
"plain scalar",
"http://example.com",
"no colon here",
"# comment: not a key",
"",
];
for line in rejected {
assert!(
!is_implicit_mapping_line(line),
"expected is_implicit_mapping_line to reject: {line:?}"
);
assert!(
find_value_indicator_offset(line).is_none(),
"find_value_indicator_offset must return None for rejected line: {line:?}"
);
}
}
}