yerba 0.8.1-arm-linux-gnu → 0.9.1-arm-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.
- checksums.yaml +4 -4
- data/README.md +170 -19
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +9 -0
- data/ext/yerba/yerba.c +72 -5
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/formatting.rb +9 -13
- data/lib/yerba/map.rb +11 -8
- data/lib/yerba/sequence.rb +2 -7
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/blank_lines.rs +22 -1
- data/rust/src/commands/init.rs +6 -0
- data/rust/src/commands/insert.rs +12 -6
- data/rust/src/commands/mod.rs +2 -3
- data/rust/src/commands/set.rs +15 -5
- data/rust/src/commands/ui.rs +1 -2
- data/rust/src/commands/version.rs +1 -1
- data/rust/src/document/insert.rs +23 -11
- data/rust/src/document/mod.rs +13 -1
- data/rust/src/document/set.rs +108 -34
- data/rust/src/document/style.rs +131 -11
- data/rust/src/error.rs +24 -0
- data/rust/src/ffi.rs +55 -12
- data/rust/src/lib.rs +5 -3
- data/rust/src/syntax.rs +169 -26
- data/rust/src/validation.rs +30 -0
- data/rust/src/yaml_writer.rs +7 -19
- data/rust/src/yerbafile.rs +76 -4
- metadata +3 -2
|
@@ -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.
|
|
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
|
+
}
|
data/rust/src/commands/init.rs
CHANGED
|
@@ -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:
|
data/rust/src/commands/insert.rs
CHANGED
|
@@ -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(
|
|
71
|
-
|
|
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
|
|
data/rust/src/commands/mod.rs
CHANGED
|
@@ -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
|
-
"{}
|
|
195
|
-
ui::INDENT,
|
|
194
|
+
"{} {}\n",
|
|
196
195
|
ui::strong("Using Yerbafile rules"),
|
|
197
196
|
ui::subtle(format!("(from {})", yerbafile_path.display()))
|
|
198
197
|
);
|
data/rust/src/commands/set.rs
CHANGED
|
@@ -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 =
|
|
71
|
-
document.
|
|
72
|
-
|
|
73
|
-
document.
|
|
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(
|
data/rust/src/commands/ui.rs
CHANGED
|
@@ -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!("
|
|
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 {
|
data/rust/src/document/insert.rs
CHANGED
|
@@ -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(¤t_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
|
|
574
|
-
return format!("{}: {}", key,
|
|
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
|
+
}
|
data/rust/src/document/mod.rs
CHANGED
|
@@ -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 {
|
|
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> {
|
data/rust/src/document/set.rs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
116
|
+
self.set_with(dot_path, value, false)
|
|
117
|
+
}
|
|
79
118
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
130
|
+
if nodes.is_empty() {
|
|
131
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
91
132
|
}
|
|
92
133
|
|
|
93
|
-
let
|
|
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.
|
|
137
|
+
self.apply_edits(edits)
|
|
98
138
|
}
|
|
99
139
|
|
|
100
|
-
pub fn
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
109
|
-
|
|
110
|
-
for
|
|
111
|
-
if
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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(¤t_node) {
|
|
155
222
|
let span = value_span(¤t_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,
|
|
225
|
+
return self.apply_edit(span.range, &replacement_text(&span, &replacement));
|
|
158
226
|
}
|
|
159
227
|
|
|
160
228
|
let scalar_token = find_scalar_token(¤t_node).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
161
229
|
|
|
162
|
-
|
|
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 {
|