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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -2
  3. data/exe/arm-linux-gnu/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +8 -0
  5. data/ext/yerba/yerba.c +70 -8
  6. data/lib/yerba/3.2/yerba.so +0 -0
  7. data/lib/yerba/3.3/yerba.so +0 -0
  8. data/lib/yerba/3.4/yerba.so +0 -0
  9. data/lib/yerba/4.0/yerba.so +0 -0
  10. data/lib/yerba/map.rb +15 -10
  11. data/lib/yerba/node.rb +2 -2
  12. data/lib/yerba/sequence.rb +13 -1
  13. data/lib/yerba/version.rb +1 -1
  14. data/rust/Cargo.lock +2 -342
  15. data/rust/Cargo.toml +15 -6
  16. data/rust/src/commands/blank_lines.rs +2 -2
  17. data/rust/src/commands/directives.rs +4 -4
  18. data/rust/src/commands/get.rs +17 -10
  19. data/rust/src/commands/init.rs +4 -4
  20. data/rust/src/commands/insert.rs +6 -6
  21. data/rust/src/commands/location.rs +2 -2
  22. data/rust/src/commands/mate.rs +7 -7
  23. data/rust/src/commands/mod.rs +119 -90
  24. data/rust/src/commands/move_key.rs +9 -6
  25. data/rust/src/commands/quote_style.rs +4 -5
  26. data/rust/src/commands/rename.rs +3 -3
  27. data/rust/src/commands/schema.rs +6 -6
  28. data/rust/src/commands/selectors.rs +2 -2
  29. data/rust/src/commands/set.rs +12 -1
  30. data/rust/src/commands/sort.rs +36 -42
  31. data/rust/src/commands/sort_keys.rs +2 -2
  32. data/rust/src/commands/ui.rs +157 -0
  33. data/rust/src/commands/unique.rs +7 -7
  34. data/rust/src/commands/version.rs +13 -3
  35. data/rust/src/document/condition.rs +89 -32
  36. data/rust/src/document/delete.rs +6 -1
  37. data/rust/src/document/get.rs +94 -26
  38. data/rust/src/document/insert.rs +26 -0
  39. data/rust/src/document/mod.rs +73 -20
  40. data/rust/src/document/set.rs +91 -2
  41. data/rust/src/error.rs +17 -0
  42. data/rust/src/ffi.rs +76 -9
  43. data/rust/src/json.rs +10 -1
  44. data/rust/src/lib.rs +23 -7
  45. data/rust/src/main.rs +1 -0
  46. data/rust/src/quote_style.rs +12 -11
  47. data/rust/src/selector.rs +19 -3
  48. data/rust/src/syntax.rs +178 -26
  49. data/rust/src/yaml_writer.rs +4 -4
  50. data/rust/src/yerbafile.rs +30 -6
  51. metadata +3 -2
@@ -1,3 +1,4 @@
1
+ use super::ui;
1
2
  use std::sync::LazyLock;
2
3
 
3
4
  use indoc::indoc;
