yerba 0.4.2 → 0.5.0

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.
data/lib/yerba/map.rb CHANGED
@@ -3,51 +3,46 @@
3
3
  module Yerba
4
4
  class Map
5
5
  include Enumerable
6
+ include Node
6
7
 
7
- attr_reader :selector, :location, :key
8
-
9
- def initialize(document_or_hash = nil, selector = nil, location = nil, key = nil)
10
- if document_or_hash.is_a?(Document)
11
- @document = document_or_hash
12
- @selector = selector
13
- @location = location
14
- @key = key
15
- @data = nil
16
- elsif document_or_hash.is_a?(Hash)
17
- @document = nil
18
- @selector = nil
19
- @location = nil
20
- @data = document_or_hash
21
- else
22
- @document = nil
23
- @selector = nil
24
- @location = nil
25
- @data = {}
26
- end
8
+ def initialize(hash = nil, **data)
9
+ init_node(nil, nil, nil, nil, nil, nil)
10
+
11
+ @data = if hash.is_a?(Hash)
12
+ hash
13
+ else
14
+ (data.empty? ? {} : data)
15
+ end
27
16
  end
28
17
 
29
18
  def [](key)
30
- if @document
19
+ if connected?
31
20
  new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
32
- @document[new_path]
21
+ document[new_path]
33
22
  else
34
23
  @data[key]
35
24
  end
36
25
  end
37
26
 
38
27
  def []=(key, value)
39
- if @document
28
+ if connected?
40
29
  new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
41
- @document.set(new_path, value)
30
+
31
+ if document.exists?(new_path)
32
+ document.set(new_path, value)
33
+ else
34
+ document.insert(new_path, value)
35
+ end
42
36
  else
43
37
  @data[key] = value
44
38
  end
45
39
  end
46
40
 
47
41
  def insert(key, value, before: nil, after: nil)
48
- if @document
42
+ if connected?
49
43
  new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
50
- @document.insert(new_path, value.to_s, before: before, after: after)
44
+
45
+ document.insert(new_path, value, before: before, after: after)
51
46
  else
52
47
  @data[key] = value
53
48
  end
@@ -56,14 +51,14 @@ module Yerba
56
51
  end
57
52
 
58
53
  def sort_keys(order)
59
- @document&.sort_keys(@selector, order)
54
+ document&.sort_keys(@selector, order)
60
55
 
61
56
  self
62
57
  end
63
58
 
64
59
  def keys
65
- if @document
66
- results = @document.find(@selector)
60
+ if connected?
61
+ results = document.find(@selector)
67
62
  return [] unless results.is_a?(Array) && results.first.is_a?(Hash)
68
63
 
69
64
  results.first.keys
@@ -75,28 +70,45 @@ module Yerba
75
70
  def each(&)
76
71
  return enum_for(:each) unless block_given?
77
72
 
78
- if @document
73
+ if connected?
79
74
  keys.each { |key| yield key, self[key] }
80
75
  else
81
76
  @data.each(&)
82
77
  end
83
78
  end
84
79
 
80
+ def fetch(key)
81
+ if connected?
82
+ new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
83
+ document.fetch(new_path)
84
+ else
85
+ @data.fetch(key)
86
+ end
87
+ end
88
+
85
89
  def dig(*keys)
86
- if @document
87
- result = keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
88
- result&.value
90
+ if connected?
91
+ keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
89
92
  else
90
93
  @data.dig(*keys)
91
94
  end
92
95
  end
93
96
 
97
+ def value_at(key)
98
+ if connected?
99
+ new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
100
+ document.value_at(new_path)
101
+ else
102
+ @data[key]
103
+ end
104
+ end
105
+
94
106
  def delete(key = nil)
95
- if key && @document
107
+ if key && connected?
96
108
  new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
97
- @document.delete(new_path)
98
- elsif @document
99
- @document.delete(@selector)
109
+ document.delete(new_path)
110
+ elsif connected?
111
+ document.delete(@selector)
100
112
  else
101
113
  @data.delete(key)
102
114
  end
@@ -105,9 +117,9 @@ module Yerba
105
117
  end
106
118
 
107
119
  def key?(key)
108
- if @document
120
+ if connected?
109
121
  new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
