yaml-merge 7.1.1 → 7.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c26a79265545e7b520e68b2d4c68c5462968352f99b7bcd084a7135648cfc72
4
- data.tar.gz: '092b5b792e8e73c4deab1ac3daca37171b8a3b65ae78917369ced3c46536f4d1'
3
+ metadata.gz: 48a5059a57b0ae4e4a6c114f2a867611725094229e1d813d1db3ae648ac011af
4
+ data.tar.gz: e9b8d83835c84f4ffdf1c0699cdc6c81c3737a9c5948711d6cf690934456fec4
5
5
  SHA512:
6
- metadata.gz: 2cc02e011d243ac5c3514df795144d5934b0ce4c144c92f19f946a5e0773c4ecdd97da17218bbfb224fd925e40328c84d64ebea18f7741fc6f5998e5021dc5b2
7
- data.tar.gz: f2e12622a2ea98eca51165bc28dbe1db6e58c0e9ec5bd3ad115b7be4c075b44787c0cbc3ee4c4315527c44dcf22e13f87932a0163ad1fad6d6038a1f27162bf3
6
+ metadata.gz: 680650bcce9925958c92b042b9873d71b33fd3509e847d8dfed3f6c4d3fad7041b359f1310b30b4a4f00828c73414ef02e1538e1e8894568f71b3ce7fb30577f
7
+ data.tar.gz: 33843a20386ebc9c5d59e5815b952f814880706a38bbe6c45f9295469438e3e69d664dc69200e09113985f2d326262ed8e388092a37eee5eb0080d23455e7548
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -23,22 +23,22 @@ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-ta
23
23
 
24
24
  `Yaml::Merge` is the canonical YAML family package. It is the YAML substrate for
25
25
  StructuredMerge Ruby: it registers the tree-sitter YAML path and should own the
26
- shared YAML merge behavior used by all YAML backends, including `psych-merge`.
27
- YAML parsing and backend selection enter through `tree_haver`; source-preserving
26
+ shared YAML merge behavior used by provider implementations. YAML parsing and
27
+ backend selection enter through `tree_haver`; source-preserving
28
28
  partial document insertion, replacement, and removal should enter through
29
29
  `ast-crispr`; merge orchestration should use `ast-merge`.
30
30
 
31
- Direct parser calls in YAML merge behavior are a smell. Tree-sitter YAML details
32
- belong behind the TreeHaver backend path, and Psych details belong behind the
33
- Psych TreeHaver backend. YAML-family code should operate on normalized owners,
34
- ranges, comments, and edit plans rather than native Ruby Hash/Array round-trips.
31
+ Direct parser calls in YAML merge behavior are a smell. Parser-specific details
32
+ belong behind the provider boundary. YAML-family code should operate on
33
+ normalized owners, ranges, comments, and edit plans rather than native Ruby
34
+ Hash/Array round-trips.
35
35
 
36
36
  ### Key Features
37
37
 
38
38
  - YAML mapping-root validation.
39
39
  - Backend feature profiles for the shared language-pack parser.
40
40
  - Path-based owner matching for mapping entries.
41
- - Shared YAML-family merge semantics for provider gems such as `psych-merge`.
41
+ - Shared YAML-family merge semantics for provider gems.
42
42
  - Destination-wins array policy.
43
43
  - Hash result API for fixture runners and higher-level tools.
44
44
 
@@ -57,7 +57,7 @@ ranges, comments, and edit plans rather than native Ruby Hash/Array round-trips.
57
57
 
58
58
  ### Compatibility
59
59
 
60
- Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
60
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
61
61
  CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
62
62
  This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
63
63
  may be higher than the gem's runtime compatibility floor when legacy Rubies are
@@ -132,20 +132,12 @@ See [SECURITY.md][🔐security].
132
132
  ## 🤝 Contributing
133
133
 
134
134
  If you need some ideas of where to help, you could work on adding more code coverage,
135
- or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
136
- or use the gem and think about how it could be better.
135
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
137
136
 
138
137
  We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
139
138
 
140
139
  See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
141
140
 
142
- ### Code Coverage
143
-
144
- <details markdown="1">
145
- <summary>Coverage service badges</summary>
146
-
147
- </details>
148
-
149
141
  ## 📌 Versioning
