use granit_parser::{ErrorKind, Event, Parser, ScalarStyle, ScanError, StructureStyle};
fn collect_events(input: &str) -> Result<Vec<Event<'_>>, ScanError> {
let str_events = Parser::new_from_str(input).collect::<Result<Vec<_>, _>>();
let iter_events = Parser::new_from_iter(input.chars()).collect::<Result<Vec<_>, _>>();
assert_eq!(str_events, iter_events, "input: {input:?}");
Ok(str_events?.into_iter().map(|(event, _)| event).collect())
}
fn collect_scalars(input: &str) -> Result<Vec<String>, ScanError> {
Ok(collect_events(input)?
.into_iter()
.filter_map(|event| match event {
Event::Scalar(value, ..) => Some(value.into_owned()),
_ => None,
})
.collect())
}
#[test]
fn tabs_separate_mapping_values_in_block_and_flow_contexts() {
for value in [
"1", "-1", "true", "false", "null", "value", "-item", "_value", "äöü",
] {
for separator in ["\t", "\t\t", "\t ", " \t"] {
for (yaml, mapping_style, key_style) in [
(
format!("{{\"key\":{separator}{value}}}"),
StructureStyle::Flow,
ScalarStyle::DoubleQuoted,
),
(
format!("key:{separator}{value}\n"),
StructureStyle::Block,
ScalarStyle::Plain,
),
(
format!("? key\n:{separator}{value}\n"),
StructureStyle::Block,
ScalarStyle::Plain,
),
] {
assert_eq!(
collect_events(&yaml).unwrap(),
[
Event::StreamStart,
Event::DocumentStart(false, None),
Event::MappingStart(mapping_style, 0, None),
Event::Scalar("key".into(), key_style, 0, None),
Event::Scalar(value.into(), ScalarStyle::Plain, 0, None),
Event::MappingEnd,
Event::DocumentEnd,
Event::StreamEnd,
],
"input: {yaml:?}",
);
}
}
}
}
#[test]
fn tabs_after_colons_preserve_value_and_comment_parsing() {
for (yaml, expected) in [
("key:\t\"value\"\n", "value"),
("key:\t|\n value\n", "value\n"),
("key:\t[value]\n", "value"),
("[key:\tvalue]\n", "value"),
("? key\n:\tvalue\n", "value"),
("? key\n:\t[value]\n", "value"),
(" key:\tvalue\n", "value"),
("key:\tvalue # comment\n", "value"),
("key:\t# comment\n value\n", "value"),
("key:\t\n value\n", "value"),
("{key:\t# comment\nvalue}\n", "value"),
] {
assert_eq!(
collect_scalars(yaml).unwrap(),
["key", expected],
"input: {yaml:?}",
);
}
}
#[test]
fn tabs_after_colons_preserve_empty_values_and_following_entries() {
for yaml in [
"key:\t",
"key:\t\n",
"{key:\t}",
"[key:\t]",
"key:\t# empty\n",
"{key:\t# empty\n}",
"[key:\t# empty\n]",
] {
assert_eq!(
collect_scalars(yaml).unwrap(),
["key", "~"],
"input: {yaml:?}"
);
}
for yaml in [
"key:\t# empty\nnext:\tvalue\n",
"{key:\t, next:\tvalue}",
"{key:\t# empty\n, next:\tvalue}",
"[key:\t, next:\tvalue]",
] {
assert_eq!(
collect_scalars(yaml).unwrap(),
["key", "~", "next", "value"],
"input: {yaml:?}",
);
}
}
#[test]
fn tabs_after_colons_preserve_tagged_anchors_and_aliases() {
for (yaml, style) in [
("key:\t&a !local value\ncopy:\t*a\n", StructureStyle::Block),
("{key:\t&a !local value, copy:\t*a}", StructureStyle::Flow),
] {
assert_eq!(
collect_events(yaml).unwrap(),
[
Event::StreamStart,
Event::DocumentStart(false, None),
Event::MappingStart(style, 0, None),
Event::Scalar("key".into(), ScalarStyle::Plain, 0, None),
Event::Scalar(
"value".into(),
ScalarStyle::Plain,
1,
Some(std::borrow::Cow::Owned(granit_parser::Tag::new(
"!", "local"
))),
),
Event::Scalar("copy".into(), ScalarStyle::Plain, 0, None),
Event::Alias(1),
Event::MappingEnd,
Event::DocumentEnd,
Event::StreamEnd,
],
"input: {yaml:?}",
);
}
}
#[test]
fn tabs_after_colons_do_not_allow_tab_indentation() {
for yaml in [
"key:\n\tvalue\n",
"key:\t\n\tvalue\n",
"key:\t# comment\n\tvalue\n",
] {
assert_eq!(
collect_scalars(yaml).unwrap_err().kind(),
&ErrorKind::TabInBlockIndentation,
"input: {yaml:?}",
);
}
}
#[test]
fn tabs_after_colons_cannot_indent_compact_block_collections() {
for (value, expected) in [
("- value", vec!["key", "value"]),
("child: value", vec!["key", "child", "value"]),
("\"child\": value", vec!["key", "child", "value"]),
] {
let yaml = format!("? key\n: {value}\n");
assert_eq!(collect_scalars(&yaml).unwrap(), expected);
for separator in ["\t", "\t\t", "\t ", " \t"] {
let yaml = format!("? key\n:{separator}{value}\n");
assert!(collect_scalars(&yaml).is_err(), "input: {yaml:?}");
let yaml = format!("? key\n:{separator}# comment\n {value}\n");
assert_eq!(collect_scalars(&yaml).unwrap(), expected, "input: {yaml:?}");
}
}
}
#[test]
fn tab_in_block_literal_body_is_allowed() {
let yaml = "key: |\n a\tb"; let scalars = collect_scalars(yaml).expect("parser should accept tab inside block scalar body");
assert_eq!(scalars, vec!["key".to_string(), "a\tb\n".to_string()]);
}
#[test]
fn tab_in_block_folded_body_is_allowed() {
let yaml = "key: >\n a\tb";
let scalars =
collect_scalars(yaml).expect("parser should accept tab inside folded block scalar body");
assert_eq!(scalars, vec!["key".to_string(), "a\tb\n".to_string()]);
}
#[test]
fn tab_at_start_of_block_scalar_is_rejected() {
let yaml = "key: |\n\tvalue";
let mut got_err: Option<ScanError> = None;
for item in Parser::new_from_str(yaml) {
match item {
Ok(_) => {}
Err(e) => {
got_err = Some(e);
break;
}
}
}
let err =
got_err.expect("expected a ScanError due to leading tab at start of block scalar content");
assert!(
err.info()
.contains("a block scalar content cannot start with a tab"),
"unexpected error message: {}",
err.info()
);
}
#[test]
fn explicit_indent_root_literal_can_end_before_document_start() {
let yaml = "|2\n---\n";
let scalars =
collect_scalars(yaml).expect("parser should accept empty explicit-indented block scalar");
assert_eq!(scalars.first().map(String::as_str), Some(""));
}
#[test]
fn explicit_indent_root_literal_can_end_before_comment() {
let yaml = "|2\n# comment\n";
let scalars = collect_scalars(yaml)
.expect("parser should accept empty explicit-indented block scalar before comment");
assert_eq!(scalars, vec![String::new()]);
}