yerba 0.8.1-aarch64-linux-gnu → 0.9.1-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,8 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
11
11
  yerba blank-lines videos.yml "[]" 1
12
12
  yerba blank-lines videos.yml "[].speakers" 1
13
13
  yerba blank-lines config.yml "tags" 0
14
+ yerba blank-lines event.yml "" 1 --before "schedule,speakers"
15
+ yerba blank-lines event.yml "" 1 --after "location" --skip-empty
14
16
  "#})
15
17
  });
16
18
 
@@ -26,6 +28,15 @@ pub struct Args {
26
28
  first: String,
27
29
  /// Count (when selector is provided as first positional)
28
30
  second: Option<usize>,
31
+ /// Comma-separated map keys to separate from what precedes them
32
+ #[arg(long)]
33
+ before: Option<String>,
34
+ /// Comma-separated map keys to separate from what follows them
35
+ #[arg(long)]
36
+ after: Option<String>,
37
+ /// Leave entries alone when their value is null, an empty collection, or an empty string
38
+ #[arg(long)]
39
+ skip_empty: bool,
29
40
  #[arg(long)]
30
41
  dry_run: bool,
31
42
  }
@@ -44,12 +55,22 @@ impl Args {
44
55
  std::process::exit(1);
45
56
  };
46
57
 
58
+ let options = yerba::BlankLineOptions {
59
+ before: split_keys(self.before.as_deref()),
60
+ after: split_keys(self.after.as_deref()),
61
+ skip_empty: self.skip_empty,
62
+ };
63
+
47
64
  for resolved_file in resolve_files(&self.file) {
48
65
  let mut document = parse_file(&resolved_file);
49
66
 
50
- if document.enforce_blank_lines(selector, count).is_ok() {
67
+ if document.enforce_blank_lines_with(selector, count, &options).is_ok() {
51
68
  output(&resolved_file, &document, self.dry_run);
52
69
  }
53
70
  }
54
71
  }
55
72
  }