150
142
 
151
143
  This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
@@ -285,7 +277,7 @@ If none of the available licenses suit your use case, please [contact us](mailto
285
277
  [📌gitmoji]: https://gitmoji.dev
286
278
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
287
279
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
288
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.606-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
280
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.667-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
289
281
  [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
290
282
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
291
283
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -5,7 +5,7 @@ module Yaml
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = '7.1.1'
8
+ VERSION = '7.1.4'
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
data/lib/yaml/merge.rb CHANGED
@@ -6,6 +6,9 @@ require 'ast/merge'
6
6
  require_relative 'merge/version'
7
7
 
8
8
  module Yaml
9
+ # The YAML language layer owns this parser-neutral contract. Keep this
10
+ # facade co-located so psych-merge can depend on it without a reverse edge.
11
+ # rubocop:disable Metrics/ModuleLength -- the public YAML facade owns this structural boundary
9
12
  module Merge
10
13
  PACKAGE_NAME = 'yaml-merge'
11
14
  DESTINATION_WINS_ARRAY_POLICY = {
@@ -14,6 +17,22 @@ module Yaml
14
17
  }.freeze
15
18
  BACKEND_REFERENCE = TreeHaver::KREUZBERG_LANGUAGE_PACK_BACKEND
16
19
  BACKEND_REGISTRY = Struct.new(:registered, :mutex).new(false, Mutex.new)
20
+ YAML_TREE_NODE_HANDLERS = {
21
+ child: :yaml_value_from_tree_child_node,
22
+ mapping: :yaml_mapping_from_tree_node,
23
+ sequence: :yaml_sequence_from_tree_node,
24
+ scalar: :yaml_scalar_from_tree_node
25
+ }.freeze
26
+ YAML_DOUBLE_QUOTE_ESCAPES = {
27
+ '"' => '"',
28
+ '\\' => '\\',
29
+ '/' => '/',
30
+ 'b' => "\b",
31
+ 'f' => "\f",
32
+ 'n' => "\n",
33
+ 'r' => "\r",
34
+ 't' => "\t"
35
+ }.freeze
17
36
 
18
37
  class Error < Ast::Merge::Error; end
19
38
  class ParseError < Ast::Merge::ParseError; end
@@ -89,48 +108,58 @@ module Yaml
89
108
  return unsupported_feature_parse_result("Unsupported YAML backend #{diagnostic_backend}.")
90
109
  end
91
110
 
92
- tree = parse_tree_sitter_source(:yaml, source, backend: requested)
93
- collect_parse_errors(tree.root_node)
94
-
95
- analyze_yaml_document(yaml_value_from_tree(tree.root_node, source), dialect)
111
+ parse_yaml_tree(source, dialect, requested)
96
112
  rescue TreeHaver::Error, StandardError => e
97
113
  parse_error_result(e.message)
98
114
  end
99
115
 
116
+ def parse_yaml_tree(source, dialect, backend)
117
+ tree = parse_tree_sitter_source(:yaml, source, backend: backend)
118
+ collect_parse_errors(tree.root_node)
119
+ analyze_yaml_document(yaml_value_from_tree(tree.root_node, source), dialect)
120
+ end
121
+ private_class_method :parse_yaml_tree
122
+
100
123
  def analyze_yaml_document(parsed, dialect)
101
124
  return unsupported_feature_parse_result("Unsupported YAML dialect #{dialect}.") unless dialect == 'yaml'
102
125
  return parse_error_result('YAML documents must parse to a mapping root.') unless parsed.is_a?(Hash)
103
126
 
104
127
  validated = validate_yaml_node(parsed, '')
105
- return { ok: false, diagnostics: [validated[:diagnostic]], policies: [] } unless validated[:ok]
128
+ return invalid_yaml_analysis(validated) unless validated[:ok]
129
+
130
+ yaml_analysis_result(validated[:value])
131
+ end
106
132
 
133
+ def yaml_analysis_result(value)
107
134
  {
108
135
  ok: true,
109
136
  diagnostics: [],
110
- analysis: {
111
- kind: 'yaml',
112
- dialect: 'yaml',
113
- normalized_source: canonical_yaml(validated[:value]),
114
- document: validated[:value],
115
- root_kind: 'mapping',
116
- owners: collect_yaml_owners(validated[:value])
117
- },
137
+ analysis: yaml_document_analysis(value),
118
138
  policies: []
119
139
  }
120
140
  end
141
+ private_class_method :yaml_analysis_result
121
142
 
122
- def match_yaml_owners(template, destination)
123
- destination_paths = destination[:owners].to_h { |owner| [owner[:path], true] }
124
- template_paths = template[:owners].to_h { |owner| [owner[:path], true] }
125
-
143
+ def yaml_document_analysis(value)
126
144
  {
127
- matched: template[:owners]
128
- .filter { |owner| destination_paths[owner[:path]] }
129
- .map { |owner| { template_path: owner[:path], destination_path: owner[:path] } },
130
- unmatched_template: template[:owners].map { |owner| owner[:path] }.reject { |path| destination_paths[path] },
131
- unmatched_destination: destination[:owners].map { |owner| owner[:path] }.reject { |path| template_paths[path] }
145
+ kind: 'yaml',
146
+ dialect: 'yaml',
147
+ normalized_source: canonical_yaml(value),
148
+ document: value,
149
+ root_kind: 'mapping',
150
+ owners: collect_yaml_owners(value)
132
151
  }
133
152
  end
153
+ private_class_method :yaml_document_analysis
154
+
155
+ def invalid_yaml_analysis(validated)
156
+ { ok: false, diagnostics: [validated[:diagnostic]], policies: [] }
157
+ end
158
+ private_class_method :invalid_yaml_analysis
159
+
160
+ def match_yaml_owners(template, destination)
161
+ Ast::Merge::OwnerSelection.match_by_path(template, destination)
162
+ end
134
163
 
135
164
  def merge_yaml(template_source, destination_source, dialect, backend: nil)
136
165
  requested = backend.to_s.empty? ? nil : backend.to_s
@@ -147,45 +176,71 @@ module Yaml
147
176
 
148
177
  def merge_yaml_with_parser(template_source, destination_source, dialect, backend: nil)
149
178
  template = yield(template_source, dialect)
150
- return { ok: false, diagnostics: template[:diagnostics], policies: [] } unless template[:ok]
179
+ return parser_failure_result(template, :template) unless template[:ok]
151
180
 
152
181
  destination = yield(destination_source, dialect)
153
- unless destination[:ok]
154
- return {
155
- ok: false,
156
- diagnostics: destination[:diagnostics].map do |diagnostic|
157
- diagnostic[:category] == 'parse_error' ? diagnostic.merge(category: 'destination_parse_error') : diagnostic
158
- end,
159
- policies: []
160
- }
161
- end
182
+ return parser_failure_result(destination, :destination) unless destination[:ok]
183
+
184
+ return parse_error_merge_result('YAML documents must parse to a mapping root.') unless
185
+ yaml_parse_results_valid?(template, destination)
162
186
 
163
- template_document = template.dig(:analysis, :document)
164
- destination_document = destination.dig(:analysis, :document)
165
- unless template_document.is_a?(Hash) && destination_document.is_a?(Hash)
166
- return parse_error_merge_result('YAML documents must parse to a mapping root.')
187
+ yaml_merge_result(template_source, destination_source, backend)
188
+ rescue StandardError => e
189
+ parser_exception_result(e)
190
+ end
191
+
192
+ def parser_exception_result(error)
193
+ {
194
+ ok: false,
195
+ diagnostics: [{ severity: 'error', category: 'destination_parse_error', message: error.message }],
196
+ policies: []
197
+ }
198
+ end
199
+ private_class_method :parser_exception_result
200
+
201
+ def parser_failure_result(result, role)
202
+ diagnostics = result[:diagnostics]
203
+ if role == :destination
204
+ diagnostics = diagnostics.map do |diagnostic|
205
+ diagnostic[:category] == 'parse_error' ? diagnostic.merge(category: 'destination_parse_error') : diagnostic
206
+ end
167
207
  end
168
208
 
209
+ { ok: false, diagnostics: diagnostics, policies: [] }
210
+ end
211
+ private_class_method :parser_failure_result
212
+
213
+ def yaml_documents?(template_document, destination_document)
214
+ template_document.is_a?(Hash) && destination_document.is_a?(Hash)
215
+ end
216
+ private_class_method :yaml_documents?
217
+
218
+ def yaml_parse_results_valid?(template, destination)
219
+ yaml_documents?(template.dig(:analysis, :document), destination.dig(:analysis, :document))
220
+ end
221
+ private_class_method :yaml_parse_results_valid?
222
+
223
+ def yaml_merge_result(template_source, destination_source, backend)
169
224
  {
170
225
  ok: true,
171
226
  diagnostics: [],
172
- output: with_tree_sitter_backend(backend) do
173
- SmartMerger.new(
174
- template_source,
175
- destination_source,
176
- add_template_only_nodes: true,
177
- merge_sequences: false
178
- ).merge_result.to_yaml
179
- end,
227
+ output: yaml_merge_output(template_source, destination_source, backend),
180
228
  policies: [DESTINATION_WINS_ARRAY_POLICY]
181
229
  }
182
- rescue StandardError => e
183
- {
184
- ok: false,
185
- diagnostics: [{ severity: 'error', category: 'destination_parse_error', message: e.message }],
186
- policies: []
187
- }
188
230
  end
231
+ private_class_method :yaml_merge_result
232
+
233
+ def yaml_merge_output(template_source, destination_source, backend)
234
+ with_tree_sitter_backend(backend) do
235
+ SmartMerger.new(
236
+ template_source,
237
+ destination_source,
238
+ add_template_only_nodes: true,
239
+ merge_sequences: false
240
+ ).merge_result.to_yaml
241
+ end
242
+ end
243
+ private_class_method :yaml_merge_output
189
244
 
190
245
  def resolve_backend(backend)
191
246
  return backend.to_s unless backend.to_s.empty?
@@ -236,30 +291,61 @@ module Yaml
236
291
 
237
292
  def yaml_value_from_tree(node, source)
238
293
  named = yaml_named_children(node)
239
- case node.type
240
- when 'stream', 'document', 'block_node', 'flow_node'
241
- return nil if named.empty?
242
-
243
- yaml_value_from_tree(named.first, source)
244
- when 'block_mapping', 'flow_mapping'
245
- yaml_mapping_from_node(node, source)
246
- when 'block_sequence', 'flow_sequence'
247
- yaml_sequence_from_node(node, source)
248
- when 'block_sequence_item'
249
- value_node = named.first
250
- value_node ? yaml_value_from_tree(value_node, source) : nil
251
- when 'plain_scalar', 'string_scalar', 'double_quote_scalar', 'single_quote_scalar'
252
- yaml_scalar_from_text(source[node.start_byte...node.end_byte])
253
- else
254
- if named.length == 1
255
- yaml_value_from_tree(named.first, source)
256
- else
257
- yaml_scalar_from_text(source[node.start_byte...node.end_byte])
258
- end
259
- end
294
+ handler = YAML_TREE_NODE_HANDLERS[yaml_tree_node_kind(node.type)]
295
+ return send(handler, node, named, source) if handler
296
+
297
+ yaml_value_from_unknown_tree(node, named, source)
260
298
  end
261
299
  private_class_method :yaml_value_from_tree
262
300
 
301
+ def yaml_tree_node_kind(type)
302
+ case type
303
+ when 'stream', 'document', 'block_node', 'flow_node', 'block_sequence_item' then :child
304
+ when 'block_mapping', 'flow_mapping' then :mapping
305
+ when 'block_sequence', 'flow_sequence' then :sequence
306
+ when 'plain_scalar', 'string_scalar', 'double_quote_scalar', 'single_quote_scalar' then :scalar
307
+ end
308
+ end
309
+ private_class_method :yaml_tree_node_kind
310
+
311
+ def yaml_value_from_tree_child(named, source)
312
+ value_node = named.first
313
+ value_node ? yaml_value_from_tree(value_node, source) : nil
314
+ end
315
+ private_class_method :yaml_value_from_tree_child
316
+
317
+ def yaml_value_from_tree_child_node(_node, named, source)
318
+ yaml_value_from_tree_child(named, source)
319
+ end
320
+ private_class_method :yaml_value_from_tree_child_node
321
+
322
+ def yaml_mapping_from_tree_node(node, _named, source)
323
+ yaml_mapping_from_node(node, source)
324
+ end
325
+ private_class_method :yaml_mapping_from_tree_node
326
+
327
+ def yaml_sequence_from_tree_node(node, _named, source)
328
+ yaml_sequence_from_node(node, source)
329
+ end
330
+ private_class_method :yaml_sequence_from_tree_node
331
+
332
+ def yaml_scalar_from_tree_node(node, _named, source)
333
+ yaml_scalar_from_tree(node, source)
334
+ end
335
+ private_class_method :yaml_scalar_from_tree_node
336
+
337
+ def yaml_scalar_from_tree(node, source)
338
+ yaml_scalar_from_text(source[node.start_byte...node.end_byte])
339
+ end
340
+ private_class_method :yaml_scalar_from_tree
341
+
342
+ def yaml_value_from_unknown_tree(node, named, source)
343
+ return yaml_value_from_tree(named.first, source) if named.length == 1
344
+
345
+ yaml_scalar_from_text(source[node.start_byte...node.end_byte])
346
+ end
347
+ private_class_method :yaml_value_from_unknown_tree
348
+
263
349
  def yaml_mapping_from_node(node, source)
264
350
  yaml_named_children(node).each_with_object({}) do |child, mapping|
265
351
  next unless child.type.end_with?('mapping_pair')
@@ -293,12 +379,18 @@ module Yaml
293
379
 
294
380
  def yaml_scalar_from_text(text)
295
381
  stripped = text.to_s.strip
296
- return nil if stripped.empty? || stripped == '~' || stripped.casecmp('null').zero?
297
- return true if stripped.casecmp('true').zero?
298
- return false if stripped.casecmp('false').zero?
382
+ case stripped.downcase
383
+ when '', '~', 'null' then nil
384
+ when 'true' then true
385
+ when 'false' then false
386
+ else yaml_numeric_or_quoted_scalar(stripped)
387
+ end
388
+ end
389
+ private_class_method :yaml_scalar_from_text
390
+
391
+ def yaml_numeric_or_quoted_scalar(stripped)
299
392
  return stripped.to_i if stripped.match?(/\A[-+]?\d+\z/)
300
393
  return stripped.to_f if stripped.match?(/\A[-+]?(?:\d+\.\d*|\d*\.\d+)(?:[eE][-+]?\d+)?\z/)
301
-
302
394
  if stripped.start_with?('"') && stripped.end_with?('"')
303
395
  return unescape_yaml_double_quoted_scalar(stripped[1...-1])
304
396
  end
@@ -306,47 +398,49 @@ module Yaml
306
398
 
307
399
  stripped
308
400
  end
309
- private_class_method :yaml_scalar_from_text
401
+ private_class_method :yaml_numeric_or_quoted_scalar
310
402
 
311
403
  def unescape_yaml_double_quoted_scalar(value)
312
- value.gsub(%r{\\(["\\/bfnrt])}) do
313
- {
314
- '"' => '"',
315
- '\\' => '\\',
316
- '/' => '/',
317
- 'b' => "\b",
318
- 'f' => "\f",
319
- 'n' => "\n",
320
- 'r' => "\r",
321
- 't' => "\t"
322
- }.fetch(Regexp.last_match(1))
323
- end
404
+ value.gsub(%r{\\(["\\/bfnrt])}) { YAML_DOUBLE_QUOTE_ESCAPES.fetch(Regexp.last_match(1)) }
324
405
  end
325
406
  private_class_method :unescape_yaml_double_quoted_scalar
326
407
 
327
408
  def validate_yaml_node(value, path)
328
- if scalar?(value)
329
- { ok: true, value: value }
330
- elsif value.is_a?(Array)
331
- value.each_with_index.each_with_object({ ok: true, value: [] }) do |(item, index), memo|
332
- validated = validate_yaml_node(item, "#{path}/#{index}")
333
- return validated unless validated[:ok]
334
-
335
- memo[:value] << validated[:value]
336
- end
337
- elsif value.is_a?(Hash)
338
- value.keys.each_with_object({ ok: true, value: {} }) do |key, memo|
339
- validated = validate_yaml_node(value[key], "#{path}/#{key}")
340
- return validated unless validated[:ok]
409
+ return { ok: true, value: value } if scalar?(value)
410
+ return validate_yaml_collection(value, path) if value.is_a?(Array) || value.is_a?(Hash)
341
411
 
342
- memo[:value][key] = validated[:value]
343
- end
344
- else
345
- unsupported_feature_result("Unsupported YAML value at #{display_path(path)}. Only mappings, scalar values, and sequences are supported.")
346
- end
412
+ unsupported_feature_result(
413
+ "Unsupported YAML value at #{display_path(path)}. " \
414
+ 'Only mappings, scalar values, and sequences are supported.'
415
+ )
347
416
  end
348
417
  private_class_method :validate_yaml_node
349
418
 
419
+ def validate_yaml_collection(value, path)
420
+ value.is_a?(Array) ? validate_yaml_array(value, path) : validate_yaml_hash(value, path)
421
+ end
422
+ private_class_method :validate_yaml_collection
423
+
424
+ def validate_yaml_array(value, path)
425
+ value.each_with_index.each_with_object({ ok: true, value: [] }) do |(item, index), memo|
426
+ validated = validate_yaml_node(item, "#{path}/#{index}")
427
+ return validated unless validated[:ok]
428
+
429
+ memo[:value] << validated[:value]
430
+ end
431
+ end
432
+ private_class_method :validate_yaml_array
433
+
434
+ def validate_yaml_hash(value, path)
435
+ value.each_with_object({ ok: true, value: {} }) do |(key, item), memo|
436
+ validated = validate_yaml_node(item, "#{path}/#{key}")
437
+ return validated unless validated[:ok]
438
+
439
+ memo[:value][key] = validated[:value]
440
+ end
441
+ end
442
+ private_class_method :validate_yaml_hash
443
+
350
444
  def scalar?(value)
351
445
  value.nil? || value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
352
446
  end
@@ -393,60 +487,72 @@ module Yaml
393
487
 
394
488
  def render_yaml_sequence(sequence, indent)
395
489
  prefix = ' ' * indent
396
- sequence.flat_map do |item|
397
- if scalar?(item)
398
- ["#{prefix}- #{render_yaml_scalar(item)}"]
399
- elsif item.is_a?(Hash)
400
- ["#{prefix}-"] + render_yaml_mapping(item, indent + 2)
401
- elsif item.is_a?(Array)
402
- ["#{prefix}-"] + render_yaml_sequence(item, indent + 2)
403
- else
404
- ["#{prefix}- #{render_yaml_scalar(item)}"]
405
- end
406
- end
490
+ sequence.flat_map { |item| render_yaml_sequence_item(item, prefix, indent) }
407
491
  end
408
492
  private_class_method :render_yaml_sequence
409
493
 
494
+ def render_yaml_sequence_item(item, prefix, indent)
495
+ if scalar?(item)
496
+ ["#{prefix}- #{render_yaml_scalar(item)}"]
497
+ elsif item.is_a?(Hash)
498
+ ["#{prefix}-"] + render_yaml_mapping(item, indent + 2)
499
+ elsif item.is_a?(Array)
500
+ ["#{prefix}-"] + render_yaml_sequence(item, indent + 2)
501
+ else
502
+ ["#{prefix}- #{render_yaml_scalar(item)}"]
503
+ end
504
+ end
505
+ private_class_method :render_yaml_sequence_item
506
+
410
507
  def canonical_yaml(mapping)
411
508
  "#{render_yaml_mapping(mapping).join("\n")}\n"
412
509
  end
413
510
  private_class_method :canonical_yaml
414
511
 
415
512
  def collect_yaml_owners(mapping, prefix = '')
416
- mapping.keys.sort.flat_map do |key|
417
- path = "#{prefix}/#{key}"
418
- value = mapping[key]
419
- if value.is_a?(Array)
420
- [{ path: path, owner_kind: 'key_value', match_key: key }] +
421
- value.each_with_index.flat_map do |item, index|
422
- item_path = "#{path}/#{index}"
423
- nested = item.is_a?(Hash) ? collect_yaml_owners(item, item_path) : []
424
- [{ path: item_path, owner_kind: 'sequence_item' }] + nested
425
- end
426
- elsif value.is_a?(Hash)
427
- [{ path: path, owner_kind: 'mapping', match_key: key }] + collect_yaml_owners(value, path)
428
- else
429
- [{ path: path, owner_kind: 'key_value', match_key: key }]
430
- end
431
- end
513
+ mapping.keys.sort.flat_map { |key| collect_yaml_owners_for_value(key, mapping[key], prefix) }
432
514
  end
433
515
  private_class_method :collect_yaml_owners
434
516
 
517
+ def collect_yaml_owners_for_value(key, value, prefix)
518
+ path = "#{prefix}/#{key}"
519
+ case value
520
+ when Array then collect_yaml_sequence_owners(key, value, path)
521
+ when Hash then [{ path: path, owner_kind: 'mapping', match_key: key }] + collect_yaml_owners(value, path)
522
+ else [{ path: path, owner_kind: 'key_value', match_key: key }]
523
+ end
524
+ end
525
+ private_class_method :collect_yaml_owners_for_value
526
+
527
+ def collect_yaml_sequence_owners(key, value, path)
528
+ [{ path: path, owner_kind: 'key_value', match_key: key }] +
529
+ value.each_with_index.flat_map do |item, index|
530
+ item_path = "#{path}/#{index}"
531
+ nested = item.is_a?(Hash) ? collect_yaml_owners(item, item_path) : []
532
+ [{ path: item_path, owner_kind: 'sequence_item' }] + nested
533
+ end
534
+ end
535
+ private_class_method :collect_yaml_sequence_owners
536
+
435
537
  def merge_yaml_mappings(template, destination)
436
538
  ordered_merge_keys(template, destination).each_with_object({}) do |key, merged|
437
- merged[key] = if !template.key?(key)
438
- destination[key]
439
- elsif !destination.key?(key)
440
- template[key]
441
- elsif template[key].is_a?(Hash) && destination[key].is_a?(Hash)
442
- merge_yaml_mappings(template[key], destination[key])
443
- else
444
- destination[key]
445
- end
539
+ merged[key] = merge_yaml_mapping_value(template, destination, key)
446
540
  end
447
541
  end
448
542
  private_class_method :merge_yaml_mappings
449
543
 
544
+ def merge_yaml_mapping_value(template, destination, key)
545
+ return destination[key] unless template.key?(key)
546
+ return template[key] unless destination.key?(key)
547
+ if template[key].is_a?(Hash) && destination[key].is_a?(Hash)
548
+ return merge_yaml_mappings(template[key],
549
+ destination[key])
550
+ end
551
+
552
+ destination[key]
553
+ end
554
+ private_class_method :merge_yaml_mapping_value
555
+
450
556
  def ordered_merge_keys(template, destination)
451
557
  template.keys + destination.keys.reject { |key| template.key?(key) }
452
558
  end
@@ -479,6 +585,7 @@ module Yaml
479
585
  end
480
586
  private_class_method :unsupported_feature_result
481
587
  end
588
+ # rubocop:enable Metrics/ModuleLength
482
589
  end
483
590
 
484
591
  Yaml::Merge.register_backend!
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yaml-merge
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.1
4
+ version: 7.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -43,28 +43,28 @@ dependencies:
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 7.1.1
46
+ version: 7.1.4
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 7.1.1
53
+ version: 7.1.4
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: tree_haver
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - '='
59
59
  - !ruby/object:Gem::Version
60
- version: 7.1.1
60
+ version: 7.1.4
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - '='
66
66
  - !ruby/object:Gem::Version
67
- version: 7.1.1
67
+ version: 7.1.4
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: version_gem
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -91,20 +91,20 @@ dependencies:
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '2.5'
94
+ version: '3.0'
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: 2.5.17
97
+ version: 3.0.7
98
98
  type: :development
99
99
  prerelease: false
100
100
  version_requirements: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: '2.5'
104
+ version: '3.0'
105
105
  - - ">="
106
106
  - !ruby/object:Gem::Version
107
- version: 2.5.17
107
+ version: 3.0.7
108
108
  - !ruby/object:Gem::Dependency
109
109
  name: bundler-audit
110
110
  requirement: !ruby/object:Gem::Requirement
@@ -182,7 +182,7 @@ dependencies:
182
182
  version: '3.2'
183
183
  - - ">="
184
184
  - !ruby/object:Gem::Version
185
- version: 3.2.0
185
+ version: 3.2.2
186
186
  type: :development
187
187
  prerelease: false
188
188
  version_requirements: !ruby/object:Gem::Requirement
@@ -192,7 +192,7 @@ dependencies:
192
192
  version: '3.2'
193
193
  - - ">="
194
194
  - !ruby/object:Gem::Version
195
- version: 3.2.0
195
+ version: 3.2.2
196
196
  - !ruby/object:Gem::Dependency
197
197
  name: kettle-test
198
198
  requirement: !ruby/object:Gem::Requirement
@@ -202,7 +202,7 @@ dependencies:
202
202
  version: '2.0'
203
203
  - - ">="
204
204
  - !ruby/object:Gem::Version
205
- version: 2.0.19
205
+ version: 2.0.20
206
206
  type: :development
207
207
  prerelease: false
208
208
  version_requirements: !ruby/object:Gem::Requirement
@@ -212,7 +212,7 @@ dependencies:
212
212
  version: '2.0'
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
- version: 2.0.19
215
+ version: 2.0.20
216
216
  - !ruby/object:Gem::Dependency
217
217
  name: turbo_tests2
218
218
  requirement: !ruby/object:Gem::Requirement
@@ -222,7 +222,7 @@ dependencies:
222
222
  version: '3.2'
223
223
  - - ">="
224
224
  - !ruby/object:Gem::Version
225
- version: 3.2.4
225
+ version: 3.2.6
226
226
  type: :development
227
227
  prerelease: false
228
228
  version_requirements: !ruby/object:Gem::Requirement
@@ -232,7 +232,7 @@ dependencies:
232
232
  version: '3.2'
233
233
  - - ">="
234
234
  - !ruby/object:Gem::Version
235
- version: 3.2.4
235
+ version: 3.2.6
236
236
  - !ruby/object:Gem::Dependency
237
237
  name: ruby-progressbar
238
238
  requirement: !ruby/object:Gem::Requirement
@@ -276,7 +276,7 @@ dependencies:
276
276
  version: '2.0'
277
277
  - - ">="
278
278
  - !ruby/object:Gem::Version
279
- version: 2.0.11
279
+ version: 2.0.12
280
280
  type: :development
281
281
  prerelease: false
282
282
  version_requirements: !ruby/object:Gem::Requirement
@@ -286,7 +286,7 @@ dependencies:
286
286
  version: '2.0'
287
287
  - - ">="
288
288
  - !ruby/object:Gem::Version
289
- version: 2.0.11
289
+ version: 2.0.12
290
290
  description: "☯️ Portable YAML analysis, owner matching, and merge behavior for Structured
291
291
  Merge."
292
292
  email:
@@ -306,17 +306,16 @@ files:
306
306
  - lib/yaml/merge/node_wrapper.rb
307
307
  - lib/yaml/merge/smart_merger.rb
308
308
  - lib/yaml/merge/version.rb
309
- - sig/yaml/merge.rbs
310
309
  homepage: https://github.com/structuredmerge/structuredmerge-ruby
311
310
  licenses:
312
311
  - AGPL-3.0-only
313
312
  - PolyForm-Small-Business-1.0.0
314
313
  metadata:
315
314
  homepage_uri: https://structuredmerge.org
316
- source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.1
317
- changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.1/CHANGELOG.md
315
+ source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.4
316
+ changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.4/CHANGELOG.md
318
317
  bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
319
- documentation_uri: https://www.rubydoc.info/gems/yaml-merge/7.1.1
318
+ documentation_uri: https://www.rubydoc.info/gems/yaml-merge/7.1.4
320
319
  funding_uri: https://github.com/sponsors/pboling
321
320
  wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
322
321
  news_uri: https://www.railsbling.com/tags/yaml-merge
metadata.gz.sig CHANGED
Binary file
data/sig/yaml/merge.rbs DELETED
@@ -1,8 +0,0 @@
1
- module Yaml
2
- module Merge
3
- module Version
4
- VERSION: String
5
- end
6
- VERSION: String
7
- end
8
- end