yerba 0.9.1-aarch64-linux-gnu → 0.10.0-aarch64-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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -16
  3. data/exe/aarch64-linux-gnu/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +67 -32
  5. data/ext/yerba/yerba.c +296 -186
  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/collection.rb +4 -4
  11. data/lib/yerba/document.rb +134 -0
  12. data/lib/yerba/formatting.rb +1 -0
  13. data/lib/yerba/location.rb +8 -1
  14. data/lib/yerba/map.rb +16 -6
  15. data/lib/yerba/node.rb +41 -0
  16. data/lib/yerba/query_result.rb +33 -10
  17. data/lib/yerba/scalar.rb +29 -0
  18. data/lib/yerba/selector.rb +142 -0
  19. data/lib/yerba/sequence.rb +38 -33
  20. data/lib/yerba/version.rb +1 -1
  21. data/lib/yerba.rb +1 -0
  22. data/rust/Cargo.lock +1 -1
  23. data/rust/Cargo.toml +1 -1
  24. data/rust/src/commands/get.rs +12 -12
  25. data/rust/src/commands/init.rs +8 -8
  26. data/rust/src/commands/insert.rs +4 -18
  27. data/rust/src/commands/location.rs +2 -0
  28. data/rust/src/commands/mod.rs +34 -6
  29. data/rust/src/commands/move_item.rs +1 -1
  30. data/rust/src/commands/move_key.rs +8 -8
  31. data/rust/src/commands/rename.rs +1 -1
  32. data/rust/src/commands/selectors.rs +1 -1
  33. data/rust/src/commands/set.rs +14 -6
  34. data/rust/src/document/condition.rs +25 -25
  35. data/rust/src/document/delete.rs +57 -43
  36. data/rust/src/document/get.rs +91 -82
  37. data/rust/src/document/insert.rs +77 -77
  38. data/rust/src/document/mod.rs +138 -64
  39. data/rust/src/document/nodes.rs +162 -0
  40. data/rust/src/document/schema.rs +8 -8
  41. data/rust/src/document/set.rs +49 -26
  42. data/rust/src/document/sort.rs +54 -54
  43. data/rust/src/document/style.rs +50 -49
  44. data/rust/src/document/unique.rs +5 -5
  45. data/rust/src/error.rs +8 -4
  46. data/rust/src/ffi.rs +272 -102
  47. data/rust/src/json.rs +62 -2
  48. data/rust/src/lib.rs +2 -1
  49. data/rust/src/main.rs +2 -1
  50. data/rust/src/selector.rs +48 -7
  51. data/rust/src/syntax.rs +449 -56
  52. data/rust/src/validation.rs +5 -3
  53. data/rust/src/yerbafile.rs +124 -76
  54. metadata +4 -2
@@ -45,6 +45,9 @@ module Yerba
45
45
 
46
46
  def value_at(index)
47
47
  if connected?
48
+ child = current_children&.[](index) if index.is_a?(Integer) && !index.negative?
49
+ return child.value if child.is_a?(Scalar)
50
+
48
51
  new_path = "#{@selector}[#{index}]"
49
52
  document.value_at(new_path)
50
53
  else
@@ -73,15 +76,15 @@ module Yerba
73
76
 
74
77
  def concat(items)
75
78
  if connected?
76
- hashes = items.map do |item|
79
+ values = items.map do |item|
77
80
  case item
78
81
  when Map then item.to_hash
79
- when Hash then item
80
- else { value: item.to_s }
82
+ when Scalar then item.value
83
+ else item
81
84
  end
82
85
  end
83
86
 
84
- document.insert_objects(@selector, hashes)
87
+ document.insert_objects(@selector, values)
85
88
  else
86
89
  @data.concat(items)
87
90
  end
@@ -92,6 +95,12 @@ module Yerba
92
95
  def each(&)
93
96
  return enum_for(:each) unless block_given?
94
97
 
98
+ if connected? && (tree = current_children)
99
+ tree.each { |child| yield child&.dup }
100
+
101
+ return self
102
+ end
103
+
95
104
  if connected?
96
105
  items = document.get_all("#{@selector}[]")
97
106
 