110
- @document.exists?(new_path)
122
+ document.exists?(new_path)
111
123
  else
112
124
  @data.key?(key)
113
125
  end
@@ -120,8 +132,8 @@ module Yerba
120
132
  end
121
133
 
122
134
  def to_h
123
- if @document
124
- results = @document.find(@selector)
135
+ if connected?
136
+ results = document.find(@selector)
125
137
  results&.first || {}
126
138
  else
127
139
  @data
@@ -137,8 +149,8 @@ module Yerba
137
149
  end
138
150
 
139
151
  def inspect
140
- if @document
141
- results = @document.find(@selector)
152
+ if connected?
153
+ results = document.find(@selector)
142
154
 
143
155
  if results.is_a?(Array) && !results.empty? && results.first.is_a?(Hash)
144
156
  map_keys = results.first.keys.first(5)
data/lib/yerba/node.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yerba
4
+ module Node
5
+ attr_reader :selector, :location, :key
6
+
7
+ def file_path
8
+ @file_path || @document&.path
9
+ end
10
+
11
+ def line
12
+ @line || @location&.start_line
13
+ end
14
+
15
+ def document
16
+ @document ||= @file_path ? Yerba::Document.cache[@file_path] : nil
17
+ end
18
+
19
+ def connected?
20
+ !@document.nil? || !@file_path.nil?
21
+ end
22
+
23
+ module ClassMethods
24
+ def from_document(document, selector, location = nil, key = nil, **attributes)
25
+ instance = allocate
26
+
27
+ instance.send(:init_node, document, selector, location, key, nil, nil)
28
+ instance.send(:init_from, **attributes) if instance.respond_to?(:init_from, true)
29
+
30
+ instance
31
+ end
32
+
33
+ def from(file_path:, selector:, line: nil, **attributes)
34
+ instance = allocate
35
+
36
+ instance.send(:init_node, nil, selector, nil, nil, file_path, line)
37
+ instance.send(:init_from, **attributes) if instance.respond_to?(:init_from, true)
38
+
39
+ instance
40
+ end
41
+ end
42
+
43
+ def self.included(base)
44
+ base.extend(ClassMethods)
45
+ end
46
+
47
+ private
48
+
49
+ def init_node(document, selector, location, key, file_path, line)
50
+ @document = document
51
+ @selector = selector
52
+ @location = location
53
+ @key = key
54
+ @file_path = file_path
55
+ @line = line
56
+ end
57
+ end
58
+ end
data/lib/yerba/scalar.rb CHANGED
@@ -2,42 +2,32 @@
2
2
 
3
3
  module Yerba
4
4
  class Scalar
5
- attr_reader :selector, :location, :key
6
-
7
- def initialize(document_or_value, selector_or_opts = nil, value = nil, location = nil, key = nil, quote_style: nil)
8
- if document_or_value.is_a?(Document) || document_or_value.nil?
9
- @document = document_or_value
10
- @selector = selector_or_opts
11
- @value = value
12
- @location = location
13
- @key = key
14
- @quote_style = quote_style
15
- else
16
- @document = nil
17
- @selector = nil
18
- @value = document_or_value
19
- @location = nil
20
- @key = nil
21
- @quote_style = selector_or_opts.is_a?(Hash) ? selector_or_opts[:quote_style] : quote_style
22
- end
5
+ include Node
6
+
7
+ def initialize(value = nil, quote_style: nil)
8
+ init_node(nil, nil, nil, nil, nil, nil)
9
+
10
+ @value = value
11
+ @quote_style = quote_style
23
12
  end
24
13
 
25
14
  def value
26
- @value ||= @document&.get(@selector)
15
+ @value ||= document&.value_at(@selector)
27
16
  end
28
17
 
29
18
  def quote_style
30
- @quote_style || @document&.get_quote_style(@selector)
19
+ @quote_style || document&.get_quote_style(@selector)
31
20
  end
32
21
 
33
22
  def quote_style=(style)
34
- @document&.set_quote_style(@selector, style)
23
+ document&.set_quote_style(@selector, style)
35
24
 
36
25
  @quote_style = style
37
26
  end
38
27
 
39
28
  def value=(new_value)
