yerba 0.2.1-x86_64-linux-gnu → 0.3.0-x86_64-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 +78 -5
- data/exe/x86_64-linux-gnu/yerba +0 -0
- data/ext/yerba/extconf.rb +17 -9
- data/ext/yerba/include/yerba.h +169 -0
- data/ext/yerba/yerba.c +8 -1
- 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/version.rb +1 -1
- data/lib/yerba.rb +7 -2
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +2 -2
- data/rust/src/commands/delete.rs +9 -4
- data/rust/src/commands/get.rs +56 -13
- data/rust/src/commands/insert.rs +8 -4
- data/rust/src/commands/mod.rs +72 -5
- data/rust/src/commands/move_item.rs +2 -1
- data/rust/src/commands/move_key.rs +2 -1
- data/rust/src/commands/remove.rs +8 -4
- data/rust/src/commands/rename.rs +8 -4
- data/rust/src/commands/selectors.rs +174 -0
- data/rust/src/commands/set.rs +33 -16
- data/rust/src/commands/sort.rs +477 -10
- data/rust/src/didyoumean.rs +55 -0
- data/rust/src/document.rs +118 -51
- data/rust/src/error.rs +12 -2
- data/rust/src/ffi.rs +8 -3
- data/rust/src/lib.rs +2 -1
- data/rust/src/main.rs +2 -0
- metadata +6 -2
data/rust/src/commands/mod.rs
CHANGED
|
@@ -11,6 +11,7 @@ pub mod move_key;
|
|
|
11
11
|
pub mod quote_style;
|
|
12
12
|
pub mod remove;
|
|
13
13
|
pub mod rename;
|
|
14
|
+
pub mod selectors;
|
|
14
15
|
pub mod set;
|
|
15
16
|
pub mod sort;
|
|
16
17
|
pub mod sort_keys;
|
|
@@ -153,6 +154,7 @@ pub enum Command {
|
|
|
153
154
|
Sort(sort::Args),
|
|
154
155
|
QuoteStyle(quote_style::Args),
|
|
155
156
|
BlankLines(blank_lines::Args),
|
|
157
|
+
Selectors(selectors::Args),
|
|
156
158
|
#[command(about = "Create a new Yerbafile in the current directory")]
|
|
157
159
|
Init,
|
|
158
160
|
#[command(about = "Apply all rules from the Yerbafile and write changes")]
|
|
@@ -180,6 +182,7 @@ impl Command {
|
|
|
180
182
|
Command::Sort(args) => args.run(),
|
|
181
183
|
Command::QuoteStyle(args) => args.run(),
|
|
182
184
|
Command::BlankLines(args) => args.run(),
|
|
185
|
+
Command::Selectors(args) => args.run(),
|
|
183
186
|
Command::Init => init::run(),
|
|
184
187
|
Command::Apply => apply::run(),
|
|
185
188
|
Command::Check => check::run(),
|
|
@@ -224,7 +227,7 @@ pub(crate) fn run_yerbafile(write: bool) {
|
|
|
224
227
|
}
|
|
225
228
|
|
|
226
229
|
if !has_changes && !has_errors {
|
|
227
|
-
eprintln!("
|
|
230
|
+
eprintln!("\n{BOLD}{GREEN}All files match the rules.{RESET}");
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
if !write && has_changes {
|
|
@@ -327,13 +330,77 @@ pub(crate) fn parse_file(file: &str) -> yerba::Document {
|
|
|
327
330
|
})
|
|
328
331
|
}
|
|
329
332
|
|
|
330
|
-
pub(crate) fn run_op(
|
|
333
|
+
pub(crate) fn run_op(file: &str, document: &yerba::Document, result: Result<(), yerba::YerbaError>) {
|
|
334
|
+
run_op_with_hint(file, document, result, None);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
pub(crate) fn run_op_with_hint(
|
|
338
|
+
file: &str,
|
|
339
|
+
document: &yerba::Document,
|
|
340
|
+
result: Result<(), yerba::YerbaError>,
|
|
341
|
+
hint: Option<&str>,
|
|
342
|
+
) {
|
|
331
343
|
use color::*;
|
|
332
344
|
|
|
333
|
-
|
|
334
|
-
|
|
345
|
+
if let Err(error) = result {
|
|
346
|
+
if let yerba::YerbaError::SelectorNotFound(selector) = &error {
|
|
347
|
+
eprintln!("{RED}Error:{RESET} selector \"{selector}\" not found in {file}");
|
|
348
|
+
|
|
349
|
+
show_similar_selectors(file, document, selector);
|
|
350
|
+
} else {
|
|
351
|
+
eprintln!("{RED}Error:{RESET} {}", error);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if let Some(hint) = hint {
|
|
355
|
+
if matches!(error, yerba::YerbaError::SelectorNotFound(_)) {
|
|
356
|
+
eprintln!();
|
|
357
|
+
eprintln!(" {DIM}Hint: {hint}{RESET}");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
335
361
|
process::exit(1);
|
|
336
|
-
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, invalid_path: &str) {
|
|
366
|
+
use color::*;
|
|
367
|
+
use yerba::didyoumean::didyoumean_ranked;
|
|
368
|
+
|
|
369
|
+
let selectors = document.selectors();
|
|
370
|
+
|
|
371
|
+
if selectors.is_empty() {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let query = invalid_path.split_whitespace().next().unwrap_or(invalid_path);
|
|
376
|
+
let threshold = query.len() / 2 + 3;
|
|
377
|
+
let close = didyoumean_ranked(query, &selectors, threshold);
|
|
378
|
+
|
|
379
|
+
eprintln!();
|
|
380
|
+
|
|
381
|
+
if close.is_empty() {
|
|
382
|
+
eprintln!(" {BOLD}Available selectors in {file}:{RESET}");
|
|
383
|
+
|
|
384
|
+
for selector in selectors.iter().take(10) {
|
|
385
|
+
eprintln!(" {DIM}{selector}{RESET}");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if selectors.len() > 10 {
|
|
389
|
+
eprintln!(" {DIM}... and {} more{RESET}", selectors.len() - 10);
|
|
390
|
+
}
|
|
391
|
+
} else if close.len() == 1 {
|
|
392
|
+
eprintln!(" {BOLD}Did you mean this selector?{RESET} {}", close[0]);
|
|
393
|
+
} else {
|
|
394
|
+
eprintln!(" {BOLD}Did you mean one of these selectors?{RESET}");
|
|
395
|
+
|
|
396
|
+
for selector in close.iter().take(5) {
|
|
397
|
+
eprintln!(" {selector}");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
eprintln!();
|
|
402
|
+
eprintln!(" {BOLD}To see all valid selectors, run:{RESET}");
|
|
403
|
+
eprintln!(" yerba selectors \"{file}\"{RESET}");
|
|
337
404
|
}
|
|
338
405
|
|
|
339
406
|
pub(crate) fn output(file: &str, document: &yerba::Document, dry_run: bool) {
|
|
@@ -48,7 +48,8 @@ impl Args {
|
|
|
48
48
|
|document, path, reference| document.resolve_sequence_index(path, reference),
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
let result = document.move_item(&self.selector, from_index, to_index);
|
|
52
|
+
run_op(&self.file, &document, result);
|
|
52
53
|
output(&self.file, &document, self.dry_run);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -81,7 +81,8 @@ impl Args {
|
|
|
81
81
|
|document, parent_path, reference| document.resolve_key_index(parent_path, reference),
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
let result = document.move_key(parent_path, from_index, to_index);
|
|
85
|
+
run_op(&self.file, &document, result);
|
|
85
86
|
output(&self.file, &document, self.dry_run);
|
|
86
87
|
}
|
|
87
88
|
}
|
data/rust/src/commands/remove.rs
CHANGED
|
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
|
|
3
3
|
use indoc::indoc;
|
|
4
4
|
|
|
5
5
|
use super::colorize_examples;
|
|
6
|
-
use super::{output, parse_file, run_op};
|
|
6
|
+
use super::{output, parse_file, resolve_files, run_op};
|
|
7
7
|
|
|
8
8
|
static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
9
9
|
colorize_examples(indoc! {r#"
|
|
@@ -28,8 +28,12 @@ pub struct Args {
|
|
|
28
28
|
|
|
29
29
|
impl Args {
|
|
30
30
|
pub fn run(self) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
for resolved_file in resolve_files(&self.file) {
|
|
32
|
+
let mut document = parse_file(&resolved_file);
|
|
33
|
+
let result = document.remove(&self.selector, &self.value);
|
|
34
|
+
|
|
35
|
+
run_op(&resolved_file, &document, result);
|
|
36
|
+
output(&resolved_file, &document, self.dry_run);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
}
|
data/rust/src/commands/rename.rs
CHANGED
|
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
|
|
3
3
|
use indoc::indoc;
|
|
4
4
|
|
|
5
5
|
use super::colorize_examples;
|
|
6
|
-
use super::{output, parse_file, run_op};
|
|
6
|
+
use super::{output, parse_file, resolve_files, run_op};
|
|
7
7
|
|
|
8
8
|
static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
9
9
|
colorize_examples(indoc! {r#"
|
|
@@ -30,8 +30,12 @@ pub struct Args {
|
|
|
30
30
|
|
|
31
31
|
impl Args {
|
|
32
32
|
pub fn run(self) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
for resolved_file in resolve_files(&self.file) {
|
|
34
|
+
let mut document = parse_file(&resolved_file);
|
|
35
|
+
let result = document.rename(&self.source, &self.destination);
|
|
36
|
+
|
|
37
|
+
run_op(&resolved_file, &document, result);
|
|
38
|
+
output(&resolved_file, &document, self.dry_run);
|
|
39
|
+
}
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
use std::collections::BTreeMap;
|
|
2
|
+
use std::sync::LazyLock;
|
|
3
|
+
|
|
4
|
+
use indoc::indoc;
|
|
5
|
+
|
|
6
|
+
use super::colorize_examples;
|
|
7
|
+
use super::{color, parse_file, resolve_files};
|
|
8
|
+
|
|
9
|
+
static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
10
|
+
colorize_examples(indoc! {r#"
|
|
11
|
+
yerba selectors config.yml
|
|
12
|
+
yerba selectors config.yml "database"
|
|
13
|
+
yerba selectors videos.yml "[]"
|
|
14
|
+
yerba selectors videos.yml "[].speakers"
|
|
15
|
+
yerba selectors "data/**/videos.yml"
|
|
16
|
+
yerba selectors "data/**/videos.yml" "[].talks"
|
|
17
|
+
"#})
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
#[derive(clap::Args)]
|
|
21
|
+
#[command(
|
|
22
|
+
about = "Show all valid selectors in a YAML file",
|
|
23
|
+
arg_required_else_help = true,
|
|
24
|
+
after_help = EXAMPLES.as_str()
|
|
25
|
+
)]
|
|
26
|
+
pub struct Args {
|
|
27
|
+
file: String,
|
|
28
|
+
/// Show selectors starting from this path
|
|
29
|
+
selector: Option<String>,
|
|
30
|
+
/// Sort selectors alphabetically (default: document order)
|
|
31
|
+
#[arg(long)]
|
|
32
|
+
sorted: bool,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[derive(Debug, Default)]
|
|
36
|
+
struct SelectorInfo {
|
|
37
|
+
min_count: Option<usize>,
|
|
38
|
+
max_count: Option<usize>,
|
|
39
|
+
order: usize,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl SelectorInfo {
|
|
43
|
+
fn record_count(&mut self, count: usize) {
|
|
44
|
+
self.min_count = Some(self.min_count.map_or(count, |min| min.min(count)));
|
|
45
|
+
self.max_count = Some(self.max_count.map_or(count, |max| max.max(count)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn count_label(&self) -> Option<String> {
|
|
49
|
+
match (self.min_count, self.max_count) {
|
|
50
|
+
(Some(0), Some(0)) => Some("empty".to_string()),
|
|
51
|
+
|
|
52
|
+
(Some(min), Some(max)) if min == max => {
|
|
53
|
+
if max <= 5 {
|
|
54
|
+
let indices: Vec<String> = (0..max).map(|i| format!("[{}]", i)).collect();
|
|
55
|
+
|
|
56
|
+
Some(indices.join(", "))
|
|
57
|
+
} else {
|
|
58
|
+
Some(format!("[0]..[{}]", max - 1))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
(Some(min), Some(max)) => {
|
|
63
|
+
if max <= 5 {
|
|
64
|
+
let indices: Vec<String> = (0..max).map(|i| format!("[{}]", i)).collect();
|
|
65
|
+
|
|
66
|
+
Some(format!("{} ({}-{} items)", indices.join(", "), min, max))
|
|
67
|
+
} else {
|
|
68
|
+
Some(format!("[0]..[{}] ({}-{} items)", max - 1, min, max))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
_ => None,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl Args {
|
|
77
|
+
pub fn run(self) {
|
|
78
|
+
let mut all_selectors: BTreeMap<String, SelectorInfo> = BTreeMap::new();
|
|
79
|
+
let mut counter: usize = 1;
|
|
80
|
+
let selector = self.selector.as_deref().unwrap_or("");
|
|
81
|
+
|
|
82
|
+
for resolved_file in resolve_files(&self.file) {
|
|
83
|
+
let document = parse_file(&resolved_file);
|
|
84
|
+
let prefix = if selector.is_empty() {
|
|
85
|
+
String::new()
|
|
86
|
+
} else {
|
|
87
|
+
selector.to_string()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
let values = if selector.is_empty() {
|
|
91
|
+
document.get_value("").into_iter().collect::<Vec<_>>()
|
|
92
|
+
} else {
|
|
93
|
+
document.get_values(selector)
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
for value in values {
|
|
97
|
+
collect_selectors(&value, &prefix, &mut all_selectors, &mut counter);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let mut entries: Vec<(&String, &SelectorInfo)> = all_selectors.iter().collect();
|
|
102
|
+
|
|
103
|
+
if self.sorted {
|
|
104
|
+
entries.sort_by_key(|(name, _)| name.to_string());
|
|
105
|
+
} else {
|
|
106
|
+
entries.sort_by_key(|(_, info)| info.order);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let max_selector_len = entries.iter().map(|(selector, _)| selector.len()).max().unwrap_or(0);
|
|
110
|
+
|
|
111
|
+
for (selector, info) in &entries {
|
|
112
|
+
if let Some(label) = info.count_label() {
|
|
113
|
+
let padding = max_selector_len - selector.len() + 2;
|
|
114
|
+
|
|
115
|
+
println!(
|
|
116
|
+
"{}{}{}{}{}",
|
|
117
|
+
selector,
|
|
118
|
+
" ".repeat(padding),
|
|
119
|
+
color::DIM,
|
|
120
|
+
label,
|
|
121
|
+
color::RESET
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
println!("{}", selector);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn collect_selectors(
|
|
131
|
+
value: &serde_yaml::Value,
|
|
132
|
+
prefix: &str,
|
|
133
|
+
selectors: &mut BTreeMap<String, SelectorInfo>,
|
|
134
|
+
counter: &mut usize,
|
|
135
|
+
) {
|
|
136
|
+
match value {
|
|
137
|
+
serde_yaml::Value::Mapping(map) => {
|
|
138
|
+
for (key, child) in map {
|
|
139
|
+
if let serde_yaml::Value::String(key_string) = key {
|
|
140
|
+
let selector = if prefix.is_empty() {
|
|
141
|
+
key_string.clone()
|
|
142
|
+
} else {
|
|
143
|
+
format!("{}.{}", prefix, key_string)
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
let entry = selectors.entry(selector.clone()).or_default();
|
|
147
|
+
if entry.order == 0 {
|
|
148
|
+
entry.order = *counter;
|
|
149
|
+
*counter += 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
collect_selectors(child, &selector, selectors, counter);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
serde_yaml::Value::Sequence(sequence) => {
|
|
158
|
+
let bracket_prefix = format!("{}[]", prefix);
|
|
159
|
+
|
|
160
|
+
let entry = selectors.entry(bracket_prefix.clone()).or_default();
|
|
161
|
+
entry.record_count(sequence.len());
|
|
162
|
+
if entry.order == 0 {
|
|
163
|
+
entry.order = *counter;
|
|
164
|
+
*counter += 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for item in sequence {
|
|
168
|
+
collect_selectors(item, &bracket_prefix, selectors, counter);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_ => {}
|
|
173
|
+
}
|
|
174
|
+
}
|
data/rust/src/commands/set.rs
CHANGED
|
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
|
|
3
3
|
use indoc::indoc;
|
|
4
4
|
|
|
5
5
|
use super::colorize_examples;
|
|
6
|
-
use super::{output, parse_file,
|
|
6
|
+
use super::{output, parse_file, resolve_files, run_op_with_hint};
|
|
7
7
|
|
|
8
8
|
static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
9
9
|
colorize_examples(indoc! {r#"
|
|
@@ -12,6 +12,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
|
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
14
|
yerba set "data/**/event.yml" "website" "" --if-exists
|
|
15
|
+
yerba set videos.yml "[].description" "" --all
|
|
15
16
|
"#})
|
|
16
17
|
});
|
|
17
18
|
|
|
@@ -32,28 +33,44 @@ pub struct Args {
|
|
|
32
33
|
#[arg(long)]
|
|
33
34
|
condition: Option<String>,
|
|
34
35
|
#[arg(long)]
|
|
36
|
+
all: bool,
|
|
37
|
+
#[arg(long)]
|
|
35
38
|
dry_run: bool,
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
impl Args {
|
|
39
42
|
pub fn run(self) {
|
|
40
|
-
let mut document = parse_file(&self.file);
|
|
41
43
|
let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
document
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
45
|
+
for resolved_file in resolve_files(&self.file) {
|
|
46
|
+
let mut document = parse_file(&resolved_file);
|
|
47
|
+
|
|
48
|
+
let should_set = if self.if_exists {
|
|
49
|
+
document.exists(&self.selector)
|
|
50
|
+
} else if self.if_missing {
|
|
51
|
+
!document.exists(&self.selector)
|
|
52
|
+
} else if let Some(condition) = &self.condition {
|
|
53
|
+
document.evaluate_condition(parent_path, condition)
|
|
54
|
+
} else {
|
|
55
|
+
true
|
|
56
|
+
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
if should_set {
|
|
59
|
+
let result = if self.all {
|
|
60
|
+
document.set_all(&self.selector, &self.value)
|
|
61
|
+
} else {
|
|
62
|
+
document.set(&self.selector, &self.value)
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
run_op_with_hint(
|
|
66
|
+
&resolved_file,
|
|
67
|
+
&document,
|
|
68
|
+
result,
|
|
69
|
+
Some("Use --if-exists to skip files where the selector is missing"),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
output(&resolved_file, &document, self.dry_run);
|
|
74
|
+
}
|
|
58
75
|
}
|
|
59
76
|
}
|