@@ -114,7 +123,7 @@ module Yerba
114
123
  end
115
124
 
116
125
  def where(selector = nil, value = nil, **criteria)
117
- QueryResult.new(self, indices_of(selector, value, **criteria))
126
+ QueryResult.new(self, indices_of(selector, value, **criteria)) { indices_of(selector, value, **criteria) }
118
127
  end
119
128
 
120
129
  def pluck(*fields)
@@ -211,28 +220,24 @@ module Yerba
211
220
  end
212
221
 
213
222
  def length
214
- if connected?
215
- scalar_items = document.value_at("#{@selector}[]")
223
+ return @data.length unless connected?
216
224
 
217
- if scalar_items.is_a?(Array) && !scalar_items.empty?
218
- scalar_items.length
219
- else
220
- data = document.value_at(@selector)
221
-
222
- data.is_a?(Array) ? data.length : 0
223
- end
224
- else
225
- @data.length
226
- end
225
+ current_children&.size || document.length_at(@selector)
227
226
  end
228
227
  alias size length
229
228
 
230
- def first
231
- self[0]
229
+ def first(count = nil)
230
+ return self[0] if count.nil?
231
+
232
+ super
232
233
  end
233
234
 
234
- def last
235
- self[length - 1] # rubocop:disable Style/NegativeArrayIndex
235
+ def last(count = nil)
236
+ size = length
237
+ return self[size - 1] if count.nil?
238
+ raise ArgumentError, "negative array size" if count.negative?
239
+
240
+ ([size - count, 0].max...size).map { |index| self[index] }
236
241
  end
237
242
 
238
243
  def remove(value)
@@ -263,15 +268,15 @@ module Yerba
263
268
  self
264
269
  end
265
270
 
266
- def sort(by: nil, case_sensitive: false)
267
- document&.sort(@selector, by: by, case_sensitive: case_sensitive)
271
+ def sort(by: nil, order: nil, case_sensitive: false)
272
+ document&.sort(@selector, by: by, order: order, case_sensitive: case_sensitive)
268
273
 
269
274
  self
270
275
  end
271
276
 
272
277
  # Deletes the entire sequence node from the YAML document.
273
278
  #
274
- # Removes the sequence at its selector path from the parent document.
279
+ # Removes the sequence at its selector from the parent document.
275
280
  # Has no effect if the sequence is not connected to a document.
276
281
  #
277
282
  # Returns the document, or +nil+ if the sequence is not connected to a document.
@@ -345,8 +350,8 @@ module Yerba
345
350
 
346
351
  private
347
352
 
348
- def dig_into(hash, path)
349
- keys = path.split(".")
353
+ def dig_into(hash, selector)
354
+ keys = selector.split(".")
350
355
 
351
356
  keys.reduce(hash) { |current, key| current.is_a?(Hash) ? current[key] : nil }
352
357
  end
@@ -356,8 +361,8 @@ module Yerba
356
361
 
357
362
  criteria.each do |field, value|
358
363
  if value.is_a?(Hash)
359
- flatten_hash("#{field}[]", value).each do |path, leaf_value|
360
- expanded << [path, leaf_value]
364
+ flatten_hash("#{field}[]", value).each do |selector, leaf_value|
365
+ expanded << [selector, leaf_value]
361
366
  end
362
367
  elsif value.is_a?(Array)
363
368
  value.each do |item|
@@ -375,12 +380,12 @@ module Yerba
375
380
  result = {}
376
381
 
377
382
  hash.each do |key, value|
378
- path = "#{prefix}.#{key}"
383
+ selector = "#{prefix}.#{key}"
379
384
 
380
385
  if value.is_a?(Hash)
381
- flatten_hash("#{path}[]", value).each { |nested_path, leaf| result[nested_path] = leaf }
386
+ flatten_hash("#{selector}[]", value).each { |nested_selector, leaf| result[nested_selector] = leaf }
382
387
  else
383
- result[path] = value
388
+ result[selector] = value
384
389
  end
385
390
  end
386
391
 
@@ -399,8 +404,8 @@ module Yerba
399
404
  end
400
405
  end
401
406
 
