yerba 0.2.1-arm-linux-gnu → 0.3.0-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.
@@ -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!(" {GREEN}All files match the rules.{RESET}");
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(operation: impl FnOnce() -> Result<(), yerba::YerbaError>) {
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
- operation().unwrap_or_else(|error| {
334
- eprintln!("{RED}Error:{RESET} {}", error);
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
- run_op(|| document.move_item(&self.selector, from_index, to_index));
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
- run_op(|| document.move_key(parent_path, from_index, to_index));
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
  }
@@ -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
- let mut document = parse_file(&self.file);
32
- run_op(|| document.remove(&self.selector, &self.value));
33
- output(&self.file, &document, self.dry_run);
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
  }
@@ -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
- let mut document = parse_file(&self.file);
34
- run_op(|| document.rename(&self.source, &self.destination));
35
- output(&self.file, &document, self.dry_run);
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
+ }
@@ -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_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
- let should_set = if self.if_exists {
44
- document.exists(&self.selector)
45
- } else if self.if_missing {
46
- !document.exists(&self.selector)
47
- } else if let Some(condition) = &self.condition {
48
- document.evaluate_condition(parent_path, condition)
49
- } else {
50
- true
51
- };
52
-
53
- if should_set {
54
- run_op(|| document.set(&self.selector, &self.value));
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
- output(&self.file, &document, self.dry_run);
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
  }