40
- @document&.set(@selector, new_value)
29
+ document&.set(@selector, new_value)
30
+
41
31
  @value = new_value
42
32
  end
43
33
  alias set value=
@@ -71,7 +61,7 @@ module Yerba
71
61
  end
72
62
 
73
63
  def delete
74
- @document&.delete(@selector)
64
+ document&.delete(@selector)
75
65
  end
76
66
 
77
67
  def inspect
@@ -81,5 +71,12 @@ module Yerba
81
71
  "#<Yerba::Scalar value=#{value.inspect} quote_style=#{quote_style.inspect}>"
82
72
  end
83
73
  end
74
+
75
+ private
76
+
77
+ def init_from(value: nil, quote_style: nil, **)
78
+ @value = value
79
+ @quote_style = quote_style
80
+ end
84
81
  end
85
82
  end
@@ -3,50 +3,67 @@
3
3
  module Yerba
4
4
  class Sequence
5
5
  include Enumerable
6
+ include Node
6
7
 
7
- attr_reader :selector, :location, :key
8
-
9
- def initialize(document_or_array = nil, selector = nil, location = nil, key = nil)
10
- if document_or_array.is_a?(Document)
11
- @document = document_or_array
12
- @selector = selector
13
- @location = location
14
- @key = key
15
- @data = nil
16
- elsif document_or_array.is_a?(Array)
17
- @document = nil
18
- @selector = nil
19
- @location = nil
20
- @data = document_or_array
8
+ def initialize(array = nil)
9
+ init_node(nil, nil, nil, nil, nil, nil)
10
+
11
+ @data = array.is_a?(Array) ? array : []
12
+ end
13
+
14
+ def [](index)
15
+ if connected?
16
+ new_path = "#{@selector}[#{index}]"
17
+ document[new_path]
21
18
  else
22
- @document = nil
23
- @selector = nil
24
- @location = nil
25
- @data = []
19
+ @data[index]
26
20
  end
27
21
  end
28
22
 
29
- def [](index)
30
- if @document
23
+ def fetch(index)
24
+ if connected?
25
+ new_path = "#{@selector}[#{index}]"
26
+ result = document[new_path]
27
+
28
+ if result.nil?
29
+ raise IndexError, "index #{index} outside of sequence bounds: 0...#{length}"
30
+ end
31
+
32
+ result
33
+ else
34
+ @data.fetch(index)
35
+ end
36
+ end
37
+
38
+ def dig(*keys)
39
+ if connected?
40
+ keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
41
+ else
42
+ @data.dig(*keys)
43
+ end
44
+ end
45
+
46
+ def value_at(index)
47
+ if connected?
31
48
  new_path = "#{@selector}[#{index}]"
32
- @document[new_path]
49
+ document.value_at(new_path)
33
50
  else
34
51
  @data[index]
35
52
  end
36
53
  end
37
54
 
38
55
  def <<(item)
39
- if @document
56
+ if connected?
40
57
  case item
41
58
  when Map
42
- @document.insert_object(@selector, item.to_hash)
59
+ document.insert_object(@selector, item.to_hash)
43
60
  when Hash
44
- @document.insert_object(@selector, item)
61
+ document.insert_object(@selector, item)
45
62
  when Scalar
46
- @document.insert(@selector, item.to_yaml)
63
+ document.insert(@selector, item.to_yaml)
47
64
  else
48
65
  formatted = format_for_insert(item.to_s)
49
- @document.insert(@selector, formatted)
66
+ document.insert(@selector, formatted)
50
67
  end
51
68
  else
52
69
  @data << item
@@ -56,7 +73,7 @@ module Yerba
56
73
  end
57
74
 
58
75
  def concat(items)
59
- if @document
76
+ if connected?
60
77
  hashes = items.map do |item|
61
78
  case item
62
79
  when Map then item.to_hash
@@ -65,7 +82,7 @@ module Yerba
65
82
  end
66
83
  end
67
84
 
68
- @document.insert_objects(@selector, hashes)
85
+ document.insert_objects(@selector, hashes)
69
86
  else
70
87
  @data.concat(items)
71
88
  end
@@ -90,9 +107,9 @@ module Yerba
90
107
  end