@@ -28,6 +29,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
28
29
  pub struct Args {
29
30
  file: String,
30
31
  selector: String,
32
+ #[arg(allow_hyphen_values = true)]
31
33
  value: Option<String>,
32
34
  #[arg(long, help = "Read value from a file (use - for stdin)")]
33
35
  from: Option<String>,
@@ -49,8 +51,8 @@ impl Args {
49
51
  let mut buffer = String::new();
50
52
 
51
53
  std::io::stdin().read_to_string(&mut buffer).unwrap_or_else(|error| {
52
- use super::color::*;
53
- eprintln!("{RED}Error:{RESET} reading stdin: {}", error);
54
+ use super::ui;
55
+ eprintln!("{} reading stdin: {}", ui::failure("Error:"), error);
54
56
 
55
57
  std::process::exit(1);
56
58
  });
@@ -59,8 +61,7 @@ impl Args {
59
61
  } else {
60
62
  std::fs::read_to_string(&from_path)
61
63
  .unwrap_or_else(|error| {
62
- use super::color::*;
63
- eprintln!("{RED}Error:{RESET} reading {}: {}", from_path, error);
64
+ eprintln!("{} reading {}: {}", ui::failure("Error:"), from_path, error);
64
65
  std::process::exit(1);
65
66
  })
66
67
  .trim()
@@ -69,8 +70,7 @@ impl Args {
69
70
  } else if let Some(val) = self.value {
70
71
  val
71
72
  } else {
72
- use super::color::*;
73
- eprintln!("{RED}Error:{RESET} either a value argument or --from is required");
73
+ eprintln!("{} either a value argument or --from is required", ui::failure("Error:"));
74
74
 
75
75
  std::process::exit(1);
76
76
  };
@@ -28,13 +28,13 @@ pub struct Args {
28
28
 
29
29
  impl Args {
30
30
  pub fn run(self) {
31
- use super::color::*;
31
+ use super::ui;
32
32
 
33
33
  let document = parse_file(&self.file);
34
34
  let info = document.get_node_info(&self.selector);
35
35
 
36
36
  if info.location.start_line == 0 && info.location.end_line == 0 {
37
- eprintln!("{RED}Error:{RESET} selector \"{}\" not found in {}", self.selector, self.file);
37
+ eprintln!("{} selector \"{}\" not found in {}", ui::failure("Error:"), self.selector, self.file);
38
38
 
39
39
  super::show_similar_selectors(&self.file, &document, &self.selector);
40
40
  process::exit(1);
@@ -1,12 +1,12 @@
1
- use super::color::*;
1
+ use super::ui;
2
2
 
3
3
  pub fn run() {
4
- let g = GREEN;
5
- let b = BOLD;
6
- let d = DIM;
7
- let i = "\x1b[3m"; // italic
8
- let y = YELLOW;
9
- let r = RESET;
4
+ let g = ui::raw::green();
5
+ let b = ui::raw::bold();
6
+ let d = ui::raw::dim();
7
+ let i = ui::raw::italic();
8
+ let y = ui::raw::yellow();
9
+ let r = ui::raw::reset();
10
10
  let hr = format!(" {d}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}{r}");
11
11
 
12
12
  println!();
@@ -18,6 +18,7 @@ pub mod selectors;
18
18
  pub mod set;
19
19
  pub mod sort;
20
20
  pub mod sort_keys;
21
+ pub mod ui;
21
22
  pub mod unique;
22
23
  pub mod version;
23
24
 
@@ -30,56 +31,15 @@ pub(crate) fn is_github_actions() -> bool {
30
31
  std::env::var("GITHUB_ACTIONS").is_ok()
31
32
  }
32
33
 
33
- pub(crate) mod color {
34
- pub const GREEN: &str = "\x1b[32m";
35
- pub const RED: &str = "\x1b[31m";
36
- pub const YELLOW: &str = "\x1b[33m";
37
- pub const DIM: &str = "\x1b[2m";
38
- pub const BOLD: &str = "\x1b[1m";
39
- pub const RESET: &str = "\x1b[0m";
34
+ pub(crate) fn pluralize(count: usize, singular: &str, plural: &str) -> String {
35
+ format!("{} {}", count, if count == 1 { singular } else { plural })
40
36
  }
41
37
 
42
- // Compile-time ANSI macros for use in concat!() / clap attributes (used in main.rs)
43
- #[allow(unused_macros)]
44
- macro_rules! h {
45
- () => {
46
- "\x1b[1;32m"
47
- };
48
- } // header (bold green)
49
- #[allow(unused_macros)]
50
- macro_rules! b {
51
- () => {
52
- "\x1b[1m"
53
- };
54
- } // bold
55
- #[allow(unused_macros)]
56
- macro_rules! c {
57
- () => {
58
- "\x1b[36m"
59
- };
60
- } // cyan
61
- #[allow(unused_macros)]
62
- macro_rules! d {
63
- () => {
64
- "\x1b[2m"
65
- };
66
- } // dim
67
- #[allow(unused_macros)]
68
- macro_rules! r {
69
- () => {
70
- "\x1b[0m"
71
- };
72
- } // reset
73
- #[allow(unused_imports)]
74
- pub(crate) use {b, c, d, h, r};
75
-
76
38
  pub(crate) fn colorize_examples(input: &str) -> String {
77
39
  colorize_help(&format!("Examples:\n{}", input.trim()))
78
40
  }
79
41
 
80
42
  pub(crate) fn colorize_help(input: &str) -> String {
81
- use color::*;
82
-
83
43
  let mut output = String::new();
84
44
 
85
45
  for line in input.lines() {
@@ -91,7 +51,7 @@ pub(crate) fn colorize_help(input: &str) -> String {
91
51
  }
92
52
 
93
53
  if trimmed.ends_with(':') && !trimmed.contains(' ') {
94
- output.push_str(&format!("{GREEN}{BOLD}{trimmed}{RESET}\n"));
54
+ output.push_str(&format!("{}\n", ui::success(trimmed)));
95
55
  continue;
96
56
  }
97
57
 
@@ -100,11 +60,11 @@ pub(crate) fn colorize_help(input: &str) -> String {
100
60
 
101
61
  match (parts.next(), parts.next(), parts.next()) {
102
62
  (Some(cmd), Some(sub), Some(rest)) => {
103
- output.push_str(&format!(" {BOLD}{cmd}{RESET} \x1b[36m{sub}{RESET} {rest}\n"));
63
+ output.push_str(&format!(" {} {} {}\n", ui::strong(cmd), ui::muted(sub), rest));
104
64
  }
105
65
 
106
66
  (Some(cmd), Some(sub), None) => {
107
- output.push_str(&format!(" {BOLD}{cmd}{RESET} \x1b[36m{sub}{RESET}\n"));
67
+ output.push_str(&format!(" {} {}\n", ui::strong(cmd), ui::muted(sub)));
108
68
  }
109
69
 
110
70
  _ => {
@@ -134,9 +94,14 @@ pub(crate) fn colorize_help(input: &str) -> String {
134
94
  }
135
95
 
136
96
  if columns.len() == 3 {
137
- output.push_str(&format!(" \x1b[36m{:<20}{RESET} {:<21} {DIM}{}{RESET}\n", columns[0], columns[1], columns[2]));
97
+ output.push_str(&format!(
98
+ " {} {:<21} {}\n",
99
+ ui::muted(format!("{:<20}", columns[0])),
100
+ columns[1],
101
+ ui::subtle(columns[2])
102
+ ));
138
103
  } else if columns.len() == 2 {
139
- output.push_str(&format!(" \x1b[36m{:<20}{RESET} {}\n", columns[0], columns[1]));
104
+ output.push_str(&format!(" {} {}\n", ui::muted(format!("{:<20}", columns[0])), columns[1]));
140
105
  } else {
141
106
  output.push_str(&format!(" {trimmed}\n"));
142
107
  }
@@ -204,19 +169,35 @@ impl Command {
204
169
  }
205
170
 
206
171
  pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
207
- use color::*;
208
-
209
172
  let yerbafile_path = yerba::Yerbafile::find().unwrap_or_else(|| {
210
- eprintln!("{RED}No Yerbafile found.{RESET} Run {BOLD}yerba init{RESET} to create one.");
173
+ eprintln!(
174
+ "{} No Yerbafile found. Run {} to create one.",
175
+ ui::failure(ui::glyph::FAIL),
176
+ ui::strong("yerba init")
177
+ );
211
178
  process::exit(1);
212
179
  });
213
180
 
214
181
  let yerbafile = yerba::Yerbafile::load(&yerbafile_path).unwrap_or_else(|error| {
215
- eprintln!("{RED}Error loading {}:{RESET} {}", yerbafile_path.display(), error);
182
+ eprintln!(
183
+ "{} {}",
184
+ ui::failure(ui::glyph::FAIL),
185
+ ui::strong(format!("Could not read {}", yerbafile_path.display()))
186
+ );
187
+ eprintln!(" {}", error);
216
188
  process::exit(1);
217
189
  });
218
190
 
219
- eprintln!("🧉 {BOLD}Using{RESET} {}", yerbafile_path.display());
191
+ eprintln!("{}", ui::banner());
192
+
193
+ eprintln!(
194
+ "{}{} {}\n",
195
+ ui::INDENT,
196
+ ui::strong("Using Yerbafile rules"),
197
+ ui::subtle(format!("(from {})", yerbafile_path.display()))
198
+ );
199
+
200
+ let started = std::time::Instant::now();
220
201
 
221
202
  let results = if files.is_empty() {
222
203
  yerbafile.apply(write)
@@ -224,6 +205,8 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
224
205
  files.iter().flat_map(|file| yerbafile.apply_file(file, write)).collect()
225
206
  };
226
207
 
208
+ let elapsed = started.elapsed();
209
+
227
210
  let mut has_changes = false;
228
211
  let mut has_errors = false;
229
212
 
@@ -231,7 +214,8 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
231
214
 
232
215
  for result in &results {
233
216
  if let Some(error) = &result.error {
234
- eprintln!(" {RED}error:{RESET} {} {DIM}—{RESET} {}", result.file, error);
217
+ eprintln!(" {} {}", ui::failure(ui::glyph::FAIL), ui::strong(&result.file));
218
+ eprintln!(" {}", ui::muted(error));
235
219
 
236
220
  if github {
237
221
  use yerba::error::GitHubAnnotations;
@@ -244,12 +228,15 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
244
228
  has_errors = true;
245
229
  } else if result.changed {
246
230
  if write {
247
- eprintln!(" {GREEN}updated:{RESET} {}", result.file);
231
+ eprintln!(" {} {}", ui::success(ui::glyph::OK), result.file);
248
232
  } else {
249
- eprintln!(" {YELLOW}would change:{RESET} {}", result.file);
233
+ eprintln!(" {} {}", ui::pending(ui::glyph::PENDING), result.file);
250
234
 
251
235
  if github {
252
- eprintln!("::error file={}::File does not match Yerbafile rules", result.file);
236
+ eprintln!(
237
+ "::error file={}::File does not match Yerbafile rules. Run `yerba apply` to update it.",
238
+ result.file
239
+ );
253
240
  }
254
241
  }
255
242
 
@@ -257,19 +244,70 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
257
244
  }
258
245
  }
259
246
 
260
- if !has_changes && !has_errors {
261
- eprintln!("\n{BOLD}{GREEN}All files match the rules.{RESET}");
247
+ let changed = results.iter().filter(|result| result.error.is_none() && result.changed).count();
248
+ let seen: std::collections::HashSet<&String> = results.iter().map(|result| &result.file).collect();
249
+ let rules = yerbafile.rules.len() + usize::from(!yerbafile.pipeline.is_empty());
250
+ let steps = yerbafile.pipeline.len() + yerbafile.rules.iter().map(|rule| rule.pipeline.len()).sum::<usize>();
251
+
252
+ if seen.is_empty() {
253
+ eprintln!(
254
+ "{} {} {}",
255
+ ui::pending(ui::glyph::PENDING),
256
+ ui::pending("No files matched."),
257
+ ui::subtle("Check the `files:` patterns in your Yerbafile.")
258
+ );
259
+ } else if !has_changes && !has_errors {
260
+ eprintln!("{} {}", ui::success(ui::glyph::OK), ui::success("Everything's up to date."));
261
+ }
262
+
263
+ if write && has_changes {
264
+ eprintln!(
265
+ "\n{} {}",
266
+ ui::success(ui::glyph::OK),
267
+ ui::success(format!("Freshly brewed {}.", pluralize(changed, "file", "files")))
268
+ );
262
269
  }
263
270
 
264
271
  if !write && has_changes {
265
- process::exit(1);
272
+ let command = if files.is_empty() {
273
+ "yerba apply".to_string()
274
+ } else {
275
+ format!("yerba apply {}", files.join(" "))
276
+ };
277
+
278
+ eprintln!(
279
+ "\n{} {} {}",
280
+ ui::pending(ui::glyph::PENDING),
281
+ ui::pending(format!("{} out of date.", pluralize(changed, "file", "files"))),
282
+ ui::subtle(format!("Run {} to update {}.", command, if changed == 1 { "it" } else { "them" }))
283
+ );
266
284
  }
267
285
 
268
- if has_errors {
286
+ eprintln!(
287
+ "{}{}",
288
+ ui::STATUS_INDENT,
289
+ ui::subtle(format!(
290
+ "{} · {} · {} · {}",
291
+ pluralize(seen.len(), "file", "files"),
292
+ pluralize(rules, "rule", "rules"),
293
+ pluralize(steps, "step", "steps"),
294
+ format_duration(elapsed)
295
+ ))
296
+ );
297
+
298
+ if has_errors || (!write && has_changes) {
269
299
  process::exit(1);
270
300
  }
271
301
  }
272
302
 
303
+ fn format_duration(elapsed: std::time::Duration) -> String {
304
+ if elapsed.as_secs() == 0 {
305
+ format!("{}ms", elapsed.as_millis())
306
+ } else {
307
+ format!("{:.1}s", elapsed.as_secs_f64())
308
+ }
309
+ }
310
+
273
311
  pub(crate) fn resolve_move_indexes(
274
312
  document: &yerba::Document,
275
313
  path: &str,
@@ -279,10 +317,8 @@ pub(crate) fn resolve_move_indexes(
279
317
  to: Option<usize>,
280
318
  resolve: impl Fn(&yerba::Document, &str, &str) -> Result<usize, yerba::YerbaError>,
281
319
  ) -> (usize, usize) {
282
- use color::*;
283
-
284
320
  let from_index = resolve(document, path, item).unwrap_or_else(|error| {
285
- eprintln!("{RED}Error:{RESET} {}", error);
321
+ eprintln!("{} {}", ui::failure("Error:"), error);
286
322
  process::exit(1);
287
323
  });
288
324
 
@@ -290,7 +326,7 @@ pub(crate) fn resolve_move_indexes(
290
326
  index
291
327
  } else if let Some(target) = &before {
292
328
  let target_index = resolve(document, path, target).unwrap_or_else(|error| {
293
- eprintln!("{RED}Error:{RESET} {}", error);
329
+ eprintln!("{} {}", ui::failure("Error:"), error);
294
330
  process::exit(1);
295
331
  });
296
332
 
@@ -301,7 +337,7 @@ pub(crate) fn resolve_move_indexes(
301
337
  }
302
338
  } else if let Some(target) = &after {
303
339
  let target_index = resolve(document, path, target).unwrap_or_else(|error| {
304
- eprintln!("{RED}Error:{RESET} {}", error);
340
+ eprintln!("{} {}", ui::failure("Error:"), error);
305
341
  process::exit(1);
306
342
  });
307
343
 
@@ -311,7 +347,7 @@ pub(crate) fn resolve_move_indexes(
311
347
  target_index + 1
312
348
  }
313
349
  } else {
314
- eprintln!("{RED}Error:{RESET} specify --before, --after, or --to");
350
+ eprintln!("{} specify --before, --after, or --to", ui::failure("Error:"));
315
351
  process::exit(1);
316
352
  };
317
353
 
@@ -319,12 +355,10 @@ pub(crate) fn resolve_move_indexes(
319
355
  }
320
356
 
321
357
  pub(crate) fn resolve_files(pattern: &str) -> Vec<String> {
322
- use color::*;
323
-
324
358
  if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
325
359
  let paths: Vec<String> = glob::glob(pattern)
326
360
  .unwrap_or_else(|error| {
327
- eprintln!("{RED}Error:{RESET} invalid glob pattern '{}': {}", pattern, error);
361
+ eprintln!("{} invalid glob pattern '{}': {}", ui::failure("Error:"), pattern, error);
328
362
  process::exit(1);
329
363
  })
330
364
  .filter_map(|entry| entry.ok())
@@ -332,7 +366,7 @@ pub(crate) fn resolve_files(pattern: &str) -> Vec<String> {
332
366
  .collect();
333
367
 
334
368
  if paths.is_empty() {
335
- eprintln!("{RED}Error:{RESET} no files matched pattern: {}", pattern);
369
+ eprintln!("{} no files matched pattern: {}", ui::failure("Error:"), pattern);
336
370
  process::exit(1);
337
371
  }
338
372
 
@@ -343,18 +377,16 @@ pub(crate) fn resolve_files(pattern: &str) -> Vec<String> {
343
377
  }
344
378
 
345
379
  pub(crate) fn parse_file(file: &str) -> yerba::Document {
346
- use color::*;
347
-
348
380
  yerba::parse_file(file).unwrap_or_else(|error| {
349
381
  match &error {
350
382
  yerba::YerbaError::IoError(io_error) => match io_error.kind() {
351
- std::io::ErrorKind::NotFound => eprintln!("{RED}Error:{RESET} file not found: {}", file),
383
+ std::io::ErrorKind::NotFound => eprintln!("{} file not found: {}", ui::failure("Error:"), file),
352
384
  std::io::ErrorKind::PermissionDenied => {
353
- eprintln!("{RED}Error:{RESET} permission denied: {}", file)
385
+ eprintln!("{} permission denied: {}", ui::failure("Error:"), file)
354
386
  }
355
- _ => eprintln!("{RED}Error:{RESET} reading {}: {}", file, io_error),
387
+ _ => eprintln!("{} reading {}: {}", ui::failure("Error:"), file, io_error),
356
388
  },
357
- _ => eprintln!("{RED}Error:{RESET} parsing {}: {}", file, error),
389
+ _ => eprintln!("{} parsing {}: {}", ui::failure("Error:"), file, error),
358
390
  }
359
391
 
360
392
  process::exit(1);
@@ -366,21 +398,19 @@ pub(crate) fn run_op(display_file: &str, document: &yerba::Document, result: Res
366
398
  }
367
399
 
368
400
  pub(crate) fn run_op_with_hint(display_file: &str, document: &yerba::Document, result: Result<(), yerba::YerbaError>, hint: Option<&str>) {
369
- use color::*;
370
-
371
401
  if let Err(error) = result {
372
402
  if let yerba::YerbaError::SelectorNotFound(selector) = &error {
373
- eprintln!("{RED}Error:{RESET} selector \"{selector}\" not found in {display_file}");
403
+ eprintln!("{} selector \"{selector}\" not found in {display_file}", ui::failure("Error:"));
374
404
 
375
405
  show_similar_selectors(display_file, document, selector);
376
406
  } else {
377
- eprintln!("{RED}Error:{RESET} {}", error);
407
+ eprintln!("{} {}", ui::failure("Error:"), error);
378
408
  }
379
409
 
380
410
  if let Some(hint) = hint {
381
411
  if matches!(error, yerba::YerbaError::SelectorNotFound(_)) {
382
412
  eprintln!();
383
- eprintln!(" {DIM}Hint: {hint}{RESET}");
413
+ eprintln!(" {}", ui::subtle(format!("Hint: {}", hint)));
384
414
  }
385
415
  }
386
416
 
@@ -389,7 +419,6 @@ pub(crate) fn run_op_with_hint(display_file: &str, document: &yerba::Document, r
389
419
  }
390
420
 
391
421
  pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, invalid_path: &str) {
392
- use color::*;
393
422
  use yerba::didyoumean::didyoumean_ranked;
394
423
 
395
424
  let selectors = document.selectors();
@@ -405,19 +434,19 @@ pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, inv
405
434
  eprintln!();
406
435
 
407
436
  if close.is_empty() {
408
- eprintln!(" {BOLD}Available selectors in {file}:{RESET}");
437
+ eprintln!(" {}", ui::strong(format!("Available selectors in {}:", file)));
409
438
 
410
439
  for selector in selectors.iter().take(10) {
411
- eprintln!(" {DIM}{selector}{RESET}");
440
+ eprintln!(" {}", ui::subtle(selector));
412
441
  }
413
442
 
414
443
  if selectors.len() > 10 {
415
- eprintln!(" {DIM}... and {} more{RESET}", selectors.len() - 10);
444
+ eprintln!(" {}", ui::subtle(format!("... and {} more", selectors.len() - 10)));
416
445
  }
417
446
  } else if close.len() == 1 {
418
- eprintln!(" {BOLD}Did you mean this selector?{RESET} {}", close[0]);
447
+ eprintln!(" {} {}", ui::strong("Did you mean this selector?"), close[0]);
419
448
  } else {
420
- eprintln!(" {BOLD}Did you mean one of these selectors?{RESET}");
449
+ eprintln!(" {}", ui::strong("Did you mean one of these selectors?"));
421
450
 
422
451
  for selector in close.iter().take(5) {
423
452
  eprintln!(" {selector}");
@@ -425,8 +454,8 @@ pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, inv
425
454
  }
426
455
 
427
456
  eprintln!();
428
- eprintln!(" {BOLD}To see all valid selectors, run:{RESET}");
429
- eprintln!(" yerba selectors \"{file}\"{RESET}");
457
+ eprintln!(" {}", ui::strong("To see all valid selectors, run:"));
458
+ eprintln!(" yerba selectors \"{file}\"");
430
459
  }
431
460
 
432
461
  pub(crate) fn output(file: &str, document: &yerba::Document, dry_run: bool) {
@@ -1,3 +1,4 @@
1
+ use super::ui;
1
2
  use std::process;
2
3
  use std::sync::LazyLock;
3
4
 
@@ -43,10 +44,11 @@ impl Args {
43
44
  let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
44
45
 
45
46
  if target_parent != parent_path {
46
- use super::color::*;
47
47
  eprintln!(
48
- "{RED}Error:{RESET} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
49
- self.selector, target
48
+ "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
49
+ ui::failure("Error:"),
50
+ self.selector,
51
+ target
50
52
  );
51
53
 
52
54
  process::exit(1);
@@ -59,10 +61,11 @@ impl Args {
59
61
  let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
60
62
 
61
63
  if target_parent != parent_path {
62
- use super::color::*;
63
64
  eprintln!(
64
- "{RED}Error:{RESET} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
65
- self.selector, target
65
+ "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
66
+ ui::failure("Error:"),
67
+ self.selector,
68
+ target
66
69
  );
67
70
 
68
71
  process::exit(1);
@@ -1,3 +1,4 @@
1
+ use super::ui;
1
2
  use std::sync::LazyLock;
2
3
 
3
4
  use indoc::indoc;
@@ -38,8 +39,8 @@ pub struct Args {
38
39
  impl Args {
39
40
  pub fn run(self) {
40
41
  if self.values.is_none() && self.keys.is_none() {
41
- use super::color::*;
42
- eprintln!("{RED}Error:{RESET} specify --values, --keys, or both");
42
+ use super::ui;
43
+ eprintln!("{} specify --values, --keys, or both", ui::failure("Error:"));
43
44
  std::process::exit(1);
44
45
  }
45
46
 
@@ -55,9 +56,7 @@ impl Args {
55
56
  if let Some(style) = &self.values {
56
57
  if let Ok(warnings) = document.enforce_quotes_at(style, selector) {
57
58
  for warning in warnings {
58
- use super::color::*;
59
-
60
- eprintln!("{YELLOW}Warning:{RESET} {} — {}", resolved_file, warning);
59
+ eprintln!("{} {} — {}", ui::pending("Warning:"), resolved_file, warning);
61
60
  }
62
61
  }
63
62
  }
@@ -8,15 +8,15 @@ use super::{output, parse_file, resolve_files, run_op};
8
8
  static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
9
  colorize_examples(indoc! {r#"
10
10
  yerba rename config.yml "database.host" "database.hostname"
11
- yerba rename config.yml "database.host" "hostname"
12
- yerba rename config.yml "database.host" "settings.db_host"
13
11
  yerba rename videos.yml "[0].old_name" "[0].name"
12
+ yerba rename config.yml "database.host" "settings.db_host" # moves the key to another map
13
+ yerba rename config.yml "database.host" "hostname" # moves the key to the document root
14
14
  "#})
15
15
  });
16
16
 
17
17
  #[derive(clap::Args)]
18
18
  #[command(
19
- about = "Rename a key in a map (preserves value and position)",
19
+ about = "Rename a key, or move it to another map (the destination is a full selector path)",
20
20
  arg_required_else_help = true,
21
21
  after_help = EXAMPLES.as_str()
22
22
  )]
@@ -1,7 +1,7 @@
1
1
  use std::process;
2
2
 
3
- use super::color::*;
4
3
  use super::resolve_files;
4
+ use super::ui;
5
5
 
6
6
  #[derive(clap::Args)]
7
7
  #[command(
@@ -32,7 +32,7 @@ impl Args {
32
32
  let schema = match yerba::schema::load_schema(&self.schema) {
33
33
  Ok(schema) => schema,
34
34
  Err(error) => {
35
- eprintln!("{RED}Error:{RESET} {}", error);
35
+ eprintln!("{} {}", ui::failure("Error:"), error);
36
36
  process::exit(1);
37
37
  }
38
38
  };
@@ -40,7 +40,7 @@ impl Args {
40
40
  let files = resolve_files(&self.file);
41
41
 
42
42
  if files.is_empty() {
43
- eprintln!("{RED}Error:{RESET} no files matching: {}", self.file);
43
+ eprintln!("{} no files matching: {}", ui::failure("Error:"), self.file);
44
44
  process::exit(1);
45
45
  }
46
46
 
@@ -51,7 +51,7 @@ impl Args {
51
51
  let document = match yerba::Document::parse_file(file) {
52
52
  Ok(document) => document,
53
53
  Err(error) => {
54
- eprintln!(" {RED}error:{RESET} {} {DIM}—{RESET} {}", file, error);
54
+ eprintln!(" {} {} {} {}", ui::failure("error:"), ui::subtle("—"), file, error);
55
55
  has_errors = true;
56
56
  continue;
57
57
  }
@@ -66,7 +66,7 @@ impl Args {
66
66
  has_errors = true;
67
67
 
68
68
  let relative = file.strip_prefix("./").unwrap_or(file);
69
- eprintln!(" {RED}error:{RESET} {relative}");
69
+ eprintln!(" {} {relative}", ui::failure("error:"));
70
70
 
71
71
  for error in &errors {
72
72
  eprintln!(" {error}");
@@ -78,7 +78,7 @@ impl Args {
78
78
  if has_errors {
79
79
  process::exit(1);
80
80
  } else {
81
- eprintln!("{GREEN}All {count} files valid.{RESET}", count = files.len());
81
+ eprintln!("{}", ui::success(format!("All {} files valid.", files.len())));
82
82
  }
83
83
  }
84
84
  }
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
4
4
  use indoc::indoc;
5
5
 
6
6
  use super::colorize_examples;
7
- use super::{color, parse_file, resolve_files};
7
+ use super::{parse_file, resolve_files, ui};
8
8
 
9
9
  static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
10
10
  colorize_examples(indoc! {r#"
@@ -108,7 +108,7 @@ impl Args {
108
108
  if let Some(label) = info.count_label() {
109
109
  let padding = max_selector_len - selector.len() + 2;
110
110
 
111
- println!("{}{}{}{}{}", selector, " ".repeat(padding), color::DIM, label, color::RESET);
111
+ println!("{}{}{}", selector, " ".repeat(padding), ui::subtle(label));
112
112
  } else {
113
113
  println!("{}", selector);
114
114
  }
@@ -25,6 +25,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
25
25
  pub struct Args {
26
26
  file: String,
27
27
  selector: String,
28
+ #[arg(allow_hyphen_values = true)]
28
29
  value: String,
29
30
  #[arg(long)]
30
31
  if_exists: bool,
@@ -42,6 +43,16 @@ impl Args {
42
43
  pub fn run(self) {
43
44
  let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
44
45
 
46
+ if let Some(condition) = &self.condition {
47
+ if let Err(error) = yerba::validate_condition(condition) {
48
+ use super::ui;
49
+
50
+ eprintln!("{} {}", ui::failure("Error:"), error);
51
+
52
+ std::process::exit(1);
53
+ }
54
+ }
55
+
45
56
  for resolved_file in resolve_files(&self.file) {
46
57
  let mut document = parse_file(&resolved_file);
47
58
 
@@ -50,7 +61,7 @@ impl Args {
50
61
  } else if self.if_missing {
51
62
  !document.exists(&self.selector)
52
63
  } else if let Some(condition) = &self.condition {
53
- document.evaluate_condition(parent_path, condition)
64
+ document.evaluate_condition(parent_path, condition).unwrap_or(false)
54
65
  } else {
55
66
  true
56
67
  };