1use crate::{
4 as_yaml::{AsYaml, YamlNode},
5 lex::SyntaxKind,
6 yaml::{Document, SyntaxNode, YamlFile},
7};
8use rowan::{ast::AstNode, Checkpoint, GreenNodeBuilder};
9
10pub struct YamlBuilder {
12 file: YamlFile,
13}
14
15impl YamlBuilder {
16 pub fn scalar(value: impl AsYaml) -> Self {
18 let mut builder = GreenNodeBuilder::new();
19 builder.start_node(SyntaxKind::ROOT.into());
20 builder.start_node(SyntaxKind::DOCUMENT.into());
21 if !value.build_content(&mut builder, 0, false) {
22 builder.token(SyntaxKind::NEWLINE.into(), "\n");
23 }
24 builder.finish_node();
25 builder.finish_node();
26 let green = builder.finish();
27 YamlBuilder {
28 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
29 }
30 }
31
32 pub fn alias(name: impl AsRef<str>) -> Self {
45 Self::scalar(crate::yaml::Alias::new(name))
46 }
47
48 pub fn sequence() -> SequenceBuilder {
50 SequenceBuilder::new()
51 }
52
53 pub fn mapping() -> MappingBuilder {
55 MappingBuilder::new()
56 }
57
58 pub fn build(self) -> YamlFile {
60 self.file
61 }
62}
63
64impl Default for YamlBuilder {
65 fn default() -> Self {
66 Self::mapping().build()
67 }
68}
69
70pub struct SequenceBuilder {
72 builder: GreenNodeBuilder<'static>,
73 indent: usize,
77 start: Option<Checkpoint>,
79}
80
81fn begin_entry(
88 builder: &mut GreenNodeBuilder<'static>,
89 indent: usize,
90 start: &mut Option<Checkpoint>,
91) {
92 if start.is_some() {
93 if indent > 0 {
94 builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
95 }
96 return;
97 }
98 if indent > 0 {
99 builder.token(SyntaxKind::NEWLINE.into(), "\n");
100 builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
101 }
102 *start = Some(builder.checkpoint());
103}
104
105fn finish_collection(
109 builder: &mut GreenNodeBuilder<'static>,
110 kind: SyntaxKind,
111 start: Option<Checkpoint>,
112) {
113 let Some(start) = start else {
114 builder.start_node(kind.into());
115 if kind == SyntaxKind::SEQUENCE {
116 builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
117 builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
118 } else {
119 builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
120 builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
121 }
122 builder.finish_node();
123 return;
124 };
125 builder.start_node_at(start, kind.into());
126 builder.finish_node();
127}
128
129fn finish_nested(
135 builder: &mut GreenNodeBuilder<'static>,
136 kind: SyntaxKind,
137 start: Option<Checkpoint>,
138 value_start: Option<Checkpoint>,
139) {
140 if start.is_some() {
141 finish_collection(builder, kind, start);
142 if let Some(value_start) = value_start {
143 builder.start_node_at(value_start, SyntaxKind::VALUE.into());
144 builder.finish_node();
145 }
146 builder.finish_node();
147 return;
148 }
149 builder.token(SyntaxKind::WHITESPACE.into(), " ");
150 if value_start.is_some() {
151 builder.start_node(SyntaxKind::VALUE.into());
152 }
153 finish_collection(builder, kind, None);
154 if value_start.is_some() {
155 builder.finish_node();
156 }
157 finish_entry(builder, false);
158}
159
160fn finish_entry(builder: &mut GreenNodeBuilder<'static>, ends_with_newline: bool) {
162 if !ends_with_newline {
163 builder.token(SyntaxKind::NEWLINE.into(), "\n");
164 }
165 builder.finish_node();
166}
167
168fn finish_top_level(
170 mut builder: GreenNodeBuilder<'static>,
171 kind: SyntaxKind,
172 start: Option<Checkpoint>,
173) -> YamlBuilder {
174 let empty = start.is_none();
175 finish_collection(&mut builder, kind, start);
176 if empty {
178 builder.token(SyntaxKind::NEWLINE.into(), "\n");
179 }
180 builder.finish_node(); builder.finish_node(); YamlBuilder {
183 file: YamlFile(rowan::SyntaxNode::new_root_mut(builder.finish())),
184 }
185}
186
187fn root_node(built: YamlBuilder) -> YamlNode {
189 built
190 .build()
191 .document()
192 .and_then(|d| d.syntax().first_child())
193 .and_then(YamlNode::from_syntax)
194 .expect("builders always produce a document holding a collection")
195}
196
197fn start_mapping_entry(builder: &mut GreenNodeBuilder<'static>, key: &str) {
200 builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
201 builder.start_node(SyntaxKind::KEY.into());
202 crate::nodes::build_key_content(builder, &key, 0, None);
203 builder.finish_node();
204 builder.token(SyntaxKind::COLON.into(), ":");
205}
206
207fn emit_block_value(
213 builder: &mut GreenNodeBuilder<'static>,
214 source: &SyntaxNode,
215 target: usize,
216 wrap: bool,
217) -> bool {
218 let block = crate::nodes::mapping::build_block_value_node(source, target, None);
219 if block.on_key_line {
220 builder.token(SyntaxKind::WHITESPACE.into(), " ");
221 }
222 if wrap {
223 crate::yaml::copy_node_to_builder(builder, &block.node);
224 } else {
225 crate::as_yaml::copy_node_content(builder, &block.node);
226 }
227 block.ends_with_newline
228}
229
230impl SequenceBuilder {
231 pub fn new() -> Self {
233 let mut builder = GreenNodeBuilder::new();
234 builder.start_node(SyntaxKind::ROOT.into());
235 builder.start_node(SyntaxKind::DOCUMENT.into());
236 Self::nested(builder, 0)
237 }
238
239 fn nested(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
240 SequenceBuilder {
241 builder,
242 indent,
243 start: None,
244 }
245 }
246
247 fn start_entry(&mut self) {
249 begin_entry(&mut self.builder, self.indent, &mut self.start);
250 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
251 self.builder.token(SyntaxKind::DASH.into(), "-");
252 }
253
254 pub fn item(mut self, value: impl AsYaml) -> Self {
257 self.start_entry();
258 let nested = self.indent + 2;
259 let block_node = value.value_node().filter(|n| {
260 !value.is_inline() && !matches!(n.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE)
261 });
262 let ends_with_newline = match block_node {
263 Some(node) => emit_block_value(&mut self.builder, &node, nested, false),
265 None => {
268 self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
269 let indent = if value.is_inline() {
270 self.indent
271 } else {
272 nested
273 };
274 value.build_content(&mut self.builder, indent, false)
275 }
276 };
277 finish_entry(&mut self.builder, ends_with_newline);
278 self
279 }
280
281 pub fn sequence<F>(mut self, f: F) -> Self
283 where
284 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
285 {
286 self.start_entry();
287 let filled = f(SequenceBuilder::nested(self.builder, self.indent + 2));
288 self.builder = filled.builder;
289 finish_nested(&mut self.builder, SyntaxKind::SEQUENCE, filled.start, None);
290 self
291 }
292
293 pub fn mapping<F>(mut self, f: F) -> Self
295 where
296 F: FnOnce(MappingBuilder) -> MappingBuilder,
297 {
298 self.start_entry();
299 let filled = f(MappingBuilder::nested(self.builder, self.indent + 2));
300 self.builder = filled.builder;
301 finish_nested(&mut self.builder, SyntaxKind::MAPPING, filled.start, None);
302 self
303 }
304
305 pub fn insert_sequence(self, other: SequenceBuilder) -> Self {
307 self.insert_node(other.into_node())
308 }
309
310 pub fn insert_mapping(self, other: MappingBuilder) -> Self {
312 self.insert_node(other.into_node())
313 }
314
315 fn insert_node(mut self, node: YamlNode) -> Self {
318 if node.is_inline() {
319 return self.item(node);
321 }
322 self.start_entry();
323 let ends_with_newline =
324 emit_block_value(&mut self.builder, node.syntax(), self.indent + 2, false);
325 finish_entry(&mut self.builder, ends_with_newline);
326 self
327 }
328
329 fn into_node(self) -> YamlNode {
331 root_node(self.build())
332 }
333
334 pub fn build(self) -> YamlBuilder {
336 finish_top_level(self.builder, SyntaxKind::SEQUENCE, self.start)
337 }
338
339 pub fn build_document(self) -> Document {
341 self.build()
342 .build()
343 .document()
344 .expect("YamlBuilder always produces a document node")
345 }
346}
347
348impl Default for SequenceBuilder {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354pub struct MappingBuilder {
356 builder: GreenNodeBuilder<'static>,
357 indent: usize,
359 start: Option<Checkpoint>,
361}
362
363impl MappingBuilder {
364 pub fn new() -> Self {
366 let mut builder = GreenNodeBuilder::new();
367 builder.start_node(SyntaxKind::ROOT.into());
368 builder.start_node(SyntaxKind::DOCUMENT.into());
369 Self::nested(builder, 0)
370 }
371
372 fn nested(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
373 MappingBuilder {
374 builder,
375 indent,
376 start: None,
377 }
378 }
379
380 fn start_entry(&mut self, key: &str) {
382 begin_entry(&mut self.builder, self.indent, &mut self.start);
383 start_mapping_entry(&mut self.builder, key);
384 }
385
386 pub fn pair(mut self, key: impl Into<String>, value: impl AsYaml) -> Self {
392 self.start_entry(&key.into());
393 let nested = self.indent + 2;
394 let ends_with_newline = match value.value_node().filter(|_| !value.is_inline()) {
395 Some(node) => emit_block_value(&mut self.builder, &node, nested, true),
396 None if value.is_inline() => {
397 self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
398 self.builder.start_node(SyntaxKind::VALUE.into());
399 let ends = value.build_content(&mut self.builder, self.indent, false);
400 self.builder.finish_node();
401 ends
402 }
403 None => {
404 self.builder.start_node(SyntaxKind::VALUE.into());
405 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
406 self.builder
407 .token(SyntaxKind::INDENT.into(), &" ".repeat(nested));
408 let ends = value.build_content(&mut self.builder, nested, false);
409 self.builder.finish_node();
410 ends
411 }
412 };
413 finish_entry(&mut self.builder, ends_with_newline);
414 self
415 }
416
417 pub fn sequence<F>(mut self, key: impl Into<String>, f: F) -> Self
419 where
420 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
421 {
422 self.start_entry(&key.into());
423 let value_start = self.builder.checkpoint();
424 let filled = f(SequenceBuilder::nested(self.builder, self.indent + 2));
425 self.builder = filled.builder;
426 finish_nested(
427 &mut self.builder,
428 SyntaxKind::SEQUENCE,
429 filled.start,
430 Some(value_start),
431 );
432 self
433 }
434
435 pub fn mapping<F>(mut self, key: impl Into<String>, f: F) -> Self
437 where
438 F: FnOnce(MappingBuilder) -> MappingBuilder,
439 {
440 self.start_entry(&key.into());
441 let value_start = self.builder.checkpoint();
442 let filled = f(MappingBuilder::nested(self.builder, self.indent + 2));
443 self.builder = filled.builder;
444 finish_nested(
445 &mut self.builder,
446 SyntaxKind::MAPPING,
447 filled.start,
448 Some(value_start),
449 );
450 self
451 }
452
453 pub fn insert_sequence(self, key: impl Into<String>, other: SequenceBuilder) -> Self {
455 self.pair(key, other.into_node())
456 }
457
458 pub fn insert_mapping(self, key: impl Into<String>, other: MappingBuilder) -> Self {
460 self.pair(key, other.into_node())
461 }
462
463 fn into_node(self) -> YamlNode {
465 root_node(self.build())
466 }
467
468 pub fn build(self) -> YamlBuilder {
470 finish_top_level(self.builder, SyntaxKind::MAPPING, self.start)
471 }
472
473 pub fn build_document(self) -> Document {
475 self.build()
476 .build()
477 .document()
478 .expect("YamlBuilder always produces a document node")
479 }
480}
481
482impl Default for MappingBuilder {
483 fn default() -> Self {
484 Self::new()
485 }
486}