91
108
 
92
109
  def pluck(*fields)
93
- return [] unless @document
110
+ return [] unless connected?
94
111
 
95
- all_values = @document.get_value(@selector)
112
+ all_values = document.value_at(@selector)
96
113
  return [] unless all_values.is_a?(Array)
97
114
 
98
115
  if fields.length == 1
@@ -106,11 +123,11 @@ module Yerba
106
123
 
107
124
  def index_of(selector = nil, value = nil, **criteria)
108
125
  if selector && value.nil? && criteria.empty?
109
- values = @document&.get("#{@selector}[]")
126
+ all_values = document&.value_at(@selector)
110
127
 
111
- return values.index(selector) if values.is_a?(Array)
128
+ return nil unless all_values.is_a?(Array)
112
129
 
113
- return nil
130
+ return all_values.index(selector.to_s)
114
131
  end
115
132
 
116
133
  criteria[selector] = value if selector && value
@@ -124,11 +141,16 @@ module Yerba
124
141
  if field_string.include?("[]")
125
142
  matching = nested_indices_for(field_string, expected)
126
143
  else
127
- values = @document&.get("#{@selector}[].#{field_string}")
144
+ all_values = document&.value_at(@selector)
145
+
146
+ next unless all_values.is_a?(Array)
128
147
 
129
- next unless values.is_a?(Array)
148
+ matching = all_values.each_with_index.filter_map do |item, index|
149
+ next unless item.is_a?(Hash)
130
150
 
131
- matching = values.each_with_index.filter_map { |actual, index| index if actual == expected }
151
+ actual = dig_into(item, field_string)
152
+ index if actual.to_s == expected.to_s
153
+ end
132
154
  end
133
155
 
134
156
  indices = indices ? indices & matching : matching
@@ -139,10 +161,10 @@ module Yerba
139
161
 
140
162
  def indices_of(selector = nil, value = nil, **criteria)
141
163
  if selector && value.nil? && criteria.empty?
142
- values = @document&.get("#{@selector}[]")
164
+ all_values = document&.value_at(@selector)
143
165
 
144
- if values.is_a?(Array)
145
- return values.each_with_index.filter_map { |actual, index| index if actual == selector }
166
+ if all_values.is_a?(Array)
167
+ return all_values.each_with_index.filter_map { |actual, index| index if actual.to_s == selector.to_s }
146
168
  end
147
169
 
148
170
  return []
@@ -159,11 +181,16 @@ module Yerba
159
181
  if field_string.include?("[]")
160
182
  matching = nested_indices_for(field_string, expected)
161
183
  else
162
- values = @document&.get("#{@selector}[].#{field_string}")
184
+ all_values = document&.value_at(@selector)
185
+
186
+ next unless all_values.is_a?(Array)
163
187
 
164
- next unless values.is_a?(Array)
188
+ matching = all_values.each_with_index.filter_map do |item, index|
189
+ next unless item.is_a?(Hash)
165
190
 
166
- matching = values.each_with_index.filter_map { |actual, index| index if actual == expected }
191
+ actual = dig_into(item, field_string)
192
+ index if actual.to_s == expected.to_s
193
+ end
167
194
  end
168
195
 
169
196
  indices = indices ? indices & matching : matching
@@ -173,13 +200,13 @@ module Yerba
173
200
  end
174
201
 
175
202
  def length
176
- if @document
177
- scalar_items = @document.get("#{@selector}[]")
203
+ if connected?
204
+ scalar_items = document.value_at("#{@selector}[]")
178
205
 
179
206
  if scalar_items.is_a?(Array) && !scalar_items.empty?
180
207
  scalar_items.length
181
208
  else
182
- data = @document.get_value(@selector)
209
+ data = document.value_at(@selector)
183
210
 
184
211
  data.is_a?(Array) ? data.length : 0
185
212
  end
@@ -198,13 +225,13 @@ module Yerba
198
225
  end
199
226
 
200
227
  def remove(value)
201
- @document&.remove(@selector, value.to_s)
228
+ document&.remove(@selector, value.to_s)
202
229
 
203
230
  self
204
231
  end
205
232
 
206
233
  def delete_at(index)
