yerba 0.3.0-aarch64-linux-gnu → 0.4.1-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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -8
  3. data/exe/aarch64-linux-gnu/yerba +0 -0
  4. data/ext/yerba/extconf.rb +22 -3
  5. data/ext/yerba/include/yerba.h +32 -9
  6. data/ext/yerba/yerba.c +133 -25
  7. data/lib/yerba/3.2/yerba.so +0 -0
  8. data/lib/yerba/3.3/yerba.so +0 -0
  9. data/lib/yerba/3.4/yerba.so +0 -0
  10. data/lib/yerba/4.0/yerba.so +0 -0
  11. data/lib/yerba/collection.rb +35 -0
  12. data/lib/yerba/document.rb +45 -3
  13. data/lib/yerba/query_result.rb +50 -0
  14. data/lib/yerba/sequence.rb +187 -1
  15. data/lib/yerba/version.rb +1 -1
  16. data/lib/yerba/yerbafile.rb +45 -0
  17. data/lib/yerba.rb +2 -0
  18. data/rust/Cargo.lock +3 -1
  19. data/rust/Cargo.toml +3 -2
  20. data/rust/cbindgen.toml +1 -0
  21. data/rust/rustfmt.toml +1 -1
  22. data/rust/src/commands/apply.rs +11 -2
  23. data/rust/src/commands/blank_lines.rs +1 -4
  24. data/rust/src/commands/check.rs +11 -2
  25. data/rust/src/commands/directives.rs +61 -0
  26. data/rust/src/commands/get.rs +5 -22
  27. data/rust/src/commands/mod.rs +16 -18
  28. data/rust/src/commands/quote_style.rs +12 -6
  29. data/rust/src/commands/selectors.rs +3 -19
  30. data/rust/src/commands/sort.rs +22 -157
  31. data/rust/src/didyoumean.rs +2 -4
  32. data/rust/src/document/condition.rs +139 -0
  33. data/rust/src/document/delete.rs +91 -0
  34. data/rust/src/document/get.rs +262 -0
  35. data/rust/src/document/insert.rs +384 -0
  36. data/rust/src/document/mod.rs +784 -0
  37. data/rust/src/document/set.rs +100 -0
  38. data/rust/src/document/sort.rs +639 -0
  39. data/rust/src/document/style.rs +473 -0
  40. data/rust/src/error.rs +24 -6
  41. data/rust/src/ffi.rs +272 -518
  42. data/rust/src/json.rs +1 -7
  43. data/rust/src/lib.rs +88 -2
  44. data/rust/src/main.rs +2 -0
  45. data/rust/src/quote_style.rs +83 -7
  46. data/rust/src/selector.rs +2 -7
  47. data/rust/src/syntax.rs +41 -21
  48. data/rust/src/yerbafile.rs +86 -19
  49. metadata +13 -3
  50. data/rust/src/document.rs +0 -2304
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Yerba
4
4
  class Document
5
+ ROOT_SELECTOR = ""
6
+
5
7
  def root
6
- self[""]
8
+ self[ROOT_SELECTOR]
7
9
  end
8
10
 
9
11
  def map?
@@ -15,11 +17,11 @@ module Yerba
15
17
  end
16
18
 
17
19
  def to_h
18
- get_value("")
20
+ get_value(ROOT_SELECTOR)
19
21
  end
20
22
 
21
23
  def to_a
22
- get_value("")
24
+ get_value(ROOT_SELECTOR)
23
25
  end
24
26
 
25
27
  def to_yaml
@@ -48,6 +50,46 @@ module Yerba
48
50
  end
49
51
  end
50
52
 
53
+ def find_by(...)
54
+ root.find_by(...)
55
+ end
56
+
57
+ def where(...)
58
+ root.where(...)
59
+ end
60
+
61
+ def pluck(...)
62
+ root.pluck(...)
63
+ end
64
+
65
+ def <<(item)
66
+ root << item
67
+ end
68
+
69
+ def concat(items)
70
+ root.concat(items)
71
+ end
72
+
73
+ def save!(apply: false)
74
+ Yerbafile.apply!(self, apply) if apply
75
+ write!
76
+
77
+ self
78
+ end
79
+
80
+ def apply!(yerbafile = nil)
81
+ apply(yerbafile)
82
+ write! if changed?
83
+
84
+ self
85
+ end
86
+
87
+ def apply(yerbafile = nil)
88
+ Yerbafile.apply!(self, yerbafile)
89
+
90
+ self
91
+ end
92
+
51
93
  def inspect