73
+
74
+ fn split_keys(keys: Option<&str>) -> Vec<String> {
75
+ keys.map(|keys| keys.split(',').map(|key| key.trim().to_string()).collect()).unwrap_or_default()
76
+ }
@@ -45,6 +45,12 @@ pub fn run() {
45
45
  # # Enforce blank lines between sequence entries
46
46
  # - blank_lines:
47
47
  # count: 1
48
+ # # skip_empty: true # leave null/empty entries packed
49
+ # # before: # separate these keys from what precedes them
50
+ # # - schedule
51
+ # # - speakers
52
+ # # after: # separate these keys from what follows them
53
+ # # - location
48
54
  #
49
55
  # # Set a value (with optional condition)
50
56
  # - set:
@@ -8,13 +8,13 @@ use super::{output, parse_file, resolve_files, run_op};
8
8
 
9
9
  static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
10
10
  colorize_examples(indoc! {r#"
11
- yerba insert config.yml "database.ssl" true
12
- yerba insert config.yml "database.ssl" true --after "host"
13
- yerba insert config.yml "database.ssl" true --before "port"
11
+ yerba insert config.yml "database.ssl" true --plain
12
+ yerba insert config.yml "database.ssl" true --plain --after "host"
13
+ yerba insert config.yml "database.ssl" true --plain --before "port"
14
14
  yerba insert config.yml "tags" "yaml"
15
15
  yerba insert config.yml "tags" "yaml" --at 0
16
16
  yerba insert config.yml "tags" "yaml" --after "ruby"
17
- yerba insert speakers.yml "" "name: Bob" --after ".name == Alice"
17
+ yerba insert speakers.yml "" "name: Bob" --plain --after ".name == Alice"
18
18
  yerba insert videos.yml "[0].speakers" "Diana" --before ".name == Charlie"
19
19
  yerba insert videos.yml "" --from "new_talk.yml" --after ".id == first-talk"
20
20
  "#})
@@ -39,6 +39,8 @@ pub struct Args {
39
39
  after: Option<String>,
40
40
  #[arg(long)]
41
41
  at: Option<usize>,
42
+ #[arg(long, help = "Write the value as raw YAML, for literals like null, true or 42 and for map or sequence fragments")]
43
+ plain: bool,
42
44
  #[arg(long)]
43
45
  dry_run: bool,
44
46
  }
@@ -67,8 +69,12 @@ impl Args {
67
69
  .trim()
68
70
  .to_string()
69
71
  }
70
- } else if let Some(val) = self.value {
71
- val
72
+ } else if let Some(value) = self.value {
73
+ if self.plain {
74
+ value
75
+ } else {
76
+ yerba::quote_scalar(&value)
77
+ }
72
78
  } else {
73
79
  eprintln!("{} either a value argument or --from is required", ui::failure("Error:"));
74
80
 
@@ -188,11 +188,10 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
188
188
  process::exit(1);
189
189
  });
190
190
 
191
- eprintln!("{}", ui::banner());
191
+ eprintln!("\n{}\n", ui::banner());
192
192
 
193
193
  eprintln!(
194
- "{}{} {}\n",
195
- ui::INDENT,
194
+ "{} {}\n",
196
195
  ui::strong("Using Yerbafile rules"),
197
196
  ui::subtle(format!("(from {})", yerbafile_path.display()))
198
197
  );
@@ -11,8 +11,11 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
11
11
  yerba set config.yml "database.host" "0.0.0.0" --if-exists
12
12
  yerba set config.yml "database.host" "0.0.0.0" --condition ".port == 5432"
13
13
  yerba set videos.yml "[0].title" "New Title"
14
+ yerba set videos.yml "[].title" "New Title" --condition ".id == talk-1"
14
15
  yerba set "data/**/event.yml" "website" "" --if-exists
15
16
  yerba set videos.yml "[].description" "" --all
17
+ yerba set config.yml "database.replica" null --plain
18
+ yerba set videos.yml "[].published" false --plain --all
16
19
  "#})
17
20
  });
18
21
 
@@ -35,6 +38,8 @@ pub struct Args {
35
38
  condition: Option<String>,
36
39
  #[arg(long)]
37
40
  all: bool,
41
+ #[arg(long, help = "Write the value unquoted, for YAML literals like null, true or 42")]
42
+ plain: bool,
38
43
  #[arg(long)]
39
44
  dry_run: bool,
40
45
  }
@@ -53,6 +58,9 @@ impl Args {
53
58
  }
54
59
  }
55
60
 