402
- def dig_values(hash, path)
403
- parts = path.split(".")
407
+ def dig_values(hash, selector)
408
+ parts = selector.split(".")
404
409
  current = [hash]
405
410
 
406
411
  parts.each do |part|
data/lib/yerba/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yerba
4
- VERSION = "0.9.1"
4
+ VERSION = "0.10.0"
5
5
  end
data/lib/yerba.rb CHANGED
@@ -4,6 +4,7 @@ require "fileutils"
4
4
 
5
5
  require_relative "yerba/version"
6
6
  require_relative "yerba/location"
7
+ require_relative "yerba/selector"
7
8
  require_relative "yerba/formatting"
8
9
  require_relative "yerba/node"
9
10
  require_relative "yerba/scalar"
data/rust/Cargo.lock CHANGED
@@ -1431,7 +1431,7 @@ dependencies = [
1431
1431
 
1432
1432
  [[package]]
1433
1433
  name = "yerba"
1434
- version = "0.9.1"
1434
+ version = "0.10.0"
1435
1435
  dependencies = [
1436
1436
  "anstyle-query",
1437
1437
  "cbindgen",
data/rust/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "yerba"
3
- version = "0.9.1"
3
+ version = "0.10.0"
4
4
  edition = "2021"
5
5
  authors = ["Marco Roth <marco.roth@intergga.ch>"]
6
6
  description = "YAML Editing and Refactoring with Better Accuracy"
@@ -45,9 +45,9 @@ pub struct Args {
45
45
  impl Args {
46
46
  pub fn run(self) {
47
47
  let selector = yerba::Selector::parse(&self.selector);
48
- let condition_path = self.condition.as_deref().map(yerba::Selector::parse);
48
+ let condition_selector = self.condition.as_deref().map(yerba::Selector::parse);
49
49
  let select_fields: Option<Vec<&str>> = self.select.as_deref().map(|fields| fields.split(',').collect());
50
- let (search_path, extract_field) = self.resolve_search_scope(&selector, condition_path.as_ref());
50
+ let (search_selector, extract_field) = self.resolve_search_scope(&selector, condition_selector.as_ref());
51
51
 
52
52
  let normalized_condition = self.condition.as_ref().map(|condition| {
53
53
  if !condition.starts_with('.') {
@@ -73,7 +73,7 @@ impl Args {
73
73
  }
74
74
  }
75
75
 
76
- let search_path_string = search_path.to_selector_string();
76
+ let search_selector_string = search_selector.to_selector_string();
77
77
  let mut all_results: Vec<serde_json::Value> = Vec::new();
78
78
  let files = resolve_files(&self.file);
79
79
  let is_glob = files.len() > 1;
@@ -97,10 +97,10 @@ impl Args {
97
97
 
98
98
  for field in fields {
99
99
  let field_trimmed = field.trim().trim_start_matches('.');
100
- let full_selector = if search_path_string.is_empty() {
100
+ let full_selector = if search_selector_string.is_empty() {
101
101
  field_trimmed.to_string()
102
102
  } else {
103
- format!("{}.{}", search_path_string, field_trimmed)
103
+ format!("{}.{}", search_selector_string, field_trimmed)
104
104
  };
105
105
 
106
106
  if !document.exists(&full_selector) {
@@ -122,24 +122,24 @@ impl Args {
122
122
 
123
123
  let (values, selectors, lines): (Vec<yaml_serde::Value>, Vec<String>, Vec<usize>) = if select_fields.is_some() {
124
124
  if let Some(condition) = &normalized_condition {
125
- let triples = document.filter_with_selectors(&search_path_string, condition).unwrap_or_default();
125
+ let triples = document.filter_with_selectors(&search_selector_string, condition).unwrap_or_default();
126
126
  let (values, rest): (Vec<_>, Vec<_>) = triples.into_iter().map(|(v, s, l)| (v, (s, l))).unzip();
127
127
  let (selectors, lines): (Vec<_>, Vec<_>) = rest.into_iter().unzip();
128
128
 
129
129
  (values, selectors, lines)
130
130
  } else {
131
- let located = document.get_all_located(&search_path_string);
131
+ let located = document.get_all_located(&search_selector_string);
132
132
  let selectors = located.iter().map(|n| n.selector.clone()).collect();
133
133
  let lines = located.iter().map(|n| n.line).collect();
134
- let values = document.get_values(&search_path_string);
134
+ let values = document.get_values(&search_selector_string);
135
135
  let values: Vec<_> = values.into_iter().filter(|v| !v.is_null()).collect();
136
136
 
137
137
  (values, selectors, lines)
138
138
  }
139
139
  } else if let Some(condition) = &normalized_condition {
140
- (document.filter(&search_path_string, condition).unwrap_or_default(), Vec::new(), Vec::new())
140
+ (document.filter(&search_selector_string, condition).unwrap_or_default(), Vec::new(), Vec::new())
141
141
  } else {
142
- (document.get_values(&search_path_string), Vec::new(), Vec::new())
142
+ (document.get_values(&search_selector_string), Vec::new(), Vec::new())
143
143
  };
144
144
 
145
145
  for (index, value) in values.iter().enumerate() {
@@ -216,8 +216,8 @@ impl Args {
216
216
  }
217
217
  }
218
218
 
219
- fn resolve_search_scope(&self, selector: &yerba::Selector, condition_path: Option<&yerba::Selector>) -> (yerba::Selector, Option<yerba::Selector>) {
220
- if let Some(condition) = condition_path {
219
+ fn resolve_search_scope(&self, selector: &yerba::Selector, condition_selector: Option<&yerba::Selector>) -> (yerba::Selector, Option<yerba::Selector>) {
220
+ if let Some(condition) = condition_selector {
221
221
  if condition.is_relative() {
222
222
  let (container, field) = selector.split_at_last_bracket();
223
223
 
@@ -25,12 +25,12 @@ pub fn run() {
25
25
  #
26
26
  # # Override quote style for a specific selector
27
27
  # - quote_style:
28
- # path: "[].speakers"
28
+ # selector: "[].speakers"
29
29
  # value_style: plain
30
30
  #
31
31
  # # Sort keys in a predefined order (aborts on unknown keys)
32
32
  # - sort_keys:
33
- # path: "[]"
33
+ # selector: "[]"
34
34
  # order:
35
35
  # - id
36
36
  # - title
@@ -38,7 +38,7 @@ pub fn run() {
38
38
  #
39
39
  # # Sort items in a sequence by field(s)
40
40
  # - sort:
41
- # path: ""
41
+ # selector: ""
42
42
  # by: name
43
43
  # # case_sensitive: false
44
44
  #
@@ -54,19 +54,19 @@ pub fn run() {
54
54
  #
55
55
  # # Set a value (with optional condition)
56
56
  # - set:
57
- # path: "status"
57
+ # selector: "status"
58
58
  # value: "published"
59
59
  # # condition: ".draft == true"
60
60
  #
61
61
  # # Insert a new key or sequence item
62
62
  # - insert:
63
- # path: "tags"
63
+ # selector: "tags"
64
64
  # value: "yaml"
65
65
  # # condition: ".tags not_contains yaml"
66
66
  #
67
67
  # # Delete a key (with optional condition)
68
68
  # - delete:
69
- # path: "deprecated_field"
69
+ # selector: "deprecated_field"
70
70
  # # condition: ".deprecated_field == true"
71
71
  #
72
72
  # # Rename a key
@@ -76,12 +76,12 @@ pub fn run() {
76
76
  #
77
77
  # # Remove an item from a sequence
78
78
  # - remove:
79
- # path: "tags"
79
+ # selector: "tags"
80
80
  # value: "obsolete"
81
81
  #
82
82
  # # Get a value and store it as a variable
83
83
  # - get:
84
- # path: "[].name"
84
+ # selector: "[].name"
85
85
  # as: "names"
86
86
  # # file: "other_file.yml"
87
87
  "#};
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
4
4
  use indoc::indoc;
5
5
 
6
6
  use super::colorize_examples;
7
- use super::{output, parse_file, resolve_files, run_op};
7
+ use super::{default_insert_position, output, parse_file, resolve_files, run_op};
8
8
 
9
9
  static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
10
10
  colorize_examples(indoc! {r#"
@@ -81,28 +81,14 @@ impl Args {
81
81
  std::process::exit(1);
82
82
  };
83
83
 
84
- let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
85
-
86
84
  let position = if let Some(index) = self.at {
87
85
  yerba::InsertPosition::At(index)
88
86
  } else if let Some(target) = self.before {
89
- if target.starts_with('.') {
90
- yerba::InsertPosition::BeforeCondition(target)
91
- } else {
92
- yerba::InsertPosition::Before(target)
93
- }
87
+ yerba::InsertPosition::before(target)
94
88
  } else if let Some(target) = self.after {
95
- if target.starts_with('.') {
96
- yerba::InsertPosition::AfterCondition(target)
97
- } else {
98
- yerba::InsertPosition::After(target)
99
- }
89
+ yerba::InsertPosition::after(target)
100
90
  } else {
101
- yerba::Yerbafile::find()
102
- .and_then(|yerbafile_path| yerba::Yerbafile::load(&yerbafile_path).ok())
103
- .and_then(|yerbafile| yerbafile.sort_order_for(&self.file, parent_path))
104
- .map(yerba::InsertPosition::FromSortOrder)
105
- .unwrap_or(yerba::InsertPosition::Last)
91
+ default_insert_position(&self.file, &self.selector)
106
92
  };
107
93
 
108
94
  for resolved_file in resolve_files(&self.file) {
@@ -49,6 +49,8 @@ impl Args {
49
49
  "end_column": info.location.end_column,
50
50
  "start_offset": info.location.start_offset,
51
51
  "end_offset": info.location.end_offset,
52
+ "start_character_column": info.location.start_character_column,
53
+ "end_character_column": info.location.end_character_column,
52
54
  });
53
55
 
54
56
  println!("{}", serde_json::to_string_pretty(&json).unwrap_or_default());
@@ -196,6 +196,24 @@ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
196
196
  ui::subtle(format!("(from {})", yerbafile_path.display()))
197
197
  );
198
198
 
199
+ if !yerbafile.deprecated_path_keys.is_empty() {
200
+ let keys = &yerbafile.deprecated_path_keys;
201
+ let mut places = keys.iter().take(3).cloned().collect::<Vec<_>>().join(", ");
202
+
203
+ if keys.len() > 3 {
204
+ places.push_str(&format!(" and {} more", keys.len() - 3));
205
+ }
206
+
207
+ eprintln!(
208
+ "{} {} is deprecated, use {} instead ({}: {})\n",
209
+ ui::pending("Warning:"),
210
+ ui::strong("path:"),
211
+ ui::strong("selector:"),
212
+ pluralize(keys.len(), "place", "places"),
213
+ places
214
+ );
215
+ }
216
+
199
217
  let started = std::time::Instant::now();
200
218
 
201
219
  let results = if files.is_empty() {
@@ -309,14 +327,14 @@ fn format_duration(elapsed: std::time::Duration) -> String {
309
327
 
310
328
  pub(crate) fn resolve_move_indexes(
311
329
  document: &yerba::Document,
312
- path: &str,
330
+ selector: &str,
313
331
  item: &str,
314
332
  before: Option<String>,
315
333
  after: Option<String>,
316
334
  to: Option<usize>,
317
335
  resolve: impl Fn(&yerba::Document, &str, &str) -> Result<usize, yerba::YerbaError>,
318
336
  ) -> (usize, usize) {
319
- let from_index = resolve(document, path, item).unwrap_or_else(|error| {
337
+ let from_index = resolve(document, selector, item).unwrap_or_else(|error| {
320
338
  eprintln!("{} {}", ui::failure("Error:"), error);
321
339
  process::exit(1);
322
340
  });
@@ -324,7 +342,7 @@ pub(crate) fn resolve_move_indexes(
324
342
  let to_index = if let Some(index) = to {
325
343
  index
326
344
  } else if let Some(target) = &before {
327
- let target_index = resolve(document, path, target).unwrap_or_else(|error| {
345
+ let target_index = resolve(document, selector, target).unwrap_or_else(|error| {
328
346
  eprintln!("{} {}", ui::failure("Error:"), error);
329
347
  process::exit(1);
330
348
  });
@@ -335,7 +353,7 @@ pub(crate) fn resolve_move_indexes(
335
353
  target_index
336
354
  }
337
355
  } else if let Some(target) = &after {
338
- let target_index = resolve(document, path, target).unwrap_or_else(|error| {
356
+ let target_index = resolve(document, selector, target).unwrap_or_else(|error| {
339
357
  eprintln!("{} {}", ui::failure("Error:"), error);
340
358
  process::exit(1);
341
359
  });
@@ -417,7 +435,7 @@ pub(crate) fn run_op_with_hint(display_file: &str, document: &yerba::Document, r
417
435
  }
418
436
  }
419
437
 
420
- pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, invalid_path: &str) {
438
+ pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, invalid_selector: &str) {
421
439
  use yerba::didyoumean::didyoumean_ranked;
422
440
 
423
441
  let selectors = document.selectors();
@@ -426,7 +444,7 @@ pub(crate) fn show_similar_selectors(file: &str, document: &yerba::Document, inv
426
444
  return;
427
445
  }
428
446
 
429
- let query = invalid_path.split_whitespace().next().unwrap_or(invalid_path);
447
+ let query = invalid_selector.split_whitespace().next().unwrap_or(invalid_selector);
430
448
  let threshold = query.len() / 2 + 3;
431
449
  let close = didyoumean_ranked(query, &selectors, threshold);
432
450
 
@@ -468,3 +486,13 @@ pub(crate) fn output(file: &str, document: &yerba::Document, dry_run: bool) {
468
486
  });
469
487
  }
470
488
  }
489
+
490
+ pub(crate) fn default_insert_position(file: &str, selector: &str) -> yerba::InsertPosition {
491
+ let parent_selector = selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
492
+
493
+ yerba::Yerbafile::find()
494
+ .and_then(|yerbafile_path| yerba::Yerbafile::load(&yerbafile_path).ok())
495
+ .and_then(|yerbafile| yerbafile.sort_order_for(file, parent_selector))
496
+ .map(yerba::InsertPosition::FromSortOrder)
497
+ .unwrap_or(yerba::InsertPosition::Last)
498
+ }
@@ -45,7 +45,7 @@ impl Args {
45
45
  self.before,
46
46
  self.after,
47
47
  self.to,
48
- |document, path, reference| document.resolve_sequence_index(path, reference),
48
+ |document, selector, reference| document.resolve_sequence_index(selector, reference),
49
49
  );
50
50
 
51
51
  let result = document.move_item(&self.selector, from_index, to_index);
@@ -36,16 +36,16 @@ pub struct Args {
36
36
 
37
37
  impl Args {
38
38
  pub fn run(self) {
39
- let (parent_path, key) = self.selector.rsplit_once('.').unwrap_or(("", &self.selector));
39
+ let (parent_selector, key) = self.selector.rsplit_once('.').unwrap_or(("", &self.selector));
40
40
 
41
41
  let mut document = parse_file(&self.file);
42
42
 
43
43
  let before_key = self.before.map(|target| {
44
44
  let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
45
45
 
46
- if target_parent != parent_path {
46
+ if target_parent != parent_selector {
47
47
  eprintln!(
48
- "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
48
+ "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different map.",
49
49
  ui::failure("Error:"),
50
50
  self.selector,
51
51
  target
@@ -60,9 +60,9 @@ impl Args {
60
60
  let after_key = self.after.map(|target| {
61
61
  let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
62
62
 
63
- if target_parent != parent_path {
63
+ if target_parent != parent_selector {
64
64
  eprintln!(
65
- "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
65
+ "{} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different map.",
66
66
  ui::failure("Error:"),
67
67
  self.selector,
68
68
  target
@@ -76,15 +76,15 @@ impl Args {
76
76
 
77
77
  let (from_index, to_index) = resolve_move_indexes(
78
78
  &document,
79
- parent_path,
79
+ parent_selector,
80
80
  key,
81
81
  before_key,
82
82
  after_key,
83
83
  self.to,
84
- |document, parent_path, reference| document.resolve_key_index(parent_path, reference),
84
+ |document, parent_selector, reference| document.resolve_key_index(parent_selector, reference),
85
85
  );
86
86
 
87
- let result = document.move_key(parent_path, from_index, to_index);
87
+ let result = document.move_key(parent_selector, from_index, to_index);
88
88
  run_op(&self.file, &document, result);
89
89
  output(&self.file, &document, self.dry_run);
90
90
  }
@@ -16,7 +16,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
16
16
 
17
17
  #[derive(clap::Args)]
18
18
  #[command(
19
- about = "Rename a key, or move it to another map (the destination is a full selector path)",
19
+ about = "Rename a key, or move it to another map (the destination is a full selector)",
20
20
  arg_required_else_help = true,
21
21
  after_help = EXAMPLES.as_str()
22
22
  )]
@@ -25,7 +25,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
25
25
  )]
26
26
  pub struct Args {
27
27
  file: String,
28
- /// Show selectors starting from this path
28
+ /// Show selectors starting from this selector
29
29
  selector: Option<String>,
30
30
  /// Sort selectors alphabetically (default: document order)
31
31
  #[arg(long)]
@@ -3,12 +3,13 @@ use std::sync::LazyLock;
3
3
  use indoc::indoc;
4
4
 
5
5
  use super::colorize_examples;
6
- use super::{output, parse_file, resolve_files, run_op_with_hint};
6
+ use super::{default_insert_position, 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#"
10
10
  yerba set config.yml "database.host" "0.0.0.0"
11
11
  yerba set config.yml "database.host" "0.0.0.0" --if-exists
12
+ yerba set config.yml "database.pool" 5 --plain --if-missing
12
13
  yerba set config.yml "database.host" "0.0.0.0" --condition ".port == 5432"
13
14
  yerba set videos.yml "[0].title" "New Title"
14
15
  yerba set videos.yml "[].title" "New Title" --condition ".id == talk-1"
@@ -21,7 +22,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
21
22
 
22
23
  #[derive(clap::Args)]
23
24
  #[command(
24
- about = "Update an existing value at a path (preserves quote style)",
25
+ about = "Update an existing value at a selector (preserves quote style)",
25
26
  arg_required_else_help = true,
26
27
  after_help = EXAMPLES.as_str()
27
28
  )]
@@ -46,7 +47,7 @@ pub struct Args {
46
47
 
47
48
  impl Args {
48
49
  pub fn run(self) {
49
- let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
50
+ let parent_selector = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
50
51
 
51
52
  if let Some(condition) = &self.condition {
52
53
  if let Err(error) = yerba::validate_condition(condition) {
@@ -59,7 +60,7 @@ impl Args {
59
60
  }
60
61
 
61
62
  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
+ let per_item = yerba::Selector::parse(parent_selector).has_brackets();
63
64
 
64
65
  for resolved_file in resolve_files(&self.file) {
65
66
  let mut document = parse_file(&resolved_file);
@@ -69,14 +70,21 @@ impl Args {
69
70
  } else if self.if_missing {
70
71
  !document.exists(&self.selector)
71
72
  } else if let Some(condition) = &self.condition {
72
- per_item || document.evaluate_condition(parent_path, condition).unwrap_or(false)
73
+ per_item || document.evaluate_condition(parent_selector, condition).unwrap_or(false)
73
74
  } else {
74
75
  true
75
76
  };
76
77
 
77
78
  if should_set {
78
79
  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.if_missing => {
81
+ let value = if self.plain { self.value.clone() } else { yerba::quote_scalar(&self.value) };
82
+
83
+ document.insert_into(&self.selector, &value, default_insert_position(&self.file, &self.selector))
84
+ }
85
+ Some(condition) if per_item => document
86
+ .set_where(parent_selector, leaf, &self.value, condition, self.all, self.plain)
87
+ .map(|_| ()),
80
88
  _ if self.all && self.plain => document.set_all_plain(&self.selector, &self.value),
81
89
  _ if self.all => document.set_all(&self.selector, &self.value),
82
90
  _ if self.plain => document.set_plain(&self.selector, &self.value),