52
94
  if path
53
95
  "#<Yerba::Document path=#{path.inspect}>"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yerba
4
+ class QueryResult
5
+ include Enumerable
6
+
7
+ def initialize(sequence, indices)
8
+ @sequence = sequence
9
+ @indices = indices
10
+ end
11
+
12
+ def each
13
+ return enum_for(:each) unless block_given?
14
+
15
+ @indices.each { |index| yield @sequence[index] }
16
+ end
17
+
18
+ def [](position)
19
+ index = @indices[position]
20
+
21
+ @sequence[index] if index
22
+ end
23
+
24
+ def first
25
+ self[0]
26
+ end
27
+
28
+ def last
29
+ self[-1]
30
+ end
31
+
32
+ def length
33
+ @indices.length
34
+ end
35
+ alias size length
36
+ alias count length
37
+
38
+ def empty?
39
+ @indices.empty?
40
+ end
41
+
42
+ def indices
43
+ @indices.dup
44
+ end
45
+
46
+ def inspect
47
+ "#<Yerba::QueryResult length=#{length}>"
48
+ end
49
+ end
50
+ end
@@ -55,10 +55,121 @@ module Yerba
55
55
  self
56
56
  end
57
57
 
58
+ def concat(items)
59
+ if @document
60
+ hashes = items.map do |item|
61
+ case item
62
+ when Map then item.to_hash
63
+ when Hash then item
64
+ else { value: item.to_s }
65
+ end
66
+ end
67
+
68
+ @document.insert_objects(@selector, hashes)
69
+ else
70
+ @data.concat(items)
71
+ end
72
+
73
+ self
74
+ end
75
+
58
76
  def each
59
77
  return enum_for(:each) unless block_given?
60
78
 
61
- length.times { |i| yield self[i] }
79
+ length.times { |index| yield self[index] }
80
+ end
81
+
82
+ def find_by(selector = nil, value = nil, **criteria)
83
+ index = index_of(selector, value, **criteria)
84
+
85
+ self[index] if index
86
+ end
87
+
88
+ def where(selector = nil, value = nil, **criteria)
89
+ QueryResult.new(self, indices_of(selector, value, **criteria))
90
+ end
91
+
92
+ def pluck(*fields)
93
+ return [] unless @document
94
+
95
+ all_values = @document.get_value(@selector)
96
+ return [] unless all_values.is_a?(Array)
97
+
98
+ if fields.length == 1
99
+
100
+ all_values.map { |item| item.is_a?(Hash) ? item[fields.first.to_s] : item }
101
+ else
102
+
103
+ all_values.map { |item| fields.map(&:to_s).map { |field| item.is_a?(Hash) ? item[field] : nil } }
104
+ end
105
+ end
106
+
107
+ def index_of(selector = nil, value = nil, **criteria)
108
+ if selector && value.nil? && criteria.empty?
109
+ values = @document&.get("#{@selector}[]")
110
+
111
+ return values.index(selector) if values.is_a?(Array)
112
+
113
+ return nil
114
+ end
115
+
116
+ criteria[selector] = value if selector && value
117
+ pairs = expand_nested_criteria(criteria)
118
+
119
+ indices = nil
120
+
121
+ pairs.each do |field, expected|
122
+ field_string = field.to_s
123
+
124
+ if field_string.include?("[]")
125
+ matching = nested_indices_for(field_string, expected)
126
+ else
127
+ values = @document&.get("#{@selector}[].#{field_string}")
128
+
129
+ next unless values.is_a?(Array)
130
+
131
+ matching = values.each_with_index.filter_map { |actual, index| index if actual == expected }
132
+ end
133
+
134
+ indices = indices ? indices & matching : matching
135
+ end
136
+
137
+ indices&.first
138
+ end
139
+
140
+ def indices_of(selector = nil, value = nil, **criteria)
141
+ if selector && value.nil? && criteria.empty?
142
+ values = @document&.get("#{@selector}[]")
143
+
144
+ if values.is_a?(Array)
145
+ return values.each_with_index.filter_map { |actual, index| index if actual == selector }
146
+ end
147
+
148
+ return []
149
+ end
150
+
151
+ criteria[selector] = value if selector && value
152
+ pairs = expand_nested_criteria(criteria)
153
+
154
+ indices = nil
155
+
156
+ pairs.each do |field, expected|
157
+ field_string = field.to_s
158
+
159
+ if field_string.include?("[]")
160
+ matching = nested_indices_for(field_string, expected)
161
+ else
162
+ values = @document&.get("#{@selector}[].#{field_string}")
163
+
164
+ next unless values.is_a?(Array)
165
+
166
+ matching = values.each_with_index.filter_map { |actual, index| index if actual == expected }
167
+ end
168
+
169
+ indices = indices ? indices & matching : matching
170
+ end
171
+
172
+ indices || []
62
173
  end
