yerba 0.7.6-arm-linux-gnu → 0.8.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 +66 -2
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +8 -0
- data/ext/yerba/yerba.c +70 -8
- 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/map.rb +15 -10
- data/lib/yerba/node.rb +2 -2
- data/lib/yerba/sequence.rb +13 -1
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +2 -342
- data/rust/Cargo.toml +15 -6
- data/rust/src/commands/blank_lines.rs +2 -2
- data/rust/src/commands/directives.rs +4 -4
- data/rust/src/commands/get.rs +17 -10
- data/rust/src/commands/init.rs +4 -4
- data/rust/src/commands/insert.rs +6 -6
- data/rust/src/commands/location.rs +2 -2
- data/rust/src/commands/mate.rs +7 -7
- data/rust/src/commands/mod.rs +119 -90
- data/rust/src/commands/move_key.rs +9 -6
- data/rust/src/commands/quote_style.rs +4 -5
- data/rust/src/commands/rename.rs +3 -3
- data/rust/src/commands/schema.rs +6 -6
- data/rust/src/commands/selectors.rs +2 -2
- data/rust/src/commands/set.rs +12 -1
- data/rust/src/commands/sort.rs +36 -42
- data/rust/src/commands/sort_keys.rs +2 -2
- data/rust/src/commands/ui.rs +157 -0
- data/rust/src/commands/unique.rs +7 -7
- data/rust/src/commands/version.rs +13 -3
- data/rust/src/document/condition.rs +89 -32
- data/rust/src/document/delete.rs +6 -1
- data/rust/src/document/get.rs +94 -26
- data/rust/src/document/insert.rs +26 -0
- data/rust/src/document/mod.rs +73 -20
- data/rust/src/document/set.rs +91 -2
- data/rust/src/error.rs +17 -0
- data/rust/src/ffi.rs +76 -9
- data/rust/src/json.rs +10 -1
- data/rust/src/lib.rs +23 -7
- data/rust/src/main.rs +1 -0
- data/rust/src/quote_style.rs +12 -11
- data/rust/src/selector.rs +19 -3
- data/rust/src/syntax.rs +178 -26
- data/rust/src/yaml_writer.rs +4 -4
- data/rust/src/yerbafile.rs +30 -6
- metadata +3 -2
data/rust/src/commands/sort.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::ui;
|
|
1
2
|
use std::process;
|
|
2
3
|
use std::sync::LazyLock;
|
|
3
4
|
|
|
@@ -53,14 +54,14 @@ fn is_explicit_reorder(orders: &[String]) -> bool {
|
|
|
53
54
|
|
|
54
55
|
impl Args {
|
|
55
56
|
pub fn run(self) {
|
|
56
|
-
use super::
|
|
57
|
+
use super::ui;
|
|
57
58
|
|
|
58
59
|
if self.by.is_empty() && self.order.is_empty() && self.selector.is_none() {
|
|
59
60
|
let document = parse_file(&self.file);
|
|
60
61
|
|
|
61
|
-
eprintln!("{
|
|
62
|
+
eprintln!("{} specify a selector, --by, or --order", ui::failure("Error:"));
|
|
62
63
|
eprintln!();
|
|
63
|
-
eprintln!(" {
|
|
64
|
+
eprintln!(" {}", ui::strong("Examples:"));
|
|
64
65
|
eprintln!(" yerba sort \"{}\" \"tags\"", self.file);
|
|
65
66
|
eprintln!(" yerba sort \"{}\" --by \".title\" --order asc", self.file);
|
|
66
67
|
eprintln!();
|
|
@@ -80,10 +81,8 @@ impl Args {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
fn show_values(self) {
|
|
83
|
-
use super::color::*;
|
|
84
|
-
|
|
85
84
|
if self.by.len() != 1 {
|
|
86
|
-
eprintln!("{
|
|
85
|
+
eprintln!("{} --order is required when using --by", ui::failure("Error:"));
|
|
87
86
|
process::exit(1);
|
|
88
87
|
}
|
|
89
88
|
|
|
@@ -94,14 +93,14 @@ impl Args {
|
|
|
94
93
|
let (labels, context_values, selector_display) = self.resolve_labels(&document, by, selector, &items_selector);
|
|
95
94
|
|
|
96
95
|
let context_hint = if self.context.is_empty() {
|
|
97
|
-
format!("\n\n {
|
|
96
|
+
format!("\n\n {}", ui::subtle("Add --context \".field\" to show additional fields alongside values"))
|
|
98
97
|
} else {
|
|
99
98
|
String::new()
|
|
100
99
|
};
|
|
101
100
|
|
|
102
|
-
eprintln!("{
|
|
101
|
+
eprintln!("{} --order is required when using --by", ui::failure("Error:"));
|
|
103
102
|
eprintln!();
|
|
104
|
-
eprintln!(" {
|
|
103
|
+
eprintln!(" {}", ui::strong(format!("Current values (by {}):", by)));
|
|
105
104
|
|
|
106
105
|
for (index, label) in labels.iter().enumerate() {
|
|
107
106
|
let context = context_values.get(index).map(|c| c.as_slice()).unwrap_or(&[]);
|
|
@@ -113,22 +112,20 @@ impl Args {
|
|
|
113
112
|
|
|
114
113
|
eprintln!("{context_hint}");
|
|
115
114
|
eprintln!();
|
|
116
|
-
eprintln!(" {
|
|
115
|
+
eprintln!(" {}", ui::strong("To sort alphabetically:"));
|
|
117
116
|
eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order asc", self.file);
|
|
118
117
|
eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order desc", self.file);
|
|
119
118
|
eprintln!();
|
|
120
|
-
eprintln!(" {
|
|
119
|
+
eprintln!(" {}", ui::strong("To reorder explicitly:"));
|
|
121
120
|
eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order \"{csv}\"", self.file);
|
|
122
121
|
eprintln!();
|
|
123
|
-
eprintln!(" {
|
|
122
|
+
eprintln!(" {}", ui::strong("To move individual items:"));
|
|
124
123
|
eprintln!(" yerba move \"{}\"{selector_display} <item> --before/--after <target>", self.file);
|
|
125
124
|
|
|
126
125
|
process::exit(1);
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
fn run_sort(self) {
|
|
130
|
-
use super::color::*;
|
|
131
|
-
|
|
132
129
|
let selector = self.selector.as_deref().unwrap_or("");
|
|
133
130
|
|
|
134
131
|
let sort_fields: Vec<yerba::SortField> = if self.by.is_empty() {
|
|
@@ -145,7 +142,7 @@ impl Args {
|
|
|
145
142
|
Some("desc" | "descending") => yerba::SortField::desc(field),
|
|
146
143
|
Some("asc" | "ascending") | None => yerba::SortField::asc(field),
|
|
147
144
|
Some(other) => {
|
|
148
|
-
eprintln!("{
|
|
145
|
+
eprintln!("{} invalid sort direction \"{other}\". Use \"asc\" or \"desc\"", ui::failure("Error:"));
|
|
149
146
|
process::exit(1);
|
|
150
147
|
}
|
|
151
148
|
}
|
|
@@ -161,7 +158,7 @@ impl Args {
|
|
|
161
158
|
|
|
162
159
|
match document.get_value(&first_item_selector) {
|
|
163
160
|
Some(first) if first.is_mapping() => {
|
|
164
|
-
eprintln!("{
|
|
161
|
+
eprintln!("{} --by is required to sort a sequence of maps", ui::failure("Error:"));
|
|
165
162
|
eprintln!();
|
|
166
163
|
|
|
167
164
|
let selectors = document.selectors();
|
|
@@ -175,7 +172,7 @@ impl Args {
|
|
|
175
172
|
.collect();
|
|
176
173
|
|
|
177
174
|
if !fields.is_empty() {
|
|
178
|
-
eprintln!(" {
|
|
175
|
+
eprintln!(" {}", ui::strong("Available fields:"));
|
|
179
176
|
|
|
180
177
|
for field in &fields {
|
|
181
178
|
let short = field.rsplit_once('.').map(|(_, f)| f).unwrap_or(field);
|
|
@@ -187,9 +184,9 @@ impl Args {
|
|
|
187
184
|
}
|
|
188
185
|
|
|
189
186
|
None if selector.is_empty() => {
|
|
190
|
-
eprintln!("{
|
|
187
|
+
eprintln!("{} no sequence found at root level in {}", ui::failure("Error:"), resolved_file);
|
|
191
188
|
eprintln!();
|
|
192
|
-
eprintln!(" {
|
|
189
|
+
eprintln!(" {}", ui::subtle("Specify a selector for the sequence to sort:"));
|
|
193
190
|
eprintln!(" yerba sort \"{}\" \"<selector>\"", self.file);
|
|
194
191
|
eprintln!();
|
|
195
192
|
|
|
@@ -209,16 +206,14 @@ impl Args {
|
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
fn run_reorder(self) {
|
|
212
|
-
use super::color::*;
|
|
213
|
-
|
|
214
209
|
if self.by.len() != 1 {
|
|
215
|
-
eprintln!("{
|
|
210
|
+
eprintln!("{} explicit --order requires exactly one --by field", ui::failure("Error:"));
|
|
216
211
|
|
|
217
212
|
process::exit(1);
|
|
218
213
|
}
|
|
219
214
|
|
|
220
215
|
if self.order.len() != 1 {
|
|
221
|
-
eprintln!("{
|
|
216
|
+
eprintln!("{} explicit --order must be a single comma-separated list", ui::failure("Error:"));
|
|
222
217
|
|
|
223
218
|
process::exit(1);
|
|
224
219
|
}
|
|
@@ -236,17 +231,17 @@ impl Args {
|
|
|
236
231
|
let duplicates: Vec<&String> = labels.iter().filter(|label| !seen.insert(label.as_str())).collect();
|
|
237
232
|
|
|
238
233
|
if !duplicates.is_empty() {
|
|
239
|
-
eprintln!("{
|
|
234
|
+
eprintln!("{} --order requires unique values for {by}, but found duplicates", ui::failure("Error:"));
|
|
240
235
|
eprintln!();
|
|
241
|
-
eprintln!(" {
|
|
236
|
+
eprintln!(" {}", ui::strong("Duplicate values:"));
|
|
242
237
|
|
|
243
238
|
for label in &duplicates {
|
|
244
239
|
eprintln!(" {label}");
|
|
245
240
|
}
|
|
246
241
|
|
|
247
242
|
eprintln!();
|
|
248
|
-
eprintln!(" {
|
|
249
|
-
eprintln!(" choose a --by field with unique values (e.g. \".id\")
|
|
243
|
+
eprintln!(" {}", ui::subtle("Use \"yerba sort\" with --by instead to sort by field, or"));
|
|
244
|
+
eprintln!(" {}", ui::subtle("choose a --by field with unique values (e.g. \".id\")"));
|
|
250
245
|
|
|
251
246
|
process::exit(1);
|
|
252
247
|
}
|
|
@@ -256,16 +251,19 @@ impl Args {
|
|
|
256
251
|
if !values_with_commas.is_empty() {
|
|
257
252
|
let selector_display = if selector.is_empty() { String::new() } else { format!(" \"{selector}\"") };
|
|
258
253
|
|
|
259
|
-
eprintln!(
|
|
254
|
+
eprintln!(
|
|
255
|
+
"{} some values for {by} contain commas, which conflicts with --order parsing",
|
|
256
|
+
ui::failure("Error:")
|
|
257
|
+
);
|
|
260
258
|
eprintln!();
|
|
261
|
-
eprintln!(" {
|
|
259
|
+
eprintln!(" {}", ui::strong("Values with commas:"));
|
|
262
260
|
|
|
263
261
|
for label in &values_with_commas {
|
|
264
262
|
eprintln!(" {label}");
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
eprintln!();
|
|
268
|
-
eprintln!(" {
|
|
266
|
+
eprintln!(" {}", ui::strong("Use yerba move to reorder individual items instead:"));
|
|
269
267
|
eprintln!(" yerba move \"{}\"{selector_display} <item> --before/--after <target>", self.file);
|
|
270
268
|
|
|
271
269
|
process::exit(1);
|
|
@@ -277,28 +275,26 @@ impl Args {
|
|
|
277
275
|
match document.reorder_items(container, by, &desired_order) {
|
|
278
276
|
Ok(()) => output(&self.file, &document, self.dry_run),
|
|
279
277
|
Err(error) => {
|
|
280
|
-
eprintln!("{
|
|
278
|
+
eprintln!("{} {}", ui::failure("Error:"), error);
|
|
281
279
|
process::exit(1);
|
|
282
280
|
}
|
|
283
281
|
}
|
|
284
282
|
}
|
|
285
283
|
|
|
286
284
|
fn resolve_labels(&self, document: &yerba::Document, by: &str, selector: &str, items_selector: &str) -> (Vec<String>, Vec<Vec<String>>, String) {
|
|
287
|
-
use super::color::*;
|
|
288
|
-
|
|
289
285
|
let items = document.get_values(items_selector);
|
|
290
286
|
|
|
291
287
|
if items.is_empty() {
|
|
292
288
|
if selector.is_empty() {
|
|
293
|
-
eprintln!("{
|
|
289
|
+
eprintln!("{} no sequence found at root level", ui::failure("Error:"));
|
|
294
290
|
eprintln!();
|
|
295
|
-
eprintln!(" {
|
|
291
|
+
eprintln!(" {}", ui::subtle("If the file is a map, specify which sequence to sort:"));
|
|
296
292
|
eprintln!(" yerba sort \"{}\" \"<selector>\" --by \"{by}\" --order asc", self.file);
|
|
297
293
|
eprintln!();
|
|
298
294
|
|
|
299
295
|
super::show_similar_selectors(&self.file, document, "[]");
|
|
300
296
|
} else {
|
|
301
|
-
eprintln!("{
|
|
297
|
+
eprintln!("{} no sequence found at selector: {selector}", ui::failure("Error:"));
|
|
302
298
|
|
|
303
299
|
super::show_similar_selectors(&self.file, document, selector);
|
|
304
300
|
}
|
|
@@ -316,7 +312,7 @@ impl Args {
|
|
|
316
312
|
};
|
|
317
313
|
|
|
318
314
|
if !document.exists(&by_selector) {
|
|
319
|
-
eprintln!("{
|
|
315
|
+
eprintln!("{} field \"{by}\" not found in items", ui::failure("Error:"));
|
|
320
316
|
|
|
321
317
|
super::show_similar_selectors(&self.file, document, &by_selector);
|
|
322
318
|
|
|
@@ -367,17 +363,15 @@ impl Args {
|
|
|
367
363
|
}
|
|
368
364
|
|
|
369
365
|
fn format_label_line(&self, index: usize, label: &str, context: &[String]) -> String {
|
|
370
|
-
use super::color::*;
|
|
371
|
-
|
|
372
366
|
if context.is_empty() {
|
|
373
|
-
format!("{
|
|
367
|
+
format!("{} {}", ui::subtle(format!("[{}]", index)), label)
|
|
374
368
|
} else {
|
|
375
369
|
let context = context.iter().filter(|c| !c.is_empty()).cloned().collect::<Vec<_>>().join(", ");
|
|
376
370
|
|
|
377
371
|
if context.is_empty() {
|
|
378
|
-
format!("{
|
|
372
|
+
format!("{} {}", ui::subtle(format!("[{}]", index)), label)
|
|
379
373
|
} else {
|
|
380
|
-
format!("{
|
|
374
|
+
format!("{} {} {}", ui::subtle(format!("[{}]", index)), label, ui::subtle(context))
|
|
381
375
|
}
|
|
382
376
|
}
|
|
383
377
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::ui;
|
|
1
2
|
use std::process;
|
|
2
3
|
use std::sync::LazyLock;
|
|
3
4
|
|
|
@@ -41,8 +42,7 @@ impl Args {
|
|
|
41
42
|
let document = parse_file(resolved_file);
|
|
42
43
|
|
|
43
44
|
if let Err(error) = document.validate_sort_keys(&self.selector, &key_order) {
|
|
44
|
-
|
|
45
|
-
eprintln!("{RED}Error in {}{RESET}: {}", resolved_file, error);
|
|
45
|
+
eprintln!("{}: {}", ui::failure(format!("Error in {}", resolved_file)), error);
|
|
46
46
|
has_errors = true;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
use std::io::IsTerminal;
|
|
2
|
+
use std::sync::OnceLock;
|
|
3
|
+
|
|
4
|
+
fn colored() -> bool {
|
|
5
|
+
static COLORED: OnceLock<bool> = OnceLock::new();
|
|
6
|
+
|
|
7
|
+
*COLORED.get_or_init(|| {
|
|
8
|
+
if std::env::var_os("NO_COLOR").is_some() {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if std::env::var_os("CLICOLOR_FORCE").is_some_and(|value| value != "0") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
std::io::stderr().is_terminal() || super::is_github_actions()
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#[derive(Clone, Copy)]
|
|
21
|
+
pub struct Color(&'static str);
|
|
22
|
+
|
|
23
|
+
pub const GREEN: Color = Color("32");
|
|
24
|
+
pub const YELLOW: Color = Color("33");
|
|
25
|
+
pub const RED: Color = Color("31");
|
|
26
|
+
pub const CYAN: Color = Color("36");
|
|
27
|
+
|
|
28
|
+
pub struct Style {
|
|
29
|
+
color: Option<Color>,
|
|
30
|
+
bold: bool,
|
|
31
|
+
dim: bool,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl Style {
|
|
35
|
+
pub const fn plain() -> Self {
|
|
36
|
+
Style {
|
|
37
|
+
color: None,
|
|
38
|
+
bold: false,
|
|
39
|
+
dim: false,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub const fn fg(color: Color) -> Self {
|
|
44
|
+
Style {
|
|
45
|
+
color: Some(color),
|
|
46
|
+
bold: false,
|
|
47
|
+
dim: false,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub const fn bold(mut self) -> Self {
|
|
52
|
+
self.bold = true;
|
|
53
|
+
self
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub const fn dim(mut self) -> Self {
|
|
57
|
+
self.dim = true;
|
|
58
|
+
self
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fn paint(&self, text: impl std::fmt::Display) -> String {
|
|
62
|
+
if !colored() {
|
|
63
|
+
return text.to_string();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let mut codes: Vec<&str> = Vec::new();
|
|
67
|
+
|
|
68
|
+
if self.bold {
|
|
69
|
+
codes.push("1");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if self.dim {
|
|
73
|
+
codes.push("2");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if let Some(color) = &self.color {
|
|
77
|
+
codes.push(color.0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if codes.is_empty() {
|
|
81
|
+
return text.to_string();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
format!("\x1b[{}m{}\x1b[0m", codes.join(";"), text)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fn success(text: impl std::fmt::Display) -> String {
|
|
89
|
+
Style::fg(GREEN).bold().paint(text)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn pending(text: impl std::fmt::Display) -> String {
|
|
93
|
+
Style::fg(YELLOW).bold().paint(text)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub fn failure(text: impl std::fmt::Display) -> String {
|
|
97
|
+
Style::fg(RED).bold().paint(text)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub fn muted(text: impl std::fmt::Display) -> String {
|
|
101
|
+
Style::fg(CYAN).paint(text)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn subtle(text: impl std::fmt::Display) -> String {
|
|
105
|
+
Style::plain().dim().paint(text)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pub fn strong(text: impl std::fmt::Display) -> String {
|
|
109
|
+
Style::plain().bold().paint(text)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn banner() -> String {
|
|
113
|
+
format!("🧉 {} {}", Style::fg(GREEN).bold().paint("yerba"), subtle(format!("v{}", yerba::version())))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub const INDENT: &str = " ";
|
|
117
|
+
pub const STATUS_INDENT: &str = " ";
|
|
118
|
+
|
|
119
|
+
pub mod glyph {
|
|
120
|
+
pub const OK: &str = "✓";
|
|
121
|
+
pub const PENDING: &str = "~";
|
|
122
|
+
pub const FAIL: &str = "✗";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub mod raw {
|
|
126
|
+
fn sequence(code: &'static str) -> &'static str {
|
|
127
|
+
if super::colored() {
|
|
128
|
+
code
|
|
129
|
+
} else {
|
|
130
|
+
""
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub fn green() -> &'static str {
|
|
135
|
+
sequence("\x1b[32m")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub fn yellow() -> &'static str {
|
|
139
|
+
sequence("\x1b[33m")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub fn bold() -> &'static str {
|
|
143
|
+
sequence("\x1b[1m")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
pub fn dim() -> &'static str {
|
|
147
|
+
sequence("\x1b[2m")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pub fn italic() -> &'static str {
|
|
151
|
+
sequence("\x1b[3m")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pub fn reset() -> &'static str {
|
|
155
|
+
sequence("\x1b[0m")
|
|
156
|
+
}
|
|
157
|
+
}
|
data/rust/src/commands/unique.rs
CHANGED
|
@@ -37,7 +37,7 @@ pub struct Args {
|
|
|
37
37
|
|
|
38
38
|
impl Args {
|
|
39
39
|
pub fn run(self) {
|
|
40
|
-
use super::
|
|
40
|
+
use super::ui;
|
|
41
41
|
|
|
42
42
|
let selector = self.selector.as_deref().unwrap_or("");
|
|
43
43
|
let by = self.by.as_deref().unwrap_or(".");
|
|
@@ -50,20 +50,20 @@ impl Args {
|
|
|
50
50
|
let noun = if duplicates.len() == 1 { "duplicate" } else { "duplicates" };
|
|
51
51
|
|
|
52
52
|
if duplicates.is_empty() {
|
|
53
|
-
eprintln!("{
|
|
53
|
+
eprintln!("{} in {}", ui::success("No duplicates found"), resolved_file);
|
|
54
54
|
} else if self.remove {
|
|
55
|
-
eprintln!("{
|
|
55
|
+
eprintln!("{} from {}", ui::pending(format!("Removed {} {noun}", duplicates.len())), resolved_file);
|
|
56
56
|
|
|
57
57
|
for duplicate in &duplicates {
|
|
58
|
-
eprintln!(" {
|
|
58
|
+
eprintln!(" {}", ui::subtle(format!("line {}: {} == {}", duplicate.line, by, duplicate.value)));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
output(&resolved_file, &document, self.dry_run);
|
|
62
62
|
} else {
|
|
63
|
-
eprintln!("{
|
|
63
|
+
eprintln!("{} in {}", ui::failure(format!("Found {} {noun}", duplicates.len())), resolved_file);
|
|
64
64
|
|
|
65
65
|
for duplicate in &duplicates {
|
|
66
|
-
eprintln!(" {
|
|
66
|
+
eprintln!(" {}", ui::subtle(format!("line {}: {} == {}", duplicate.line, by, duplicate.value)));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
process::exit(1);
|
|
@@ -71,7 +71,7 @@ impl Args {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
Err(error) => {
|
|
74
|
-
eprintln!("{
|
|
74
|
+
eprintln!("{} {}", ui::failure("Error:"), error);
|
|
75
75
|
process::exit(1);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
use super::
|
|
1
|
+
use super::ui;
|
|
2
2
|
|
|
3
3
|
pub fn run() {
|
|
4
|
-
println!("
|
|
4
|
+
println!("{}", ui::banner());
|
|
5
5
|
println!(
|
|
6
|
-
" {
|
|
6
|
+
" {}{} {}{} {}{} {}{} {}{}",
|
|
7
|
+
ui::strong("Y"),
|
|
8
|
+
ui::subtle("AML"),
|
|
9
|
+
ui::strong("E"),
|
|
10
|
+
ui::subtle("diting and"),
|
|
11
|
+
ui::strong("R"),
|
|
12
|
+
ui::subtle("efactoring with"),
|
|
13
|
+
ui::strong("B"),
|
|
14
|
+
ui::subtle("etter"),
|
|
15
|
+
ui::strong("A"),
|
|
16
|
+
ui::subtle("ccuracy")
|
|
7
17
|
);
|
|
8
18
|
}
|
|
@@ -1,29 +1,83 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
+
const CONDITION_OPERATORS: &str = "expected `<selector> <operator> <value>`, where operator is one of ==, !=, contains, not_contains";
|
|
4
|
+
|
|
5
|
+
pub fn validate_condition(condition: &str) -> Result<(), YerbaError> {
|
|
6
|
+
let trimmed = condition.trim();
|
|
7
|
+
|
|
8
|
+
if trimmed.is_empty() {
|
|
9
|
+
return Err(YerbaError::InvalidCondition(condition.to_string(), "condition is empty".to_string()));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let Some((left, _operator, _right)) = parse_condition(trimmed) else {
|
|
13
|
+
return Err(YerbaError::InvalidCondition(condition.to_string(), CONDITION_OPERATORS.to_string()));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if crate::selector::Selector::parse(&left).is_empty() {
|
|
17
|
+
return Err(YerbaError::InvalidCondition(
|
|
18
|
+
condition.to_string(),
|
|
19
|
+
"condition is missing a selector on the left of the operator".to_string(),
|
|
20
|
+
));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Ok(())
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn validate_item_condition(condition: &str) -> Result<(), YerbaError> {
|
|
27
|
+
validate_condition(condition)?;
|
|
28
|
+
|
|
29
|
+
let trimmed = condition.trim();
|
|
30
|
+
|
|
31
|
+
let Some((left, _operator, _right)) = parse_condition(trimmed) else {
|
|
32
|
+
return Ok(());
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if !crate::selector::Selector::parse(&left).is_relative() {
|
|
36
|
+
return Err(YerbaError::InvalidCondition(
|
|
37
|
+
condition.to_string(),
|
|
38
|
+
format!(
|
|
39
|
+
"selector \"{}\" is absolute, but this condition is tested against each item, so it must be relative and start with `.` — did you mean \".{}\"?",
|
|
40
|
+
left.trim(),
|
|
41
|
+
left.trim()
|
|
42
|
+
),
|
|
43
|
+
));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Ok(())
|
|
47
|
+
}
|
|
48
|
+
|
|
3
49
|
impl Document {
|
|
4
|
-
pub fn filter(&self, dot_path: &str, condition: &str) -> Vec<yaml_serde::Value> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
50
|
+
pub fn filter(&self, dot_path: &str, condition: &str) -> Result<Vec<yaml_serde::Value>, YerbaError> {
|
|
51
|
+
validate_item_condition(condition)?;
|
|
52
|
+
|
|
53
|
+
Ok(
|
|
54
|
+
self
|
|
55
|
+
.navigate_all_compact(dot_path)
|
|
56
|
+
.iter()
|
|
57
|
+
.filter(|node| self.evaluate_condition_on_node(node, condition))
|
|
58
|
+
.map(node_to_yaml_value)
|
|
59
|
+
.collect(),
|
|
60
|
+
)
|
|
11
61
|
}
|
|
12
62
|
|
|
13
|
-
pub fn filter_with_selectors(&self, dot_path: &str, condition: &str) -> Vec<(yaml_serde::Value, String, usize)> {
|
|
63
|
+
pub fn filter_with_selectors(&self, dot_path: &str, condition: &str) -> Result<Vec<(yaml_serde::Value, String, usize)>, YerbaError> {
|
|
64
|
+
validate_item_condition(condition)?;
|
|
65
|
+
|
|
14
66
|
let source = self.source_text();
|
|
15
67
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
68
|
+
Ok(
|
|
69
|
+
self
|
|
70
|
+
.navigate_all_compact(dot_path)
|
|
71
|
+
.iter()
|
|
72
|
+
.filter(|node| self.evaluate_condition_on_node(node, condition))
|
|
73
|
+
.map(|node| {
|
|
74
|
+
let offset: usize = node.text_range().start().into();
|
|
75
|
+
let line = line_at(&source, offset);
|
|
76
|
+
|
|
77
|
+
(node_to_yaml_value(node), super::get::node_selector(node), line)
|
|
78
|
+
})
|
|
79
|
+
.collect(),
|
|
80
|
+
)
|
|
27
81
|
}
|
|
28
82
|
|
|
29
83
|
pub(super) fn evaluate_condition_on_node(&self, node: &SyntaxNode, condition: &str) -> bool {
|
|
@@ -84,31 +138,32 @@ impl Document {
|
|
|
84
138
|
}
|
|
85
139
|
}
|
|
86
140
|
|
|
87
|
-
pub fn evaluate_condition(&self, parent_path: &str, condition: &str) -> bool {
|
|
141
|
+
pub fn evaluate_condition(&self, parent_path: &str, condition: &str) -> Result<bool, YerbaError> {
|
|
142
|
+
if parent_path.is_empty() {
|
|
143
|
+
validate_condition(condition)?;
|
|
144
|
+
} else {
|
|
145
|
+
validate_item_condition(condition)?;
|
|
146
|
+
}
|
|
147
|
+
|
|
88
148
|
let condition = condition.trim();
|
|
89
149
|
|
|
90
150
|
let (left, operator, right) = match parse_condition(condition) {
|
|
91
151
|
Some(parts) => parts,
|
|
92
|
-
None => return false,
|
|
152
|
+
None => return Ok(false),
|
|
93
153
|
};
|
|
94
154
|
|
|
95
155
|
let path = crate::selector::Selector::parse(&left);
|
|
156
|
+
let path_string = path.to_selector_string();
|
|
96
157
|
|
|
97
|
-
let full_path = if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if parent_path.is_empty() {
|
|
101
|
-
path_string
|
|
102
|
-
} else {
|
|
103
|
-
format!("{}.{}", parent_path, path_string)
|
|
104
|
-
}
|
|
158
|
+
let full_path = if parent_path.is_empty() {
|
|
159
|
+
path_string
|
|
105
160
|
} else {
|
|
106
|
-
|
|
161
|
+
format!("{}.{}", parent_path, path_string)
|
|
107
162
|
};
|
|
108
163
|
|
|
109
164
|
let has_brackets = crate::selector::Selector::parse(&full_path).has_brackets();
|
|
110
165
|
|
|
111
|
-
match operator {
|
|
166
|
+
let result = match operator {
|
|
112
167
|
"==" => {
|
|
113
168
|
if has_brackets {
|
|
114
169
|
self.get_all(&full_path).iter().any(|value| value == &right)
|
|
@@ -150,6 +205,8 @@ impl Document {
|
|
|
150
205
|
}
|
|
151
206
|
}
|
|
152
207
|
_ => false,
|
|
153
|
-
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
Ok(result)
|
|
154
211
|
}
|
|
155
212
|
}
|
data/rust/src/document/delete.rs
CHANGED
|
@@ -31,6 +31,7 @@ impl Document {
|
|
|
31
31
|
|
|
32
32
|
pub fn delete(&mut self, dot_path: &str) -> Result<(), YerbaError> {
|
|
33
33
|
Self::validate_path(dot_path)?;
|
|
34
|
+
self.refuse_flow_target(dot_path)?;
|
|
34
35
|
|
|
35
36
|
let selector = crate::selector::Selector::parse(dot_path);
|
|
36
37
|
let segments = selector.segments();
|
|
@@ -88,13 +89,17 @@ impl Document {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
crate::selector::SelectorSegment::Index(index) => self.remove_at(&parent_path, *index),
|
|
91
|
-
crate::selector::SelectorSegment::AllItems => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
|
|
92
|
+
crate::selector::SelectorSegment::AllItems | crate::selector::SelectorSegment::AllKeys => Err(YerbaError::SelectorNotFound(dot_path.to_string())),
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
pub fn remove(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
96
97
|
let current_node = self.navigate(dot_path)?;
|
|
97
98
|
|
|
99
|
+
if find_flow_sequence(¤t_node).is_some() {
|
|
100
|
+
return Err(YerbaError::FlowCollectionNotWritable(dot_path.to_string()));
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
let sequence = find_block_sequence(¤t_node).ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
99
104
|
|
|
100
105
|
let target_entry = sequence
|