61
+ let leaf = self.selector.rsplit_once('.').map(|(_, leaf)| leaf).unwrap_or(&self.selector);
62
+ let per_item = yerba::Selector::parse(parent_path).has_brackets();
63
+
56
64
  for resolved_file in resolve_files(&self.file) {
57
65
  let mut document = parse_file(&resolved_file);
58
66
 
@@ -61,16 +69,18 @@ impl Args {
61
69
  } else if self.if_missing {
62
70
  !document.exists(&self.selector)
63
71
  } else if let Some(condition) = &self.condition {
64
- document.evaluate_condition(parent_path, condition).unwrap_or(false)
72
+ per_item || document.evaluate_condition(parent_path, condition).unwrap_or(false)
65
73
  } else {
66
74
  true
67
75
  };
68
76
 
69
77
  if should_set {
70
- let result = if self.all {
71
- document.set_all(&self.selector, &self.value)
72
- } else {
73
- document.set(&self.selector, &self.value)
78
+ let result = match &self.condition {
79
+ Some(condition) if per_item => document.set_where(parent_path, leaf, &self.value, condition, self.all, self.plain).map(|_| ()),
80
+ _ if self.all && self.plain => document.set_all_plain(&self.selector, &self.value),
81
+ _ if self.all => document.set_all(&self.selector, &self.value),
82
+ _ if self.plain => document.set_plain(&self.selector, &self.value),
83
+ _ => document.set(&self.selector, &self.value),
74
84
  };
75
85
 
76
86
  run_op_with_hint(
@@ -110,10 +110,9 @@ pub fn strong(text: impl std::fmt::Display) -> String {
110
110
  }
111
111
 
112
112
  pub fn banner() -> String {
113
- format!("🧉 {} {}", Style::fg(GREEN).bold().paint("yerba"), subtle(format!("v{}", yerba::version())))
113
+ format!("{} 🧉 {}", strong("Yerba"), subtle(format!("v{}", yerba::version())))
114
114
  }
115
115
 
116
- pub const INDENT: &str = " ";
117
116
  pub const STATUS_INDENT: &str = " ";
118
117
 
119
118
  pub mod glyph {
@@ -3,7 +3,7 @@ use super::ui;
3
3
  pub fn run() {
4
4
  println!("{}", ui::banner());
5
5
  println!(
6
- " {}{} {}{} {}{} {}{} {}{}",
6
+ "{}{} {}{} {}{} {}{} {}{}",
7
7
  ui::strong("Y"),
8
8
  ui::subtle("AML"),
9
9
  ui::strong("E"),
@@ -89,6 +89,14 @@ impl Document {
89
89
  }
90
90
 
91
91
  pub fn insert_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
92
+ self.insert_with(dot_path, value, position, false)
93
+ }
94
+
95
+ pub fn insert_fragment_into(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
96
+ self.insert_with(dot_path, value, position, true)
97
+ }
98
+
99
+ fn insert_with(&mut self, dot_path: &str, value: &str, position: InsertPosition, fragment: bool) -> Result<(), YerbaError> {
92
100
  Self::validate_path(dot_path)?;
93
101
  self.refuse_flow_target(dot_path)?;
94
102
 
@@ -180,7 +188,7 @@ impl Document {
180
188
  };
181
189
 
182
190
  let indent = " ".repeat(start_col);
183
- let new_entry_text = Self::format_map_entry(key, value, &indent);
191
+ let new_entry_text = Self::format_map_entry(key, value, &indent, fragment);
184
192
 
185
193
  match &position {
186
194
  InsertPosition::After(target_key) => {
@@ -218,7 +226,7 @@ impl Document {
218
226
  return Ok(());
219
227
  }
220
228
 
221
- self.insert_map_key(parent_path, key, value, position)
229
+ self.insert_map_key(parent_path, key, value, position, fragment)
222
230
  }
223
231
 
224
232
  fn insert_sequence_item(&mut self, dot_path: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
@@ -339,7 +347,7 @@ impl Document {
339
347
  }
340
348
  }
341
349
 
342
- fn insert_map_key(&mut self, dot_path: &str, key: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
350
+ fn insert_map_key(&mut self, dot_path: &str, key: &str, value: &str, position: InsertPosition, fragment: bool) -> Result<(), YerbaError> {
343
351
  let current_node = self.navigate(dot_path)?;
344
352
 
345
353
  let map = match find_block_map(&current_node) {
@@ -378,7 +386,7 @@ impl Document {
378
386
  .map(|entry| preceding_whitespace_indent(entry.syntax()))
379
387
  .unwrap_or_default();
380
388
 
381
- let new_entry_text = Self::format_map_entry(key, value, &indent);
389
+ let new_entry_text = Self::format_map_entry(key, value, &indent, fragment);
382
390
 
383
391
  match position {
384
392
  InsertPosition::Last => {
@@ -422,7 +430,7 @@ impl Document {
422
430
  self.insert_after_node(target_entry.syntax(), &new_text)
423
431
  }
424
432
 
425
- InsertPosition::BeforeCondition(_) | InsertPosition::AfterCondition(_) => self.insert_map_key(dot_path, key, value, InsertPosition::Last),
433
+ InsertPosition::BeforeCondition(_) | InsertPosition::AfterCondition(_) => self.insert_map_key(dot_path, key, value, InsertPosition::Last, fragment),
426
434
 
427
435
  InsertPosition::FromSortOrder(order) => {
428
436
  let new_key_position = order.iter().position(|ordered_key| ordered_key == key);
@@ -447,7 +455,7 @@ impl Document {
447
455
  None => InsertPosition::Last,
448
456
  };
449
457
 
450
- self.insert_map_key(dot_path, key, value, resolved)
458
+ self.insert_map_key(dot_path, key, value, resolved, fragment)
451
459
  }
452
460
  }
453
461
  }
@@ -566,15 +574,15 @@ impl Document {
566
574
  Some((trailing[comment_start..].to_string(), rowan::TextSize::from((start + line_end) as u32)))
567
575
  }
568
576
 
569
- fn format_map_entry(key: &str, value: &str, indent: &str) -> String {
570
- let is_block_value = value.contains('\n') || value.starts_with("- ");
577
+ fn format_map_entry(key: &str, value: &str, indent: &str, fragment: bool) -> String {
578
+ let is_block_value = value.contains('\n') || value.starts_with("- ") || (fragment && is_block_collection(value));
571
579
 
572
580
  if !is_block_value {
573
- if !crate::syntax::is_valid_inline_value(value) {
574
- return format!("{}: {}", key, crate::syntax::quote_if_needed(value));
581
+ if crate::syntax::is_inline_scalar_safe(value) {
582
+ return format!("{}: {}", key, value);
575
583
  }
576
584
 
577
- return format!("{}: {}", key, value);
585
+ return format!("{}: {}", key, crate::syntax::quote_scalar(value));
578
586
  }
579
587
 
580
588
  let value_indent = format!("{} ", indent);
@@ -636,3 +644,7 @@ impl Document {
636
644
  }
637
645
  }
638
646
  }
647
+
648
+ fn is_block_collection(value: &str) -> bool {
649
+ !crate::syntax::is_flow_collection(value) && !crate::syntax::is_quoted_scalar(value) && !crate::syntax::is_plain_safe(value)
650
+ }
@@ -31,6 +31,7 @@ pub struct LocatedNode {
31
31
 
32
32
  use std::fs;
33
33
  use std::path::{Path, PathBuf};
34
+ use std::sync::atomic::{AtomicU64, Ordering};
34
35
 
35
36
  use rowan::ast::AstNode;
36
37
  use rowan::{TextRange, TextSize};
@@ -126,10 +127,13 @@ pub enum InsertPosition {
126
127
  FromSortOrder(Vec<String>),
127
128
  }
128
129
 
130
+ static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
131
+
129
132
  #[derive(Debug)]
130
133
  pub struct Document {
131
134
  root: SyntaxNode,
132
135
  path: Option<PathBuf>,
136
+ generation: u64,
133
137
  }
134
138
 
135
139
  impl Document {
@@ -138,7 +142,15 @@ impl Document {
138
142
 
139
143
  check_duplicate_keys(&tree)?;
140
144
 
141
- Ok(Document { root: tree, path: None })
145
+ Ok(Document {
146
+ root: tree,
147
+ path: None,
148
+ generation: NEXT_GENERATION.fetch_add(1, Ordering::Relaxed),
149
+ })
150
+ }
151
+
152
+ pub fn generation(&self) -> u64 {
153
+ self.generation
142
154
  }
143
155
 
144
156
  pub fn parse_file(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
@@ -1,7 +1,17 @@
1
1
  use super::*;
2
2
 
3
+ fn is_plain_writable(value: &str) -> bool {
4
+ crate::syntax::is_plain_safe(value) && !crate::syntax::is_yaml_non_string(value)
5
+ }
6
+
3
7
  fn scalar_replacement_text(value: &str, kind: SyntaxKind) -> String {
4
- if kind == SyntaxKind::PLAIN_SCALAR && !crate::syntax::is_inline_scalar_safe(value) {
8
+ let representable = match kind {
9
+ SyntaxKind::PLAIN_SCALAR => is_plain_writable(value),
10
+ SyntaxKind::SINGLE_QUOTED_SCALAR => crate::syntax::is_single_quotable(value),
11
+ _ => true,
12
+ };
13
+
14
+ if !representable {
5
15
  return format_scalar_value(value, SyntaxKind::DOUBLE_QUOTED_SCALAR);
6
16
  }
7
17
 
@@ -62,6 +72,34 @@ fn value_span(node: &SyntaxNode) -> Option<ValueSpan> {
62
72
  })
63
73
  }
64
74
 
75
+ fn scalar_edit(source: &str, node: &SyntaxNode, value: &str, plain: bool) -> Option<(TextRange, String)> {
76
+ if let Some(block_scalar) = node.descendants().find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR) {
77
+ let replacement = if plain {
78
+ value.to_string()
79
+ } else {
80
+ Document::block_scalar_replacement(source, &block_scalar, value)
81
+ };
82
+
83
+ return Some((block_scalar.text_range(), replacement));
84
+ }
85
+
86
+ if holds_collection(node) {
87
+ let replacement = if plain { value.to_string() } else { crate::syntax::quote_scalar(value) };
88
+
89
+ return value_span(node).map(|span| (span.range, replacement_text(&span, &replacement)));
90
+ }
91
+
92
+ let scalar_token = find_scalar_token(node)?;
93
+
94
+ let replacement = if plain {
95
+ value.to_string()
96
+ } else {
97
+ scalar_replacement_text(value, scalar_token.kind())
98
+ };
99
+
100
+ Some((scalar_token.text_range(), replacement))
101
+ }
102
+
65
103
  fn replacement_text(span: &ValueSpan, value: &str) -> String {
66
104
  let mut text = if span.separated { format!(" {}", value) } else { value.to_string() };
67
105
 
@@ -75,51 +113,70 @@ fn replacement_text(span: &ValueSpan, value: &str) -> String {
75
113
 
76
114
  impl Document {
77
115
  pub fn set(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
78
- let current_node = self.navigate(dot_path)?;
116
+ self.set_with(dot_path, value, false)
117
+ }
79
118
 
80
- if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
81
- let source = self.source_text();
82
- let new_text = Self::block_scalar_replacement(&source, &block_scalar, value);
119
+ pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
120
+ self.set_all_with(dot_path, value, false)
121
+ }
83
122
 
84
- return self.apply_edit(block_scalar.text_range(), &new_text);
85
- }
123
+ pub fn set_all_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
124
+ self.set_all_with(dot_path, value, true)
125
+ }
86
126
 
87
- if holds_collection(&current_node) {
88
- let span = value_span(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
127
+ fn set_all_with(&mut self, dot_path: &str, value: &str, plain: bool) -> Result<(), YerbaError> {
128
+ let nodes = self.navigate_all_compact(dot_path);
89
129
 
90
- return self.apply_edit(span.range, &replacement_text(&span, value));
130
+ if nodes.is_empty() {
131
+ return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
91
132
  }
92
133
 
93
- let scalar_token = find_scalar_token(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
94
-
95
- let new_text = scalar_replacement_text(value, scalar_token.kind());
134
+ let source = self.source_text();
135
+ let edits = nodes.iter().filter_map(|node| scalar_edit(&source, node, value, plain)).collect();
96
136
 
97
- self.replace_token(&scalar_token, &new_text)
137
+ self.apply_edits(edits)
98
138
  }
99
139
 
100
- pub fn set_all(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
101
- let nodes = self.navigate_all_compact(dot_path);
140
+ pub fn set_where(&mut self, container_path: &str, relative_path: &str, value: &str, condition: &str, all: bool, plain: bool) -> Result<usize, YerbaError> {
141
+ validate_item_condition(condition)?;
102
142
 
103
- if nodes.is_empty() {
104
- return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
143
+ let items = self.navigate_all_compact(container_path);
144
+
145
+ if items.is_empty() {
146
+ return Err(YerbaError::SelectorNotFound(container_path.to_string()));
105
147
  }
106
148
 
107
149
  let source = self.source_text();
108
- let mut edits: Vec<(TextRange, String)> = Vec::new();
109
-
110
- for node in nodes {
111
- if let Some(block_scalar) = node.descendants().find(|child| child.kind() == SyntaxKind::BLOCK_SCALAR) {
112
- edits.push((block_scalar.text_range(), Self::block_scalar_replacement(&source, &block_scalar, value)));
113
- } else if holds_collection(&node) {
114
- if let Some(span) = value_span(&node) {
115
- edits.push((span.range, replacement_text(&span, value)));
116
- }
117
- } else if let Some(scalar_token) = find_scalar_token(&node) {
118
- edits.push((scalar_token.text_range(), scalar_replacement_text(value, scalar_token.kind())));
150
+ let mut targets: Vec<SyntaxNode> = Vec::new();
151
+
152
+ for item in &items {
153
+ if !self.evaluate_condition_on_node(item, condition) {
154
+ continue;
119
155
  }
156
+
157
+ targets.extend(navigate_from_node(item, relative_path));
120
158
  }
121
159
 
122
- self.apply_edits(edits)
160
+ if targets.is_empty() {
161
+ return Ok(0);
162
+ }
163
+
164
+ if !all && targets.len() > 1 {
165
+ let selector = if container_path.is_empty() {
166
+ relative_path.to_string()
167
+ } else {
168
+ format!("{}.{}", container_path, relative_path)
169
+ };
170
+
171
+ return Err(YerbaError::AmbiguousSelector(selector, targets.len()));
172
+ }
173
+
174
+ let edits: Vec<(TextRange, String)> = targets.iter().filter_map(|node| scalar_edit(&source, node, value, plain)).collect();
175
+ let count = edits.len();
176
+
177
+ self.apply_edits(edits)?;
178
+
179
+ Ok(count)
123
180
  }
124
181
 
125
182
  pub fn set_scalar_style(&mut self, dot_path: &str, style: &QuoteStyle) -> Result<(), YerbaError> {
@@ -144,22 +201,39 @@ impl Document {
144
201
  }
145
202
 
146
203
  pub fn set_plain(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
204
+ self.set_with(dot_path, value, true)
205
+ }
206
+
207
+ fn set_with(&mut self, dot_path: &str, value: &str, plain: bool) -> Result<(), YerbaError> {
147
208
  let current_node = self.navigate(dot_path)?;
148
209
 
149
210
  if let Some(block_scalar) = current_node.descendants().find(|node| node.kind() == SyntaxKind::BLOCK_SCALAR) {
150
- let range = block_scalar.text_range();
151
- return self.apply_edit(range, value);
211
+ if plain {
212
+ return self.apply_edit(block_scalar.text_range(), value);
213
+ }
214
+
215
+ let source = self.source_text();
216
+ let new_text = Self::block_scalar_replacement(&source, &block_scalar, value);
217
+
218
+ return self.apply_edit(block_scalar.text_range(), &new_text);
152
219
  }
153
220
 
154
221
  if holds_collection(&current_node) {
155
222
  let span = value_span(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
223
+ let replacement = if plain { value.to_string() } else { crate::syntax::quote_scalar(value) };
156
224
 
157
- return self.apply_edit(span.range, &replacement_text(&span, value));
225
+ return self.apply_edit(span.range, &replacement_text(&span, &replacement));
158
226
  }
159
227
 
160
228
  let scalar_token = find_scalar_token(&current_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
161
229
 
162
- self.replace_token(&scalar_token, value)
230
+ if plain {
231
+ return self.replace_token(&scalar_token, value);
232
+ }
233
+
234
+ let new_text = scalar_replacement_text(value, scalar_token.kind());
235
+
236
+ self.replace_token(&scalar_token, &new_text)
163
237
  }
164
238
 
165
239
  fn block_scalar_replacement(source: &str, block_scalar: &SyntaxNode, value: &str) -> String {