63
174
 
64
175
  def length
@@ -69,6 +180,7 @@ module Yerba
69
180
  scalar_items.length
70
181
  else
71
182
  data = @document.get_value(@selector)
183
+
72
184
  data.is_a?(Array) ? data.length : 0
73
185
  end
74
186
  else
@@ -156,6 +268,79 @@ module Yerba
156
268
 
157
269
  private
158
270
 
271
+ def expand_nested_criteria(criteria)
272
+ expanded = []
273
+
274
+ criteria.each do |field, value|
275
+ if value.is_a?(Hash)
276
+ flatten_hash("#{field}[]", value).each do |path, leaf_value|
277
+ expanded << [path, leaf_value]
278
+ end
279
+ elsif value.is_a?(Array)
280
+ value.each do |item|
281
+ expanded << ["#{field}[]", item]
282
+ end
283
+ else
284
+ expanded << [field, value]
285
+ end
286
+ end
287
+
288
+ expanded
289
+ end
290
+
291
+ def flatten_hash(prefix, hash)
292
+ result = {}
293
+
294
+ hash.each do |key, value|
295
+ path = "#{prefix}.#{key}"
296
+
297
+ if value.is_a?(Hash)
298
+ flatten_hash("#{path}[]", value).each { |nested_path, leaf| result[nested_path] = leaf }
299
+ else
300
+ result[path] = value
301
+ end
302
+ end
303
+
304
+ result
305
+ end
306
+
307
+ def nested_indices_for(field, expected)
308
+ all_values = @document&.get_value(@selector)
309
+ return [] unless all_values.is_a?(Array)
310
+
311
+ all_values.each_with_index.filter_map do |item, index|
312
+ next unless item.is_a?(Hash)
313
+
314
+ nested_values = dig_values(item, field)
315
+ index if nested_values.include?(expected)
316
+ end
317
+ end
318
+
319
+ def dig_values(hash, path)
320
+ parts = path.split(".")
321
+ current = [hash]
322
+
323
+ parts.each do |part|
324
+ next_values = []
325
+
326
+ current.each do |value|
327
+ if part == "[]" && value.is_a?(Array)
328
+ next_values.concat(value)
329
+ elsif part.end_with?("[]")
330
+ key = part.chomp("[]")
331
+ child = value.is_a?(Hash) ? value[key] : nil
332
+ next_values.concat(child) if child.is_a?(Array)
333
+ elsif value.is_a?(Hash)
334
+ next_values << value[part] if value.key?(part)
335
+ end
336
+ end
337
+
338
+ current = next_values
339
+ end
340
+
341
+ current
342
+ end
343
+
159
344
  def format_for_insert(value)
160
345
  Formatting.quote(value, detect_quote_style)
161
346
  end
@@ -172,6 +357,7 @@ module Yerba
172
357
  result
173
358
  else
174
359
  data = @document.get_value(@selector)
360
+
175
361
  data.is_a?(Array) ? data : []
176
362
  end
177
363
  else
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.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yerba
4
+ class Yerbafile
5
+ attr_reader :path
6
+
7
+ def initialize(path)
8
+ @path = File.expand_path(path)
9
+
10
+ raise Yerba::Error, "Yerbafile not found: #{path}" unless File.exist?(@path)
11
+ end
12
+
13
+ def self.find(directory = Dir.pwd)
14
+ path = locate(directory)
15
+
16
+ path ? new(path) : nil
17
+ end
18
+
19
+ def self.find!(directory = Dir.pwd)
20
+ find(directory) || raise(Yerba::Error, "No Yerbafile found")
21
+ end
22
+
23
+ def self.resolve(yerbafile = nil)
24
+ case yerbafile
25
+ when Yerbafile then yerbafile
26
+ when String then new(yerbafile)
27
+ when true, nil then find!
28
+ end
29
+ end
30
+
31
+ def self.apply!(document, yerbafile = nil)
32
+ resolve(yerbafile).apply(document)
33
+ end
34
+
35
+ def apply(document)
36
+ document.apply_yerbafile(@path)
37
+
38
+ document
39
+ end
40
+
41
+ def inspect
42
+ "#<Yerba::Yerbafile path=#{@path.inspect}>"
43
+ end
44
+ end
45
+ end
data/lib/yerba.rb CHANGED
@@ -8,8 +8,10 @@ require_relative "yerba/formatting"
8
8
  require_relative "yerba/scalar"