207
- @document&.remove_at(@selector, index)
234
+ document&.remove_at(@selector, index)
208
235
 
209
236
  self
210
237
  end
@@ -219,20 +246,20 @@ module Yerba
219
246
  end
220
247
 
221
248
  indices_to_remove.reverse_each do |index|
222
- @document&.remove_at(@selector, index)
249
+ document&.remove_at(@selector, index)
223
250
  end
224
251
 
225
252
  self
226
253
  end
227
254
 
228
255
  def sort(by: nil, case_sensitive: false)
229
- @document&.sort(@selector, by: by, case_sensitive: case_sensitive)
256
+ document&.sort(@selector, by: by, case_sensitive: case_sensitive)
230
257
 
231
258
  self
232
259
  end
233
260
 
234
261
  def delete
235
- @document&.delete(@selector)
262
+ document&.delete(@selector)
236
263
  end
237
264
 
238
265
  def value
@@ -268,6 +295,12 @@ module Yerba
268
295
 
269
296
  private
270
297
 
298
+ def dig_into(hash, path)
299
+ keys = path.split(".")
300
+
301
+ keys.reduce(hash) { |current, key| current.is_a?(Hash) ? current[key] : nil }
302
+ end
303
+
271
304
  def expand_nested_criteria(criteria)
272
305
  expanded = []
273
306
 
@@ -305,7 +338,7 @@ module Yerba
305
338
  end
306
339
 
307
340
  def nested_indices_for(field, expected)
308
- all_values = @document&.get_value(@selector)
341
+ all_values = document&.value_at(@selector)
309
342
  return [] unless all_values.is_a?(Array)
310
343
 
311
344
  all_values.each_with_index.filter_map do |item, index|
@@ -346,17 +379,17 @@ module Yerba
346
379
  end
347
380
 
348
381
  def detect_quote_style
349
- @document.get_quote_style("#{@selector}[0]")
382
+ document.get_quote_style("#{@selector}[0]")
350
383
  end
351
384
 
352
385
  def items
353
- if @document
354
- result = @document.get("#{@selector}[]")
386
+ if connected?
387
+ result = document.value_at("#{@selector}[]")
355
388
 
356
389
  if result.is_a?(Array) && !result.empty?
357
390
  result
358
391
  else
359
- data = @document.get_value(@selector)
392
+ data = document.value_at(@selector)
360
393
 
361
394
  data.is_a?(Array) ? data : []
362
395
  end
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.4.2"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/yerba.rb CHANGED
@@ -5,6 +5,7 @@ require "fileutils"
5
5
  require_relative "yerba/version"
6
6
  require_relative "yerba/location"
7
7
  require_relative "yerba/formatting"
8
+ require_relative "yerba/node"
8
9
  require_relative "yerba/scalar"
9
10
  require_relative "yerba/map"
10
11
  require_relative "yerba/sequence"
@@ -28,6 +29,7 @@ module Yerba
28
29
  class UnsupportedPlatformError < StandardError; end
29
30
  class ExecutableNotFoundError < StandardError; end
30
31
  class CompilationError < StandardError; end
32
+ class SelectorNotFoundError < StandardError; end
31
33
 
32
34
  GEM_NAME = "yerba"
33
35
  EXECUTABLE_NAME = "yerba"
data/rust/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "yerba"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  edition = "2021"
5
5
  authors = ["Marco Roth <marco.roth@intergga.ch>"]
6
6
  description = "YAML Editing and Refactoring with Better Accuracy"
@@ -29,6 +29,7 @@ serde_json = { version = "1", features = ["preserve_order"] }
29
29
  clap = { version = "4", features = ["derive"] }
30
30
  indoc = "2"
31
31
  rayon = "1"
32
+ jsonschema = "0.29"
32
33
 
33
34
  [build-dependencies]
34
35
  cbindgen = "0.28"
@@ -33,7 +33,7 @@ impl Args {
33
33
  let mut document = parse_file(&resolved_file);
34
34
  let result = document.delete(&self.selector);
35
35
 
36
- run_op(&resolved_file, &document, result);
36
+ run_op(&self.file, &document, result);
37
37
  output(&resolved_file, &document, self.dry_run);
38
38
  }
39
39
  }