use super::{
append_children, ensure_trailing_newline, entry_indices, fresh_token, has_child_token,
nth_entry_index, Lang, SyntaxNode,
};
use crate::as_yaml::{AsYaml, YamlKind};
use crate::lex::SyntaxKind;
use crate::yaml::ValueNode;
use rowan::ast::AstNode;
use rowan::GreenNodeBuilder;
ast_node!(Sequence, SEQUENCE, "A YAML sequence (list)");
pub(crate) fn must_render_flow(node: &SyntaxNode) -> bool {
if node
.parent()
.is_some_and(|p| p.kind() == SyntaxKind::SEQUENCE_ENTRY)
{
return true;
}
let mut cur = node.parent();
while let Some(p) = cur {
let opener = match p.kind() {
SyntaxKind::MAPPING => Some(SyntaxKind::LEFT_BRACE),
SyntaxKind::SEQUENCE => Some(SyntaxKind::LEFT_BRACKET),
_ => None,
};
if let Some(open) = opener {
let pending =
crate::nodes::Mapping::cast(p.clone()).is_some_and(|m| m.is_block_placeholder());
if has_child_token(&p, |k| k == open) && !pending {
return true;
}
}
cur = p.parent();
}
false
}
fn parent_value_has_leading_indent(seq: &SyntaxNode) -> bool {
let Some(parent) = seq.parent() else {
return false;
};
if parent.kind() != SyntaxKind::VALUE {
return false;
}
let mut saw_indent = false;
for child in parent.children_with_tokens() {
match &child {
rowan::NodeOrToken::Node(n) if n == seq => return saw_indent,
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::INDENT => saw_indent = true,
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::NEWLINE => {}
_ => saw_indent = false,
}
}
false
}
fn trailing_newline_indent(node: &SyntaxNode) -> Option<String> {
let tokens: Vec<_> = node
.descendants_with_tokens()
.filter_map(|el| el.into_token())
.collect();
let mut result = String::new();
let last = tokens.last()?;
match last.kind() {
SyntaxKind::NEWLINE => {
result.push_str(last.text());
}
SyntaxKind::INDENT => {
let prev = tokens.iter().rev().nth(1)?;
if prev.kind() != SyntaxKind::NEWLINE {
return None;
}
result.push_str(prev.text());
result.push_str(last.text());
}
_ => return None,
}
Some(result)
}
fn rebuild_entry_with_value(entry: &SyntaxNode, value: &impl crate::AsYaml) -> SyntaxNode {
let entry_children: Vec<_> = entry.children_with_tokens().collect();
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
let mut value_inserted = false;
let mut trailing_text: Option<String> = None;
let mut after_dash = false;
for entry_child in entry_children {
match &entry_child {
rowan::NodeOrToken::Node(n)
if matches!(
n.kind(),
SyntaxKind::SCALAR
| SyntaxKind::MAPPING
| SyntaxKind::SEQUENCE
| SyntaxKind::ALIAS
| SyntaxKind::TAGGED_NODE
) =>
{
trailing_text = trailing_newline_indent(n);
if !value_inserted {
if after_dash {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
value.build_content(&mut builder, 0, false);
value_inserted = true;
}
after_dash = false;
}
rowan::NodeOrToken::Node(n) => {
crate::yaml::copy_node_to_builder(&mut builder, n);
after_dash = false;
}
rowan::NodeOrToken::Token(t) => {
builder.token(t.kind().into(), t.text());
after_dash = t.kind() == SyntaxKind::DASH;
}
}
}
if let Some(trailing) = trailing_text {
if let Some(indent_part) = trailing.strip_prefix('\n') {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
if !indent_part.is_empty() {
builder.token(SyntaxKind::INDENT.into(), indent_part);
}
}
}
builder.finish_node();
SyntaxNode::new_root_mut(builder.finish())
}
fn strip_trailing_blank_tokens(entry: &SyntaxNode) {
let children: Vec<_> = entry.children_with_tokens().collect();
let blanks = children
.iter()
.rev()
.take_while(|c| {
c.as_token().is_some_and(|t| {
matches!(
t.kind(),
SyntaxKind::NEWLINE | SyntaxKind::INDENT | SyntaxKind::WHITESPACE
)
})
})
.count();
if blanks > 0 {
let total = children.len();
entry.splice_children((total - blanks)..total, vec![]);
}
}
fn mapping_entry_is_last_in_mapping(sequence: &SyntaxNode) -> bool {
let Some(value) = sequence.parent() else {
return false;
};
if value.kind() != SyntaxKind::VALUE {
return false;
}
let Some(entry) = value.parent() else {
return false;
};
if entry.kind() != SyntaxKind::MAPPING_ENTRY {
return false;
}
let Some(parent_mapping) = entry.parent() else {
return false;
};
if parent_mapping.kind() != SyntaxKind::MAPPING {
return false;
}
let mut seen_self = false;
for child in parent_mapping.children() {
if child == entry {
seen_self = true;
continue;
}
if seen_self && child.kind() == SyntaxKind::MAPPING_ENTRY {
return false;
}
}
true
}
use crate::yaml::collapse_empty_child_collection_in_parent as collapse_empty_child_sequence_in_parent;
impl Sequence {
pub(crate) fn items(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
self.0.children().filter_map(|child| {
if child.kind() == SyntaxKind::SEQUENCE_ENTRY {
child.children().find(|n| {
matches!(
n.kind(),
SyntaxKind::SCALAR
| SyntaxKind::MAPPING
| SyntaxKind::SEQUENCE
| SyntaxKind::ALIAS
| SyntaxKind::TAGGED_NODE
)
})
} else {
None
}
})
}
pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
self.items()
.filter_map(crate::as_yaml::YamlNode::from_syntax)
}
pub fn len(&self) -> usize {
self.items().count()
}
pub fn is_empty(&self) -> bool {
self.items().next().is_none()
}
pub fn get(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
self.items()
.nth(index)
.and_then(crate::as_yaml::YamlNode::from_syntax)
}
pub fn first(&self) -> Option<crate::as_yaml::YamlNode> {
self.get(0)
}
pub fn last(&self) -> Option<crate::as_yaml::YamlNode> {
let len = self.len();
if len == 0 {
None
} else {
self.get(len - 1)
}
}
}
impl Sequence {
pub fn new() -> Self {
Self::new_pending_block()
}
pub fn new_flow() -> Self {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE.into());
builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
builder.finish_node();
Sequence(SyntaxNode::new_root_mut(builder.finish()))
}
pub fn new_pending_block() -> Self {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE.into());
builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
builder.token(SyntaxKind::WHITESPACE.into(), "");
builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
builder.finish_node();
Sequence(SyntaxNode::new_root_mut(builder.finish()))
}
pub(crate) fn is_block_placeholder(&self) -> bool {
!self
.0
.children()
.any(|c| c.kind() == SyntaxKind::SEQUENCE_ENTRY)
&& self
.0
.children_with_tokens()
.filter_map(|c| c.into_token())
.any(|t| t.kind() == SyntaxKind::WHITESPACE && t.text().is_empty())
}
fn detect_indentation(&self) -> String {
if let Some(ind) = self.0.children_with_tokens().find_map(|child| {
child
.into_token()
.filter(|t| t.kind() == SyntaxKind::INDENT)
.map(|t| t.text().to_string())
}) {
return ind;
}
if let Some(indent) = self
.0
.children()
.filter(|c| c.kind() == SyntaxKind::SEQUENCE_ENTRY)
.find_map(|entry| {
let tokens: Vec<_> = entry.children_with_tokens().collect();
tokens.windows(2).find_map(|pair| {
let ws = pair[0].as_token()?;
let dash = pair[1].as_token()?;
if ws.kind() == SyntaxKind::WHITESPACE && dash.kind() == SyntaxKind::DASH {
Some(ws.text().to_string())
} else {
None
}
})
})
{
return indent;
}
if let Some(parent) = self.0.parent().filter(|p| p.kind() == SyntaxKind::VALUE) {
for child in parent.children_with_tokens() {
match &child {
rowan::NodeOrToken::Node(n) if n == &self.0 => break,
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::INDENT => {
return t.text().to_string();
}
_ => {}
}
}
}
match self.0.parent() {
None => return String::new(),
Some(parent) if parent.kind() == SyntaxKind::DOCUMENT => return String::new(),
Some(_) => {}
}
" ".to_string()
}
pub(crate) fn extend_with(&self, value: impl crate::AsYaml + Clone, count: usize) {
if count == 0 {
return;
}
self.push(value.clone());
let Some(remaining) = count.checked_sub(1).filter(|n| *n > 0) else {
return;
};
if self.is_flow_style() {
for _ in 0..remaining {
self.push(value.clone());
}
return;
}
let indentation = self.detect_indentation();
let mut inserts: Vec<rowan::NodeOrToken<SyntaxNode, _>> = Vec::new();
for _ in 0..remaining {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
builder.token(SyntaxKind::DASH.into(), "-");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
let ends_with_newline = value.clone().build_content(&mut builder, 0, false);
if !ends_with_newline {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node();
inserts.push(fresh_token(SyntaxKind::INDENT, &indentation).into());
inserts.push(SyntaxNode::new_root_mut(builder.finish()).into());
}
let end = self.0.green().children().len();
self.0.splice_children(end..end, inserts);
}
pub fn push(&self, value: impl crate::AsYaml) {
if self.is_block_placeholder() && !must_render_flow(&self.0) {
crate::yaml::convert_placeholder_to_block(&self.0);
}
if self.is_flow_style() {
self.insert_flow(usize::MAX, value);
return;
}
let indentation = self.detect_indentation();
let indent_token = fresh_token(SyntaxKind::INDENT, &indentation);
let green = self.0.green();
let mut tail = green.children();
let child_count = tail.len();
let mut last_entry_index = None;
let mut last_entry_has_newline = true; let mut insert_pos = child_count;
let mut idx = child_count;
while idx > 0 {
idx -= 1;
match tail.next_back() {
Some(rowan::NodeOrToken::Node(n))
if n.kind() == SyntaxKind::SEQUENCE_ENTRY.into() =>
{
last_entry_has_newline = n
.children()
.next_back()
.and_then(|c| c.into_token())
.is_some_and(|t| t.kind() == SyntaxKind::NEWLINE.into());
last_entry_index = Some(idx);
if insert_pos == child_count {
insert_pos = idx + 1;
}
break;
}
Some(rowan::NodeOrToken::Token(tok)) if tok.kind() == SyntaxKind::INDENT.into() => {
insert_pos = idx + 1;
}
Some(rowan::NodeOrToken::Token(tok))
if tok.kind() == SyntaxKind::NEWLINE.into() =>
{
insert_pos = idx;
}
_ => break,
}
}
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
builder.token(SyntaxKind::DASH.into(), "-");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
let value_ends_with_newline = value.build_content(&mut builder, 0, false);
if last_entry_has_newline && !value_ends_with_newline {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node(); let new_entry = SyntaxNode::new_root_mut(builder.finish());
if last_entry_index.is_some() && !last_entry_has_newline {
if let Some(node) = self
.0
.last_child()
.filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
{
let entry_children_count = node.green().children().len();
let nl = fresh_token(SyntaxKind::NEWLINE, "\n");
node.splice_children(entry_children_count..entry_children_count, vec![nl.into()]);
}
}
let entry_is_first = last_entry_index.is_none();
let parent_supplies_indent = entry_is_first && parent_value_has_leading_indent(&self.0);
let mut inserts: Vec<rowan::NodeOrToken<SyntaxNode, _>> = Vec::new();
if !parent_supplies_indent {
inserts.push(indent_token.into());
}
inserts.push(new_entry.clone().into());
self.0.splice_children(insert_pos..insert_pos, inserts);
if entry_is_first {
crate::yaml::detach_empty_collection_placeholder_newline(&self.0, &new_entry);
}
}
fn insert_flow(&self, index: usize, value: impl crate::AsYaml) {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
value.build_content(&mut builder, 0, false);
builder.finish_node();
let new_entry = SyntaxNode::new_root_mut(builder.finish());
let children: Vec<_> = self.0.children_with_tokens().collect();
let entry_positions = entry_indices(&self.0, SyntaxKind::SEQUENCE_ENTRY);
let Some(right_bracket_pos) = children.iter().position(|c| {
c.as_token()
.is_some_and(|t| t.kind() == SyntaxKind::RIGHT_BRACKET)
}) else {
debug_assert!(false, "flow SEQUENCE missing RIGHT_BRACKET");
return;
};
let comma = fresh_token(SyntaxKind::COMMA, ",");
let sep_ws = fresh_token(SyntaxKind::WHITESPACE, " ");
if index >= entry_positions.len() {
if let Some(&last_pos) = entry_positions.last() {
let last_entry = children[last_pos].as_node().expect("SEQUENCE_ENTRY");
let ends_with_comma = last_entry
.last_token()
.is_some_and(|t| t.kind() == SyntaxKind::COMMA);
if !ends_with_comma {
append_children(last_entry, vec![comma.into(), sep_ws.into()]);
}
}
self.0
.splice_children(right_bracket_pos..right_bracket_pos, vec![new_entry.into()]);
return;
}
let target_pos = entry_positions[index];
append_children(&new_entry, vec![comma.into(), sep_ws.into()]);
self.0
.splice_children(target_pos..target_pos, vec![new_entry.into()]);
}
pub fn insert(&self, index: usize, value: impl crate::AsYaml) {
if self.is_block_placeholder() && !must_render_flow(&self.0) {
crate::yaml::convert_placeholder_to_block(&self.0);
}
if self.is_flow_style() {
self.insert_flow(index, value);
return;
}
let indentation = self.detect_indentation();
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
builder.token(SyntaxKind::DASH.into(), "-");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
let value_ends_with_newline = value.build_content(&mut builder, 0, false);
if !value_ends_with_newline {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node();
let new_entry = SyntaxNode::new_root_mut(builder.finish());
let indent_token = fresh_token(SyntaxKind::INDENT, &indentation);
let children: Vec<_> = self.0.children_with_tokens().collect();
let mut item_count = 0;
let mut target_entry_pos = children.len();
for (i, child) in children.iter().enumerate() {
let Some(node) = child.as_node() else {
continue;
};
if node.kind() != SyntaxKind::SEQUENCE_ENTRY {
continue;
}
if item_count == index {
target_entry_pos = i;
break;
}
item_count += 1;
}
let insert_at = if target_entry_pos > 0
&& children
.get(target_entry_pos - 1)
.and_then(|c| c.as_token())
.is_some_and(|t| t.kind() == SyntaxKind::INDENT)
{
target_entry_pos - 1
} else {
target_entry_pos
};
if target_entry_pos == children.len() {
if let Some(prev_entry) = children.iter().rev().find_map(|c| {
c.as_node()
.filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
}) {
ensure_trailing_newline(prev_entry);
}
}
let inserting_at_head = target_entry_pos < children.len()
&& children[..target_entry_pos].iter().all(|c| {
c.as_node()
.map_or(true, |n| n.kind() != SyntaxKind::SEQUENCE_ENTRY)
});
if inserting_at_head && parent_value_has_leading_indent(&self.0) {
let old_first_indent = fresh_token(SyntaxKind::INDENT, &indentation);
self.0.splice_children(
insert_at..insert_at,
vec![new_entry.into(), old_first_indent.into()],
);
return;
}
self.0.splice_children(
insert_at..insert_at,
vec![indent_token.into(), new_entry.into()],
);
}
pub fn set(&self, index: usize, value: impl crate::AsYaml) -> bool {
let children: Vec<_> = self.0.children_with_tokens().collect();
let Some(i) = nth_entry_index(&self.0, SyntaxKind::SEQUENCE_ENTRY, index) else {
return false;
};
let node = children[i].as_node().expect("entry index names a node");
let new_entry = rebuild_entry_with_value(node, &value);
self.0.splice_children(i..i + 1, vec![new_entry.into()]);
true
}
fn detach_orphaned_indent(
&self,
children: &[rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<Lang>>],
i: usize,
) {
if self.is_flow_style() {
return;
}
let (neighbour, range) = if i > 0 {
(children.get(i - 1), (i - 1)..i)
} else {
(children.get(i + 1), i..(i + 1))
};
let is_indent =
neighbour.is_some_and(|c| c.as_token().is_some_and(|t| t.kind() == SyntaxKind::INDENT));
if is_indent {
self.0.splice_children(range, vec![]);
}
}
pub fn remove(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
let removed_value = self.get(index);
let children: Vec<_> = self.0.children_with_tokens().collect();
let i = children
.iter()
.enumerate()
.filter(|(_, c)| {
c.as_node()
.is_some_and(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
})
.map(|(i, _)| i)
.nth(index)?;
let is_last = !children.iter().skip(i + 1).any(|c| {
c.as_node()
.is_some_and(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
});
self.0.splice_children(i..(i + 1), vec![]);
self.detach_orphaned_indent(&children, i);
if !self.is_flow_style() && is_last && i > 0 && mapping_entry_is_last_in_mapping(&self.0) {
if let Some(prev_entry) = children[..i].iter().rev().find_map(|c| {
c.as_node()
.filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
}) {
strip_trailing_blank_tokens(prev_entry);
}
}
if self.is_empty() {
collapse_empty_child_sequence_in_parent(&self.0);
}
removed_value
}
pub fn is_flow_style(&self) -> bool {
has_child_token(&self.0, |k| k == SyntaxKind::LEFT_BRACKET)
}
pub fn pop(&self) -> Option<crate::as_yaml::YamlNode> {
let len = self.len();
if len == 0 {
return None;
}
let removed = self.remove(len - 1);
debug_assert_eq!(
self.len(),
len - 1,
"pop() invariant: remove() did not reduce length"
);
removed
}
pub fn clear(&self) {
let initial_len = self.len();
for _ in 0..initial_len {
let current_len = self.len();
if current_len == 0 {
break;
}
let removed = self.remove(0);
debug_assert!(
removed.is_some(),
"clear() invariant: remove(0) returned None"
);
debug_assert_eq!(
self.len(),
current_len - 1,
"clear() invariant: remove(0) did not reduce length"
);
}
}
crate::nodes::ast_node_spans!("sequence");
}
impl Default for Sequence {
fn default() -> Self {
Self::new()
}
}
impl<'a> IntoIterator for &'a Sequence {
type Item = crate::as_yaml::YamlNode;
type IntoIter = Box<dyn Iterator<Item = crate::as_yaml::YamlNode> + 'a>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.values())
}
}
impl AsYaml for Sequence {
fn as_node(&self) -> Option<&SyntaxNode> {
Some(&self.0)
}
fn kind(&self) -> YamlKind {
YamlKind::Sequence
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
_flow_context: bool,
) -> bool {
builder.start_node(SyntaxKind::SEQUENCE.into());
crate::as_yaml::copy_node_content_with_indent(builder, &self.0, indent);
builder.finish_node();
self.0
.last_token()
.is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
}
fn is_inline(&self) -> bool {
ValueNode::is_inline(self)
}
}
#[cfg(test)]
mod tests {
use crate::yaml::YamlFile;
use std::str::FromStr;
#[test]
fn pending_block_sequence_round_trips_and_becomes_block() {
use crate::yaml::Sequence;
use crate::Document;
use std::str::FromStr;
let doc = Document::from_str("a: 1\n").unwrap();
doc.as_mapping()
.unwrap()
.set("s", Sequence::new_pending_block());
let text = doc.to_string();
assert_eq!(text, "a: 1\ns: []\n");
let reparsed = Document::from_str(&text).unwrap();
let value = reparsed.as_mapping().and_then(|m| m.get("s")).unwrap();
assert!(
value.as_sequence().is_some(),
"empty sequence must survive as a sequence"
);
let doc = Document::from_str("a: 1\n").unwrap();
let root = doc.as_mapping().unwrap();
root.set("s", Sequence::new_pending_block());
let nested = root.get("s").unwrap();
nested.as_sequence().unwrap().push("x");
assert_eq!(doc.to_string(), "a: 1\ns:\n - x\n");
}
#[test]
fn parsed_flow_sequence_stays_flow_when_filled() {
use crate::yaml::Sequence;
use crate::Document;
use std::str::FromStr;
let doc = Document::from_str("a: 1\n").unwrap();
let root = doc.as_mapping().unwrap();
root.set("s", Sequence::new_flow());
let nested = root.get("s").unwrap();
nested.as_sequence().unwrap().push("x");
assert_eq!(doc.to_string(), "a: 1\ns: [x]\n");
}
#[test]
fn set_path_builds_block_sequence_intermediates() {
use crate::path::YamlPath;
use crate::Document;
use std::str::FromStr;
let doc = Document::from_str("name: test\n").unwrap();
doc.try_set_path("a.b[0]", "v").unwrap();
assert_eq!(doc.to_string(), "name: test\na:\n b:\n - v\n");
}
#[test]
fn extend_with_matches_a_push_loop() {
use crate::yaml::Document;
use std::str::FromStr;
for src in [
"s:\n - a\n",
"s: []\n",
"s: [1]\n",
"s:\n - x\n - y\n",
"s:\n - deep\n",
"s:\n - a\nafter: kept\n",
] {
for count in 0..6usize {
let pushed = {
let doc = Document::from_str(src).unwrap();
let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
for _ in 0..count {
seq.push(crate::scalar::ScalarValue::null());
}
doc.to_string()
};
let extended = {
let doc = Document::from_str(src).unwrap();
let seq = doc.as_mapping().unwrap().get_sequence("s").unwrap();
seq.extend_with(crate::scalar::ScalarValue::null(), count);
doc.to_string()
};
assert_eq!(extended, pushed, "src={src:?} count={count}");
}
}
}
#[test]
fn test_push_into_empty_flow_sequence_reshapes_to_block() {
use crate::path::YamlPath;
use crate::Document;
let doc = Document::from_str("seq: []").unwrap();
let seq = doc
.try_get_path("seq")
.unwrap()
.as_sequence()
.unwrap()
.clone();
seq.push("item1");
assert_eq!(doc.to_string(), "seq: [item1]");
}
#[test]
fn test_push_into_empty_flow_sequence_nested_indent() {
use crate::path::YamlPath;
use crate::Document;
let doc = Document::from_str("a:\n seq: []\n").unwrap();
let seq = doc
.try_get_path("a.seq")
.unwrap()
.as_sequence()
.unwrap()
.clone();
seq.push("item1");
assert_eq!(doc.to_string(), "a:\n seq: [item1]\n");
}
#[test]
fn test_push_deeply_nested_block_sequence_inherits_indent() {
use crate::path::YamlPath;
use crate::Document;
let doc = Document::from_str("a:\n b:\n c:\n - existing\n").unwrap();
let seq = doc
.try_get_path("a.b.c")
.unwrap()
.as_sequence()
.unwrap()
.clone();
seq.push("new_item");
assert_eq!(
doc.to_string(),
"a:\n b:\n c:\n - existing\n - new_item\n"
);
}
#[test]
fn test_push_into_empty_sequence_under_mapping_placeholder() {
use crate::{Document, Sequence};
let doc = Document::from_str("existing: value\n").unwrap();
let mapping = doc.as_mapping().unwrap();
mapping.set("items", Sequence::new());
let items = mapping.get_sequence("items").unwrap();
items.push("apple");
items.push("banana");
assert_eq!(
doc.to_string(),
"existing: value\nitems:\n - apple\n - banana\n"
);
}
#[test]
fn test_implicit_null_item_shares_indexes_with_set_and_remove() {
use crate::Document;
let doc = Document::from_str("- a\n- \n- c\n").unwrap();
let seq = doc.as_sequence().unwrap();
assert_eq!(seq.len(), 3);
assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "");
assert_eq!(seq.get(2).unwrap().as_scalar().unwrap().as_string(), "c");
assert!(seq.set(1, "x"));
assert_eq!(doc.to_string(), "- a\n- x\n- c\n");
let doc = Document::from_str("- a\n- \n- c\n").unwrap();
let seq = doc.as_sequence().unwrap();
let removed = seq.remove(1);
assert_eq!(
removed.and_then(|n| n.as_scalar().map(|s| s.as_string())),
Some(String::new())
);
assert_eq!(seq.len(), 2);
assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "c");
let doc = Document::from_str("- a\n-\n- c\n").unwrap();
let seq = doc.as_sequence().unwrap();
assert_eq!(seq.len(), 3);
assert!(seq.set(1, "x"));
assert_eq!(doc.to_string(), "- a\n- x\n- c\n");
}
#[test]
fn test_flow_sequence_implicit_null_shares_indexes() {
use crate::debug::{roundtrip_ok, validate_tree};
use crate::{AsYaml, Document};
let doc = Document::from_str("[a, , c]").unwrap();
let seq = doc.as_sequence().unwrap();
assert_eq!(seq.len(), 3);
assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "");
assert_eq!(seq.get(2).unwrap().as_scalar().unwrap().as_string(), "c");
validate_tree(doc.as_node().unwrap()).unwrap();
roundtrip_ok(doc.as_node().unwrap()).unwrap();
assert!(seq.set(1, "x"));
assert_eq!(doc.to_string(), "[a, x, c]");
let doc = Document::from_str("[a, , c]").unwrap();
let seq = doc.as_sequence().unwrap();
let removed = seq.remove(1);
assert_eq!(
removed.and_then(|n| n.as_scalar().map(|s| s.as_string())),
Some(String::new())
);
assert_eq!(seq.len(), 2);
assert_eq!(doc.to_string(), "[a, c]");
}
#[test]
fn test_flow_sequence_trailing_comma_is_not_extra_entry() {
use crate::Document;
let doc = Document::from_str("[a, b,]").unwrap();
let seq = doc.as_sequence().unwrap();
assert_eq!(seq.len(), 2);
assert_eq!(seq.get(0).unwrap().as_scalar().unwrap().as_string(), "a");
assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "b");
assert_eq!(doc.to_string(), "[a, b,]");
}
#[test]
fn test_flow_sequence_only_nulls() {
use crate::debug::{roundtrip_ok, validate_tree};
use crate::{AsYaml, Document};
let doc = Document::from_str("[,]").unwrap();
assert_eq!(doc.as_sequence().unwrap().len(), 1);
validate_tree(doc.as_node().unwrap()).unwrap();
roundtrip_ok(doc.as_node().unwrap()).unwrap();
let doc = Document::from_str("[,,]").unwrap();
assert_eq!(doc.as_sequence().unwrap().len(), 2);
validate_tree(doc.as_node().unwrap()).unwrap();
roundtrip_ok(doc.as_node().unwrap()).unwrap();
}
#[test]
fn test_sequence_items_tagged_node() {
let yaml = "- !custom foo\n- !custom bar\n- plain\n";
let parsed = YamlFile::from_str(yaml).unwrap();
let doc = parsed.document().unwrap();
let seq = doc.as_sequence().unwrap();
assert_eq!(
seq.items().count(),
3,
"Tagged scalars should be included in items()"
);
assert_eq!(
seq.values().count(),
3,
"Tagged scalars should be included in values()"
);
}
#[test]
fn test_sequence_set_tagged_node() {
let yaml = "- !custom foo\n- bar\n";
let parsed = YamlFile::from_str(yaml).unwrap();
let doc = parsed.document().unwrap();
let seq = doc.as_sequence().unwrap();
seq.set(0, "replaced");
let values: Vec<_> = seq.values().collect();
assert_eq!(values.len(), 2);
assert_eq!(
values[0].as_scalar().map(|s| s.as_string()),
Some("replaced".to_string())
);
assert_eq!(
values[1].as_scalar().map(|s| s.as_string()),
Some("bar".to_string())
);
}
#[test]
fn test_sequence_set_alias() {
let yaml = "colors:\n - *red\n - keep\n";
let parsed = YamlFile::from_str(yaml).unwrap();
let doc = parsed.document().unwrap();
let mapping = doc.as_mapping().unwrap();
let colors_node = mapping.get("colors").unwrap();
let seq = colors_node.as_sequence().unwrap();
assert!(seq.set(0, "blue"));
assert_eq!(parsed.to_string(), "colors:\n - blue\n - keep\n");
let first = seq.get(0).unwrap();
assert_eq!(
first.as_scalar().map(|s| s.as_string()),
Some("blue".to_string())
);
}
#[test]
fn test_sequence_operations() {
let yaml = "- item1\n- item2";
let parsed = YamlFile::from_str(yaml).unwrap();
let doc = parsed.document().expect("expected a document");
let seq = doc.as_sequence().expect("expected a sequence");
seq.push("item3");
let values: Vec<_> = seq.values().collect();
assert_eq!(values.len(), 3);
assert_eq!(
values[2].as_scalar().map(|s| s.as_string()),
Some("item3".to_string())
);
seq.insert(0, "item0");
let values: Vec<_> = seq.values().collect();
assert_eq!(values.len(), 4);
assert_eq!(
values[0].as_scalar().map(|s| s.as_string()),
Some("item0".to_string())
);
}
#[test]
fn test_sequence_into_iterator() {
use crate::Document;
let text = "items:\n - apple\n - banana\n - cherry";
let doc = Document::from_str(text).unwrap();
let mapping = doc.as_mapping().unwrap();
let sequence = mapping.get_sequence("items").unwrap();
let mut items = Vec::new();
for value in &sequence {
if let Some(scalar) = value.as_scalar() {
items.push(scalar.to_string());
}
}
assert_eq!(items.len(), 3);
assert_eq!(items[0], "apple");
assert_eq!(items[1], "banana");
assert_eq!(items[2], "cherry");
}
#[test]
fn test_sequence_into_iterator_count() {
use crate::Document;
let text = "[1, 2, 3, 4, 5]";
let doc = Document::from_str(text).unwrap();
let sequence = doc.as_sequence().unwrap();
let count = (&sequence).into_iter().count();
assert_eq!(count, 5);
}
#[test]
fn test_sequence_iterator_map() {
use crate::Document;
let text = "numbers: [1, 2, 3]";
let doc = Document::from_str(text).unwrap();
let mapping = doc.as_mapping().unwrap();
let sequence = mapping.get_sequence("numbers").unwrap();
let strings: Vec<_> = (&sequence)
.into_iter()
.filter_map(|v| v.as_scalar().map(|s| s.to_string()))
.collect();
assert_eq!(strings, vec!["1", "2", "3"]);
}
#[test]
fn test_empty_sequence_iterator() {
use crate::Document;
let text = "items: []";
let doc = Document::from_str(text).unwrap();
let mapping = doc.as_mapping().unwrap();
let sequence = mapping.get_sequence("items").unwrap();
let count = (&sequence).into_iter().count();
assert_eq!(count, 0);
}
#[test]
fn test_sequence_push_single() {
use crate::Document;
let original = r#"team:
- Alice
- Bob"#;
let doc = Document::from_str(original).unwrap();
let mapping = doc.as_mapping().unwrap();
let team = mapping.get_sequence("team").unwrap();
team.push("Charlie");
let expected = r#"team:
- Alice
- Bob
- Charlie"#;
assert_eq!(doc.to_string(), expected);
}
#[test]
fn test_sequence_push_multiple() {
use crate::Document;
let original = r#"team:
- Alice
- Bob"#;
let doc = Document::from_str(original).unwrap();
let mapping = doc.as_mapping().unwrap();
let team = mapping.get_sequence("team").unwrap();
team.push("Charlie");
team.push("Diana");
let expected = r#"team:
- Alice
- Bob
- Charlie
- Diana"#;
assert_eq!(doc.to_string(), expected);
}
#[test]
fn test_sequence_set_item() {
use crate::Document;
let original = r#"team:
- Alice
- Bob
- Charlie"#;
let doc = Document::from_str(original).unwrap();
let mapping = doc.as_mapping().unwrap();
let team = mapping.get_sequence("team").unwrap();
team.set(1, "Robert");
let expected = r#"team:
- Alice
- Robert
- Charlie"#;
assert_eq!(doc.to_string(), expected);
}
#[test]
fn test_multiple_sequences() {
use crate::Document;
let original = r#"team:
- Alice
- Bob
scores:
- 95
- 87"#;
let doc = Document::from_str(original).unwrap();
let mapping = doc.as_mapping().unwrap();
let team = mapping.get_sequence("team").unwrap();
team.push("Charlie");
let scores = mapping.get_sequence("scores").unwrap();
scores.push(92);
scores.set(0, 100);
let expected = r#"team:
- Alice
- Bob
- Charlie
scores:
- 100
- 87
- 92"#;
assert_eq!(doc.to_string(), expected);
}
#[test]
fn test_nested_structure_with_sequences() {
use crate::Document;
let original = r#"config:
enabled: true
retries: 3
servers:
- host1
- host2"#;
let doc = Document::from_str(original).unwrap();
let mapping = doc.as_mapping().unwrap();
let config = mapping.get_mapping("config").unwrap();
config.set("enabled", false);
config.set("retries", 5);
let servers = config.get_sequence("servers").unwrap();
servers.push("host3");
servers.set(0, "primary-host");
let expected = r#"config:
enabled: false
retries: 5
servers:
- primary-host
- host2
- host3"#;
assert_eq!(doc.to_string(), expected);
}
#[test]
fn test_sequence_len_and_is_empty() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
assert!(!seq.is_empty());
let empty_doc = Document::from_str("items: []").unwrap();
let empty_mapping = empty_doc.as_mapping().unwrap();
let empty_seq = empty_mapping.get_sequence("items").unwrap();
assert_eq!(empty_seq.len(), 0);
assert!(empty_seq.is_empty());
}
#[test]
fn test_sequence_get() {
use crate::Document;
let doc = Document::from_str("items:\n - first\n - second\n - third").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.get(0).unwrap().to_string(), "first");
assert_eq!(seq.get(1).unwrap().to_string(), "second");
assert_eq!(seq.get(2).unwrap().to_string(), "third");
assert!(seq.get(3).is_none());
}
#[test]
fn test_sequence_first_and_last() {
use crate::Document;
let doc = Document::from_str("items:\n - first\n - middle\n - last").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.first().unwrap().to_string(), "first");
assert_eq!(seq.last().unwrap().to_string(), "last");
let empty_doc = Document::from_str("items: []").unwrap();
let empty_mapping = empty_doc.as_mapping().unwrap();
let empty_seq = empty_mapping.get_sequence("items").unwrap();
assert!(empty_seq.first().is_none());
assert!(empty_seq.last().is_none());
}
#[test]
fn test_sequence_values_iterator() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_sequence_pop() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "c");
assert_eq!(seq.len(), 2);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "b");
assert_eq!(seq.len(), 1);
let expected = "items:\n - a";
assert_eq!(doc.to_string().trim_end(), expected);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "a");
assert_eq!(seq.len(), 0);
assert!(seq.pop().is_none());
}
#[test]
fn test_sequence_clear() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
seq.clear();
assert_eq!(seq.len(), 0);
assert!(seq.is_empty());
}
#[test]
fn test_sequence_get_with_nested_values() {
use crate::Document;
let doc = Document::from_str(
r#"items:
- simple
- {key: value}
- [nested, list]"#,
)
.unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
assert!(seq.get(0).unwrap().is_scalar());
assert!(seq.get(1).unwrap().is_mapping());
assert!(seq.get(2).unwrap().is_sequence());
}
#[test]
fn test_flow_sequence_len_and_is_empty() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
assert!(!seq.is_empty());
let empty_doc = Document::from_str("items: []").unwrap();
let empty_mapping = empty_doc.as_mapping().unwrap();
let empty_seq = empty_mapping.get_sequence("items").unwrap();
assert_eq!(empty_seq.len(), 0);
assert!(empty_seq.is_empty());
}
#[test]
fn test_flow_sequence_get() {
use crate::Document;
let doc = Document::from_str("items: [first, second, third]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.get(0).unwrap().to_string(), "first");
assert_eq!(seq.get(1).unwrap().to_string(), "second");
assert_eq!(seq.get(2).unwrap().to_string(), "third");
assert!(seq.get(3).is_none());
}
#[test]
fn test_flow_sequence_first_and_last() {
use crate::Document;
let doc = Document::from_str("items: [first, middle, last]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.first().unwrap().to_string(), "first");
assert_eq!(seq.last().unwrap().to_string(), "last");
}
#[test]
fn test_flow_sequence_values_iterator() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_flow_sequence_remove_middle() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(1);
assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "c"]);
}
#[test]
fn test_flow_sequence_remove_first() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(0);
assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["b", "c"]);
}
#[test]
fn test_flow_sequence_remove_last() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(2);
assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b"]);
}
#[test]
fn test_flow_sequence_pop() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "c");
assert_eq!(seq.len(), 2);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "b");
assert_eq!(seq.len(), 1);
let popped = seq.pop().unwrap();
assert_eq!(popped.to_string(), "a");
assert_eq!(seq.len(), 0);
assert!(seq.pop().is_none());
}
#[test]
fn test_flow_sequence_clear() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
seq.clear();
assert_eq!(seq.len(), 0);
assert!(seq.is_empty());
}
#[test]
fn test_flow_sequence_with_whitespace() {
use crate::Document;
let doc = Document::from_str("items: [ a , b , c ]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_block_sequence_remove_middle() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(1);
assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "c"]);
}
#[test]
fn test_block_sequence_remove_first() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(0);
assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["b", "c"]);
}
#[test]
fn test_block_sequence_remove_last() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
let removed = seq.remove(2);
assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
assert_eq!(seq.len(), 2);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b"]);
}
#[test]
fn test_single_item_block_sequence_remove() {
use crate::Document;
let doc = Document::from_str("items:\n - only").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 1);
let removed = seq.remove(0);
assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
assert_eq!(seq.len(), 0);
}
#[test]
fn test_single_item_flow_sequence_remove() {
use crate::Document;
let doc = Document::from_str("items: [only]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 1);
let removed = seq.remove(0);
assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
assert_eq!(seq.len(), 0);
}
#[test]
fn test_flow_sequence_push() {
use crate::Document;
let doc = Document::from_str("items: [a, b]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 2);
seq.push("c");
assert_eq!(seq.len(), 3);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_flow_sequence_push_multiple() {
use crate::Document;
let doc = Document::from_str("items: [a]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.push("b");
seq.push("c");
seq.push("d");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c", "d"]);
}
#[test]
fn test_flow_sequence_set_item() {
use crate::Document;
let doc = Document::from_str("items: [a, b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.set(1, "modified");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "modified", "c"]);
}
#[test]
fn test_flow_sequence_insert_beginning() {
use crate::Document;
let doc = Document::from_str("items: [b, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(0, "a");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_flow_sequence_insert_middle() {
use crate::Document;
let doc = Document::from_str("items: [a, c]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(1, "b");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_flow_sequence_insert_end() {
use crate::Document;
let doc = Document::from_str("items: [a, b]").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(2, "c");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_block_sequence_push() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
assert_eq!(seq.len(), 2);
seq.push("c");
assert_eq!(seq.len(), 3);
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_block_sequence_set_item() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.set(1, "modified");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "modified", "c"]);
}
#[test]
fn test_block_sequence_insert_beginning() {
use crate::Document;
let doc = Document::from_str("items:\n - b\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(0, "a");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_block_sequence_insert_middle() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - c").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(1, "b");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_block_sequence_insert_end() {
use crate::Document;
let doc = Document::from_str("items:\n - a\n - b").unwrap();
let mapping = doc.as_mapping().unwrap();
let seq = mapping.get_sequence("items").unwrap();
seq.insert(2, "c");
let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
assert_eq!(values, vec!["a", "b", "c"]);
}
#[test]
fn test_sequence_get_node() {
let doc = YamlFile::from_str("items:\n - alpha\n - beta\n - gamma")
.unwrap()
.document()
.unwrap();
let seq = doc.as_mapping().unwrap().get_sequence("items").unwrap();
assert_eq!(seq.len(), 3);
assert!(seq.get(0).is_some());
assert!(seq.get(1).is_some());
assert!(seq.get(2).is_some());
assert!(seq.get(3).is_none());
assert_eq!(
seq.get(0).unwrap().as_scalar().unwrap().as_string(),
"alpha"
);
assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "beta");
assert_eq!(
seq.get(2).unwrap().as_scalar().unwrap().as_string(),
"gamma"
);
}
#[test]
fn test_sequence_set_with_nested_mapping() {
use crate::path::YamlPath;
use crate::Document;
let yaml_str = "items:\n - name: first\n value: 1\n - name: second\n value: 2\n";
let doc = Document::from_str(yaml_str).unwrap();
let items_node = doc.try_get_path("items").unwrap();
let items = items_node.as_sequence().unwrap();
assert_eq!(items.len(), 2);
let first = items.get(0).unwrap();
assert!(first.is_mapping());
items.set(0, "replaced");
assert_eq!(
doc.to_string(),
"items:\n - replaced\n - name: second\n value: 2\n"
);
}
}