9
9
  require_relative "yerba/map"
10
10
  require_relative "yerba/sequence"
11
+ require_relative "yerba/query_result"
11
12
  require_relative "yerba/document"
12
13
  require_relative "yerba/collection"
14
+ require_relative "yerba/yerbafile"
13
15
 
14
16
  begin
15
17
  major, minor, = RUBY_VERSION.split(".")
data/rust/Cargo.lock CHANGED
@@ -472,6 +472,7 @@ version = "1.0.149"
472
472
  source = "registry+https://github.com/rust-lang/crates.io-index"
473
473
  checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
474
474
  dependencies = [
475
+ "indexmap",
475
476
  "itoa",
476
477
  "memchr",
477
478
  "serde",
@@ -784,7 +785,7 @@ dependencies = [
784
785
 
785
786
  [[package]]
786
787
  name = "yerba"
787
- version = "0.3.0"
788
+ version = "0.4.1"
788
789
  dependencies = [
789
790
  "cbindgen",
790
791
  "clap",
@@ -795,6 +796,7 @@ dependencies = [
795
796
  "serde",
796
797
  "serde_json",
797
798
  "serde_yaml",
799
+ "tempfile",
798
800
  "yaml_parser",
799
801
  ]
800
802
 
data/rust/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "yerba"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  authors = ["Marco Roth <marco.roth@intergga.ch>"]
6
6
  description = "YAML Editing and Refactoring with Better Accuracy"
@@ -25,7 +25,7 @@ rowan = "0.16"
25
25
  glob = "0.3"
26
26
  serde = { version = "1", features = ["derive"] }
27
27
  serde_yaml = "0.9"
28
- serde_json = "1"
28
+ serde_json = { version = "1", features = ["preserve_order"] }
29
29
  clap = { version = "4", features = ["derive"] }
30
30
  indoc = "2"
31
31
  rayon = "1"
@@ -34,3 +34,4 @@ rayon = "1"
34
34
  cbindgen = "0.28"
35
35
 
36
36
  [dev-dependencies]
37
+ tempfile = "3"
data/rust/cbindgen.toml CHANGED
@@ -9,6 +9,7 @@ no_includes = true
9
9
 
10
10
  [export]
11
11
  include = [
12
+ "NodeType",
12
13
  "YerbaValueType",
13
14
  "YerbaResult",
14
15
  "YerbaTypedValue",
data/rust/rustfmt.toml CHANGED
@@ -1,2 +1,2 @@
1
1
  tab_spaces = 2
2
- max_width = 120
2
+ max_width = 160
@@ -1,5 +1,14 @@
1
1
  use super::run_yerbafile;
2
2
 
3
- pub fn run() {
4
- run_yerbafile(true);
3
+ #[derive(clap::Args)]
4
+ #[command(about = "Apply Yerbafile rules to all matching files (or specific files)")]
5
+ pub struct Args {
6
+ /// Specific files to apply rules to (applies to all if omitted)
7
+ files: Vec<String>,
8
+ }
9
+
10
+ impl Args {
11
+ pub fn run(self) {
12
+ run_yerbafile(true, self.files);
13
+ }
5
14
  }
@@ -39,10 +39,7 @@ impl Args {
39
39
  } else {
40
40
  use super::color::*;
41
41
 
42
- eprintln!(
43
- "{RED}Error:{RESET} expected a number for blank line count, got '{}'",
44
- self.first
45
- );
42
+ eprintln!("{RED}Error:{RESET} expected a number for blank line count, got '{}'", self.first);
46
43
 
47
44
  std::process::exit(1);
48
45
  };
@@ -1,5 +1,14 @@
1
1
  use super::run_yerbafile;
2
2
 
3
- pub fn run() {
4
- run_yerbafile(false);
3
+ #[derive(clap::Args)]
4
+ #[command(about = "Check if files match Yerbafile rules (exits 1 if changes needed)")]
5
+ pub struct Args {
6
+ /// Specific files to check (checks all if omitted)
7
+ files: Vec<String>,
8
+ }
9
+
10
+ impl Args {
11
+ pub fn run(self) {
12
+ run_yerbafile(false, self.files);
13
+ }
5
14
  }
@@ -0,0 +1,61 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, resolve_files};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba directives config.yml --ensure
11
+ yerba directives config.yml --remove
12
+ yerba directives "data/**/*.yml" --ensure
13
+ yerba directives config.yml --ensure --dry-run
14
+ "#})
15
+ });
16
+
17
+ #[derive(clap::Args)]
18
+ #[command(
19
+ about = "Add or remove the document start marker (---)",
20
+ arg_required_else_help = true,
21
+ after_help = EXAMPLES.as_str()
22
+ )]
23
+ pub struct Args {
24
+ file: String,
25
+ /// Add --- if missing
26
+ #[arg(long)]
27
+ ensure: bool,
28
+ /// Remove --- if present
29
+ #[arg(long)]
30
+ remove: bool,
31
+ #[arg(long)]
32
+ dry_run: bool,
33
+ }
34
+
35
+ impl Args {
36
+ pub fn run(self) {
37
+ if !self.ensure && !self.remove {
38
+ use super::color::*;
39
+ eprintln!("{RED}Error:{RESET} specify --ensure or --remove");
40
+ std::process::exit(1);
41
+ }
42
+
43
+ if self.ensure && self.remove {
44
+ use super::color::*;
45
+ eprintln!("{RED}Error:{RESET} --ensure and --remove are mutually exclusive");
46
+ std::process::exit(1);
47
+ }
48
+
49
+ for resolved_file in resolve_files(&self.file) {
50
+ let mut document = parse_file(&resolved_file);
51
+
52
+ if self.ensure {
53
+ let _ = document.ensure_directives();
54
+ } else if self.remove {
55
+ let _ = document.remove_directives();
56
+ }
57
+
58
+ output(&resolved_file, &document, self.dry_run);
59
+ }
60
+ }
61
+ }
@@ -77,10 +77,7 @@ impl Args {
77
77
 
78
78
  use super::color::*;
79
79
 
80
- eprintln!(
81
- "{RED}Error:{RESET} selector \"{}\" not found in {}",
82
- self.selector, resolved_file
83
- );
80
+ eprintln!("{RED}Error:{RESET} selector \"{}\" not found in {}", self.selector, resolved_file);
84
81
 
85
82
  show_similar_selectors(resolved_file, &document, &self.selector);
86
83
  process::exit(1);
@@ -97,11 +94,7 @@ impl Args {
97
94
 
98
95
  if !document.exists(&full_selector) {
99
96
  use super::color::*;
100
- eprintln!(
101
- "{RED}Error:{RESET} select field \"{}\" not found in {}",
102
- field.trim(),
103
- resolved_file
104
- );
97
+ eprintln!("{RED}Error:{RESET} select field \"{}\" not found in {}", field.trim(), resolved_file);
105
98
  show_similar_selectors(resolved_file, &document, &full_selector);
106
99
  process::exit(1);
107
100
  }
@@ -175,23 +168,13 @@ impl Args {
175
168
  }
176
169
  }
177
170
  } else if all_results.len() == 1 {
178
- println!(
179
- "{}",
180
- serde_json::to_string_pretty(&all_results[0]).unwrap_or_else(|_| "null".to_string())
181
- );
171
+ println!("{}", serde_json::to_string_pretty(&all_results[0]).unwrap_or_else(|_| "null".to_string()));
182
172
  } else {
183
- println!(
184
- "{}",
185
- serde_json::to_string_pretty(&all_results).unwrap_or_else(|_| "[]".to_string())
186
- );
173
+ println!("{}", serde_json::to_string_pretty(&all_results).unwrap_or_else(|_| "[]".to_string()));
187
174
  }
188
175
  }
189
176
 
190
- fn resolve_search_scope(
191
- &self,
192
- selector: &yerba::Selector,
193
- condition_path: Option<&yerba::Selector>,
194
- ) -> (yerba::Selector, Option<yerba::Selector>) {
177
+ fn resolve_search_scope(&self, selector: &yerba::Selector, condition_path: Option<&yerba::Selector>) -> (yerba::Selector, Option<yerba::Selector>) {
195
178
  if let Some(condition) = condition_path {
196
179
  if condition.is_relative() {
197
180
  let (container, field) = selector.split_at_last_bracket();