use crate::{
as_yaml::{AsYaml, YamlNode},
lex::SyntaxKind,
yaml::{Document, SyntaxNode, YamlFile},
};
use rowan::{ast::AstNode, Checkpoint, GreenNodeBuilder};
pub struct YamlBuilder {
file: YamlFile,
}
impl YamlBuilder {
pub fn scalar(value: impl AsYaml) -> Self {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
builder.start_node(SyntaxKind::DOCUMENT.into());
if !value.build_content(&mut builder, 0, false) {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node();
builder.finish_node();
let green = builder.finish();
YamlBuilder {
file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
}
}
pub fn alias(name: impl AsRef<str>) -> Self {
Self::scalar(crate::yaml::Alias::new(name))
}
pub fn sequence() -> SequenceBuilder {
SequenceBuilder::new()
}
pub fn mapping() -> MappingBuilder {
MappingBuilder::new()
}
pub fn build(self) -> YamlFile {
self.file
}
}
impl Default for YamlBuilder {
fn default() -> Self {
Self::mapping().build()
}
}
pub struct SequenceBuilder {
builder: GreenNodeBuilder<'static>,
indent: usize,
start: Option<Checkpoint>,
}
fn begin_entry(
builder: &mut GreenNodeBuilder<'static>,
indent: usize,
start: &mut Option<Checkpoint>,
) {
if start.is_some() {
if indent > 0 {
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
}
return;
}
if indent > 0 {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
}
*start = Some(builder.checkpoint());
}
fn finish_collection(
builder: &mut GreenNodeBuilder<'static>,
kind: SyntaxKind,
start: Option<Checkpoint>,
) {
let Some(start) = start else {
builder.start_node(kind.into());
if kind == SyntaxKind::SEQUENCE {
builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
} else {
builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
}
builder.finish_node();
return;
};
builder.start_node_at(start, kind.into());
builder.finish_node();
}
fn finish_nested(
builder: &mut GreenNodeBuilder<'static>,
kind: SyntaxKind,
start: Option<Checkpoint>,
value_start: Option<Checkpoint>,
) {
if start.is_some() {
finish_collection(builder, kind, start);
if let Some(value_start) = value_start {
builder.start_node_at(value_start, SyntaxKind::VALUE.into());
builder.finish_node();
}
builder.finish_node();
return;
}
builder.token(SyntaxKind::WHITESPACE.into(), " ");
if value_start.is_some() {
builder.start_node(SyntaxKind::VALUE.into());
}
finish_collection(builder, kind, None);
if value_start.is_some() {
builder.finish_node();
}
finish_entry(builder, false);
}
fn finish_entry(builder: &mut GreenNodeBuilder<'static>, ends_with_newline: bool) {
if !ends_with_newline {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node();
}
fn finish_top_level(
mut builder: GreenNodeBuilder<'static>,
kind: SyntaxKind,
start: Option<Checkpoint>,
) -> YamlBuilder {
let empty = start.is_none();
finish_collection(&mut builder, kind, start);
if empty {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node(); builder.finish_node(); YamlBuilder {
file: YamlFile(rowan::SyntaxNode::new_root_mut(builder.finish())),
}
}
fn root_node(built: YamlBuilder) -> YamlNode {
built
.build()
.document()
.and_then(|d| d.syntax().first_child())
.and_then(YamlNode::from_syntax)
.expect("builders always produce a document holding a collection")
}
fn start_mapping_entry(builder: &mut GreenNodeBuilder<'static>, key: &str) {
builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
builder.start_node(SyntaxKind::KEY.into());
crate::nodes::build_key_content(builder, &key, 0, None);
builder.finish_node();
builder.token(SyntaxKind::COLON.into(), ":");
}
fn emit_block_value(
builder: &mut GreenNodeBuilder<'static>,
source: &SyntaxNode,
target: usize,
wrap: bool,
) -> bool {
let block = crate::nodes::mapping::build_block_value_node(source, target, None);
if block.on_key_line {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
if wrap {
crate::yaml::copy_node_to_builder(builder, &block.node);
} else {
crate::as_yaml::copy_node_content(builder, &block.node);
}
block.ends_with_newline
}
impl SequenceBuilder {
pub fn new() -> Self {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
builder.start_node(SyntaxKind::DOCUMENT.into());
Self::nested(builder, 0)
}
fn nested(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
SequenceBuilder {
builder,
indent,
start: None,
}
}
fn start_entry(&mut self) {
begin_entry(&mut self.builder, self.indent, &mut self.start);
self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
self.builder.token(SyntaxKind::DASH.into(), "-");
}
pub fn item(mut self, value: impl AsYaml) -> Self {
self.start_entry();
let nested = self.indent + 2;
let block_node = value.value_node().filter(|n| {
!value.is_inline() && !matches!(n.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE)
});
let ends_with_newline = match block_node {
Some(node) => emit_block_value(&mut self.builder, &node, nested, false),
None => {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
let indent = if value.is_inline() {
self.indent
} else {
nested
};
value.build_content(&mut self.builder, indent, false)
}
};
finish_entry(&mut self.builder, ends_with_newline);
self
}
pub fn sequence<F>(mut self, f: F) -> Self
where
F: FnOnce(SequenceBuilder) -> SequenceBuilder,
{
self.start_entry();
let filled = f(SequenceBuilder::nested(self.builder, self.indent + 2));
self.builder = filled.builder;
finish_nested(&mut self.builder, SyntaxKind::SEQUENCE, filled.start, None);
self
}
pub fn mapping<F>(mut self, f: F) -> Self
where
F: FnOnce(MappingBuilder) -> MappingBuilder,
{
self.start_entry();
let filled = f(MappingBuilder::nested(self.builder, self.indent + 2));
self.builder = filled.builder;
finish_nested(&mut self.builder, SyntaxKind::MAPPING, filled.start, None);
self
}
pub fn insert_sequence(self, other: SequenceBuilder) -> Self {
self.insert_node(other.into_node())
}
pub fn insert_mapping(self, other: MappingBuilder) -> Self {
self.insert_node(other.into_node())
}
fn insert_node(mut self, node: YamlNode) -> Self {
if node.is_inline() {
return self.item(node);
}
self.start_entry();
let ends_with_newline =
emit_block_value(&mut self.builder, node.syntax(), self.indent + 2, false);
finish_entry(&mut self.builder, ends_with_newline);
self
}
fn into_node(self) -> YamlNode {
root_node(self.build())
}
pub fn build(self) -> YamlBuilder {
finish_top_level(self.builder, SyntaxKind::SEQUENCE, self.start)
}
pub fn build_document(self) -> Document {
self.build()
.build()
.document()
.expect("YamlBuilder always produces a document node")
}
}
impl Default for SequenceBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct MappingBuilder {
builder: GreenNodeBuilder<'static>,
indent: usize,
start: Option<Checkpoint>,
}
impl MappingBuilder {
pub fn new() -> Self {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
builder.start_node(SyntaxKind::DOCUMENT.into());
Self::nested(builder, 0)
}
fn nested(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
MappingBuilder {
builder,
indent,
start: None,
}
}
fn start_entry(&mut self, key: &str) {
begin_entry(&mut self.builder, self.indent, &mut self.start);
start_mapping_entry(&mut self.builder, key);
}
pub fn pair(mut self, key: impl Into<String>, value: impl AsYaml) -> Self {
self.start_entry(&key.into());
let nested = self.indent + 2;
let ends_with_newline = match value.value_node().filter(|_| !value.is_inline()) {
Some(node) => emit_block_value(&mut self.builder, &node, nested, true),
None if value.is_inline() => {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
self.builder.start_node(SyntaxKind::VALUE.into());
let ends = value.build_content(&mut self.builder, self.indent, false);
self.builder.finish_node();
ends
}
None => {
self.builder.start_node(SyntaxKind::VALUE.into());
self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
self.builder
.token(SyntaxKind::INDENT.into(), &" ".repeat(nested));
let ends = value.build_content(&mut self.builder, nested, false);
self.builder.finish_node();
ends
}
};
finish_entry(&mut self.builder, ends_with_newline);
self
}
pub fn sequence<F>(mut self, key: impl Into<String>, f: F) -> Self
where
F: FnOnce(SequenceBuilder) -> SequenceBuilder,
{
self.start_entry(&key.into());
let value_start = self.builder.checkpoint();
let filled = f(SequenceBuilder::nested(self.builder, self.indent + 2));
self.builder = filled.builder;
finish_nested(
&mut self.builder,
SyntaxKind::SEQUENCE,
filled.start,
Some(value_start),
);
self
}
pub fn mapping<F>(mut self, key: impl Into<String>, f: F) -> Self
where
F: FnOnce(MappingBuilder) -> MappingBuilder,
{
self.start_entry(&key.into());
let value_start = self.builder.checkpoint();
let filled = f(MappingBuilder::nested(self.builder, self.indent + 2));
self.builder = filled.builder;
finish_nested(
&mut self.builder,
SyntaxKind::MAPPING,
filled.start,
Some(value_start),
);
self
}
pub fn insert_sequence(self, key: impl Into<String>, other: SequenceBuilder) -> Self {
self.pair(key, other.into_node())
}
pub fn insert_mapping(self, key: impl Into<String>, other: MappingBuilder) -> Self {
self.pair(key, other.into_node())
}
fn into_node(self) -> YamlNode {
root_node(self.build())
}
pub fn build(self) -> YamlBuilder {
finish_top_level(self.builder, SyntaxKind::MAPPING, self.start)
}
pub fn build_document(self) -> Document {
self.build()
.build()
.document()
.expect("YamlBuilder always produces a document node")
}
}
impl Default for MappingBuilder {
fn default() -> Self {
Self::new()
}
}