yaml-merge 7.1.3 → 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: 861dc2bba17b21e44e3e46b947e458d7e5114c376f1076648cf14f15793e9a7d
4
- data.tar.gz: 246f068f78244935250454a8f5c4ca38e1735286f48252f17f023083aaeedb0e
3
+ metadata.gz: 48a5059a57b0ae4e4a6c114f2a867611725094229e1d813d1db3ae648ac011af
4
+ data.tar.gz: e9b8d83835c84f4ffdf1c0699cdc6c81c3737a9c5948711d6cf690934456fec4
5
5
  SHA512:
6
- metadata.gz: d87a3509a72a8c7ab1d37e098ff47bd689d1c600228e740cca1dd08565062b17e978d8ffb985d74ef4037227354cf9e75b11f84a392fc672ac97ada2bd11bb29
7
- data.tar.gz: 49e729482712daec1b3a62fd3851d4e91a728bbeed26ec5af4533adf1c8a7bc1a5f155700099a0fe6ddbf526ab7d059ea738b7d8b79cd946141e7f9652206e64
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
 
@@ -277,7 +277,7 @@ If none of the available licenses suit your use case, please [contact us](mailto
277
277
  [📌gitmoji]: https://gitmoji.dev
278
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
279
279
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
280
- [🧮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
281
281
  [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
282
282
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
283
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.3'
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
@@ -3,10 +3,12 @@
3
3
  require 'json'
4
4
  require 'tree_haver'
5
5
  require 'ast/merge'
6
- require 'psych/merge'
7
6
  require_relative 'merge/version'
8
7
 
9
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
10
12
  module Merge
11
13
  PACKAGE_NAME = 'yaml-merge'
12
14
  DESTINATION_WINS_ARRAY_POLICY = {
@@ -15,6 +17,22 @@ module Yaml
15
17
  }.freeze
16
18
  BACKEND_REFERENCE = TreeHaver::KREUZBERG_LANGUAGE_PACK_BACKEND
17
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
18
36
 
19
37
  class Error < Ast::Merge::Error; end
20
38
  class ParseError < Ast::Merge::ParseError; end
@@ -90,20 +108,57 @@ module Yaml
90
108
  return unsupported_feature_parse_result("Unsupported YAML backend #{diagnostic_backend}.")
91
109
  end
92
110
 
93
- tree = parse_tree_sitter_source(:yaml, source, backend: requested)
94
- collect_parse_errors(tree.root_node)
95
-
96
- analyze_yaml_document(yaml_value_from_tree(tree.root_node, source), dialect)
111
+ parse_yaml_tree(source, dialect, requested)
97
112
  rescue TreeHaver::Error, StandardError => e
98
113
  parse_error_result(e.message)
99
114
  end
100
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
+
101
123
  def analyze_yaml_document(parsed, dialect)
102
- Ast::Merge::YamlDocument.analyze(parsed, dialect)
124
+ return unsupported_feature_parse_result("Unsupported YAML dialect #{dialect}.") unless dialect == 'yaml'
125
+ return parse_error_result('YAML documents must parse to a mapping root.') unless parsed.is_a?(Hash)
126
+
127
+ validated = validate_yaml_node(parsed, '')
128
+ return invalid_yaml_analysis(validated) unless validated[:ok]
129
+
130
+ yaml_analysis_result(validated[:value])
103
131
  end
104
132
 
133
+ def yaml_analysis_result(value)
134
+ {
135
+ ok: true,
136
+ diagnostics: [],
137
+ analysis: yaml_document_analysis(value),
138
+ policies: []
139
+ }
140
+ end
141
+ private_class_method :yaml_analysis_result
142
+
143
+ def yaml_document_analysis(value)
144
+ {
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)
151
+ }
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
+
105
160
  def match_yaml_owners(template, destination)
106
- Ast::Merge::YamlDocument.match_owners(template, destination)
161
+ Ast::Merge::OwnerSelection.match_by_path(template, destination)
107
162
  end
108
163
 
109
164
  def merge_yaml(template_source, destination_source, dialect, backend: nil)
@@ -121,45 +176,71 @@ module Yaml
121
176
 
122
177
  def merge_yaml_with_parser(template_source, destination_source, dialect, backend: nil)
123
178
  template = yield(template_source, dialect)
124
- return { ok: false, diagnostics: template[:diagnostics], policies: [] } unless template[:ok]
179
+ return parser_failure_result(template, :template) unless template[:ok]
125
180
 
126
181
  destination = yield(destination_source, dialect)
127
- unless destination[:ok]
128
- return {
129
- ok: false,
130
- diagnostics: destination[:diagnostics].map do |diagnostic|
131
- diagnostic[:category] == 'parse_error' ? diagnostic.merge(category: 'destination_parse_error') : diagnostic
132
- end,
133
- policies: []
134
- }
135
- 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)
186
+
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
136
200
 
137
- template_document = template.dig(:analysis, :document)
138
- destination_document = destination.dig(:analysis, :document)
139
- unless template_document.is_a?(Hash) && destination_document.is_a?(Hash)
140
- return parse_error_merge_result('YAML documents must parse to a mapping root.')
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
141
207
  end
142
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)
143
224
  {
144
225
  ok: true,
145
226
  diagnostics: [],
146
- output: with_tree_sitter_backend(backend) do
147
- SmartMerger.new(
148
- template_source,
149
- destination_source,
150
- add_template_only_nodes: true,
151
- merge_sequences: false
152
- ).merge_result.to_yaml
153
- end,
227
+ output: yaml_merge_output(template_source, destination_source, backend),
154
228
  policies: [DESTINATION_WINS_ARRAY_POLICY]
155
229
  }
156
- rescue StandardError => e
157
- {
158
- ok: false,
159
- diagnostics: [{ severity: 'error', category: 'destination_parse_error', message: e.message }],
160
- policies: []
161
- }
162
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
163
244
 
164
245
  def resolve_backend(backend)
165
246
  return backend.to_s unless backend.to_s.empty?
@@ -210,30 +291,61 @@ module Yaml
210
291
 
211
292
  def yaml_value_from_tree(node, source)
212
293
  named = yaml_named_children(node)
213
- case node.type
214
- when 'stream', 'document', 'block_node', 'flow_node'
215
- return nil if named.empty?
216
-
217
- yaml_value_from_tree(named.first, source)
218
- when 'block_mapping', 'flow_mapping'
219
- yaml_mapping_from_node(node, source)
220
- when 'block_sequence', 'flow_sequence'
221
- yaml_sequence_from_node(node, source)
222
- when 'block_sequence_item'
223
- value_node = named.first
224
- value_node ? yaml_value_from_tree(value_node, source) : nil
225
- when 'plain_scalar', 'string_scalar', 'double_quote_scalar', 'single_quote_scalar'
226
- yaml_scalar_from_text(source[node.start_byte...node.end_byte])
227
- else
228
- if named.length == 1
229
- yaml_value_from_tree(named.first, source)
230
- else
231
- yaml_scalar_from_text(source[node.start_byte...node.end_byte])
232
- end
233
- 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)
234
298
  end
235
299
  private_class_method :yaml_value_from_tree
236
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
+
237
349
  def yaml_mapping_from_node(node, source)
238
350
  yaml_named_children(node).each_with_object({}) do |child, mapping|
239
351
  next unless child.type.end_with?('mapping_pair')
@@ -267,12 +379,18 @@ module Yaml
267
379
 
268
380
  def yaml_scalar_from_text(text)
269
381
  stripped = text.to_s.strip
270
- return nil if stripped.empty? || stripped == '~' || stripped.casecmp('null').zero?
271
- return true if stripped.casecmp('true').zero?
272
- 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)
273
392
  return stripped.to_i if stripped.match?(/\A[-+]?\d+\z/)
274
393
  return stripped.to_f if stripped.match?(/\A[-+]?(?:\d+\.\d*|\d*\.\d+)(?:[eE][-+]?\d+)?\z/)
275
-
276
394
  if stripped.start_with?('"') && stripped.end_with?('"')
277
395
  return unescape_yaml_double_quoted_scalar(stripped[1...-1])
278
396
  end
@@ -280,47 +398,49 @@ module Yaml
280
398
 
281
399
  stripped
282
400
  end
283
- private_class_method :yaml_scalar_from_text
401
+ private_class_method :yaml_numeric_or_quoted_scalar
284
402
 
285
403
  def unescape_yaml_double_quoted_scalar(value)
286
- value.gsub(%r{\\(["\\/bfnrt])}) do
287
- {
288
- '"' => '"',
289
- '\\' => '\\',
290
- '/' => '/',
291
- 'b' => "\b",
292
- 'f' => "\f",
293
- 'n' => "\n",
294
- 'r' => "\r",
295
- 't' => "\t"
296
- }.fetch(Regexp.last_match(1))
297
- end
404
+ value.gsub(%r{\\(["\\/bfnrt])}) { YAML_DOUBLE_QUOTE_ESCAPES.fetch(Regexp.last_match(1)) }
298
405
  end
299
406
  private_class_method :unescape_yaml_double_quoted_scalar
300
407
 
301
408
  def validate_yaml_node(value, path)
302
- if scalar?(value)
303
- { ok: true, value: value }
304
- elsif value.is_a?(Array)
305
- value.each_with_index.each_with_object({ ok: true, value: [] }) do |(item, index), memo|
306
- validated = validate_yaml_node(item, "#{path}/#{index}")
307
- return validated unless validated[:ok]
308
-
309
- memo[:value] << validated[:value]
310
- end
311
- elsif value.is_a?(Hash)
312
- value.keys.each_with_object({ ok: true, value: {} }) do |key, memo|
313
- validated = validate_yaml_node(value[key], "#{path}/#{key}")
314
- 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)
315
411
 
316
- memo[:value][key] = validated[:value]
317
- end
318
- else
319
- unsupported_feature_result("Unsupported YAML value at #{display_path(path)}. Only mappings, scalar values, and sequences are supported.")
320
- end
412
+ unsupported_feature_result(
413
+ "Unsupported YAML value at #{display_path(path)}. " \
414
+ 'Only mappings, scalar values, and sequences are supported.'
415
+ )
321
416
  end
322
417
  private_class_method :validate_yaml_node
323
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
+
324
444
  def scalar?(value)
325
445
  value.nil? || value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false
326
446
  end
@@ -367,60 +487,72 @@ module Yaml
367
487
 
368
488
  def render_yaml_sequence(sequence, indent)
369
489
  prefix = ' ' * indent
370
- sequence.flat_map do |item|
371
- if scalar?(item)
372
- ["#{prefix}- #{render_yaml_scalar(item)}"]
373
- elsif item.is_a?(Hash)
374
- ["#{prefix}-"] + render_yaml_mapping(item, indent + 2)
375
- elsif item.is_a?(Array)
376
- ["#{prefix}-"] + render_yaml_sequence(item, indent + 2)
377
- else
378
- ["#{prefix}- #{render_yaml_scalar(item)}"]
379
- end
380
- end
490
+ sequence.flat_map { |item| render_yaml_sequence_item(item, prefix, indent) }
381
491
  end
382
492
  private_class_method :render_yaml_sequence
383
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
+
384
507
  def canonical_yaml(mapping)
385
508
  "#{render_yaml_mapping(mapping).join("\n")}\n"
386
509
  end
387
510
  private_class_method :canonical_yaml
388
511
 
389
512
  def collect_yaml_owners(mapping, prefix = '')
390
- mapping.keys.sort.flat_map do |key|
391
- path = "#{prefix}/#{key}"
392
- value = mapping[key]
393
- if value.is_a?(Array)
394
- [{ path: path, owner_kind: 'key_value', match_key: key }] +
395
- value.each_with_index.flat_map do |item, index|
396
- item_path = "#{path}/#{index}"
397
- nested = item.is_a?(Hash) ? collect_yaml_owners(item, item_path) : []
398
- [{ path: item_path, owner_kind: 'sequence_item' }] + nested
399
- end
400
- elsif value.is_a?(Hash)
401
- [{ path: path, owner_kind: 'mapping', match_key: key }] + collect_yaml_owners(value, path)
402
- else
403
- [{ path: path, owner_kind: 'key_value', match_key: key }]
404
- end
405
- end
513
+ mapping.keys.sort.flat_map { |key| collect_yaml_owners_for_value(key, mapping[key], prefix) }
406
514
  end
407
515
  private_class_method :collect_yaml_owners
408
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
+
409
537
  def merge_yaml_mappings(template, destination)
410
538
  ordered_merge_keys(template, destination).each_with_object({}) do |key, merged|
411
- merged[key] = if !template.key?(key)
412
- destination[key]
413
- elsif !destination.key?(key)
414
- template[key]
415
- elsif template[key].is_a?(Hash) && destination[key].is_a?(Hash)
416
- merge_yaml_mappings(template[key], destination[key])
417
- else
418
- destination[key]
419
- end
539
+ merged[key] = merge_yaml_mapping_value(template, destination, key)
420
540
  end
421
541
  end
422
542
  private_class_method :merge_yaml_mappings
423
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
+
424
556
  def ordered_merge_keys(template, destination)
425
557
  template.keys + destination.keys.reject { |key| template.key?(key) }
426
558
  end
@@ -453,8 +585,7 @@ module Yaml
453
585
  end
454
586
  private_class_method :unsupported_feature_result
455
587
  end
588
+ # rubocop:enable Metrics/ModuleLength
456
589
  end
457
590
 
458
591
  Yaml::Merge.register_backend!
459
- require_relative 'merge/provider'
460
- Yaml::Merge.register_provider!
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.3
4
+ version: 7.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -43,42 +43,28 @@ dependencies:
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 7.1.3
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.3
54
- - !ruby/object:Gem::Dependency
55
- name: psych-merge
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - '='
59
- - !ruby/object:Gem::Version
60
- version: 7.1.3
61
- type: :runtime
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - '='
66
- - !ruby/object:Gem::Version
67
- version: 7.1.3
53
+ version: 7.1.4
68
54
  - !ruby/object:Gem::Dependency
69
55
  name: tree_haver
70
56
  requirement: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - '='
73
59
  - !ruby/object:Gem::Version
74
- version: 7.1.3
60
+ version: 7.1.4
75
61
  type: :runtime
76
62
  prerelease: false
77
63
  version_requirements: !ruby/object:Gem::Requirement
78
64
  requirements:
79
65
  - - '='
80
66
  - !ruby/object:Gem::Version
81
- version: 7.1.3
67
+ version: 7.1.4
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: version_gem
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +94,7 @@ dependencies:
108
94
  version: '3.0'
109
95
  - - ">="
110
96
  - !ruby/object:Gem::Version
111
- version: 3.0.0
97
+ version: 3.0.7
112
98
  type: :development
113
99
  prerelease: false
114
100
  version_requirements: !ruby/object:Gem::Requirement
@@ -118,7 +104,7 @@ dependencies:
118
104
  version: '3.0'
119
105
  - - ">="
120
106
  - !ruby/object:Gem::Version
121
- version: 3.0.0
107
+ version: 3.0.7
122
108
  - !ruby/object:Gem::Dependency
123
109
  name: bundler-audit
124
110
  requirement: !ruby/object:Gem::Requirement
@@ -216,7 +202,7 @@ dependencies:
216
202
  version: '2.0'
217
203
  - - ">="
218
204
  - !ruby/object:Gem::Version
219
- version: 2.0.19
205
+ version: 2.0.20
220
206
  type: :development
221
207
  prerelease: false
222
208
  version_requirements: !ruby/object:Gem::Requirement
@@ -226,7 +212,7 @@ dependencies:
226
212
  version: '2.0'
227
213
  - - ">="
228
214
  - !ruby/object:Gem::Version
229
- version: 2.0.19
215
+ version: 2.0.20
230
216
  - !ruby/object:Gem::Dependency
231
217
  name: turbo_tests2
232
218
  requirement: !ruby/object:Gem::Requirement
@@ -236,7 +222,7 @@ dependencies:
236
222
  version: '3.2'
237
223
  - - ">="
238
224
  - !ruby/object:Gem::Version
239
- version: 3.2.5
225
+ version: 3.2.6
240
226
  type: :development
241
227
  prerelease: false
242
228
  version_requirements: !ruby/object:Gem::Requirement
@@ -246,7 +232,7 @@ dependencies:
246
232
  version: '3.2'
247
233
  - - ">="
248
234
  - !ruby/object:Gem::Version
249
- version: 3.2.5
235
+ version: 3.2.6
250
236
  - !ruby/object:Gem::Dependency
251
237
  name: ruby-progressbar
252
238
  requirement: !ruby/object:Gem::Requirement
@@ -290,7 +276,7 @@ dependencies:
290
276
  version: '2.0'
291
277
  - - ">="
292
278
  - !ruby/object:Gem::Version
293
- version: 2.0.11
279
+ version: 2.0.12
294
280
  type: :development
295
281
  prerelease: false
296
282
  version_requirements: !ruby/object:Gem::Requirement
@@ -300,7 +286,7 @@ dependencies:
300
286
  version: '2.0'
301
287
  - - ">="
302
288
  - !ruby/object:Gem::Version
303
- version: 2.0.11
289
+ version: 2.0.12
304
290
  description: "☯️ Portable YAML analysis, owner matching, and merge behavior for Structured
305
291
  Merge."
306
292
  email:
@@ -318,20 +304,18 @@ files:
318
304
  - lib/yaml/merge/file_analysis.rb
319
305
  - lib/yaml/merge/merge_result.rb
320
306
  - lib/yaml/merge/node_wrapper.rb
321
- - lib/yaml/merge/provider.rb
322
307
  - lib/yaml/merge/smart_merger.rb
323
308
  - lib/yaml/merge/version.rb
324
- - sig/yaml/merge.rbs
325
309
  homepage: https://github.com/structuredmerge/structuredmerge-ruby
326
310
  licenses:
327
311
  - AGPL-3.0-only
328
312
  - PolyForm-Small-Business-1.0.0
329
313
  metadata:
330
314
  homepage_uri: https://structuredmerge.org
331
- source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.3
332
- changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.3/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
333
317
  bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
334
- documentation_uri: https://www.rubydoc.info/gems/yaml-merge/7.1.3
318
+ documentation_uri: https://www.rubydoc.info/gems/yaml-merge/7.1.4
335
319
  funding_uri: https://github.com/sponsors/pboling
336
320
  wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
337
321
  news_uri: https://www.railsbling.com/tags/yaml-merge
metadata.gz.sig CHANGED
Binary file
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Yaml
4
- # Portable YAML workflow integration.
5
- module Merge
6
- # YAML workflow selector delegating source-preserving operations to Psych.
7
- class Provider
8
- BACKEND = :'kreuzberg-language-pack'
9
- DELEGATED_BACKEND = :psych
10
- DELEGATED_PROVIDER_ID = 'ruby.yaml.psych'
11
- DEFAULT_PROFILE = :source_preserving
12
-
13
- def provider_id = 'ruby.yaml'
14
- def family = 'yaml'
15
-
16
- def capabilities
17
- {
18
- operations: Ast::Merge::ProviderContract::OPERATIONS,
19
- dialects: %i[yaml],
20
- backends: [BACKEND],
21
- profiles: [DEFAULT_PROFILE],
22
- role: :workflow,
23
- source_preservation: Psych::Merge.merge_provider.capabilities.fetch(:source_preservation)
24
- }.freeze
25
- end
26
-
27
- Ast::Merge::ProviderContract::OPERATIONS.each do |operation|
28
- define_method(operation) do |request|
29
- delegated = Ast::Merge.dispatch_provider(operation, delegated_request(request))
30
- delegated.merge(provider: workflow_identity(request, delegated))
31
- end
32
- end
33
-
34
- private
35
-
36
- def delegated_request(request)
37
- request.merge(
38
- provider_id: DELEGATED_PROVIDER_ID,
39
- family: :yaml,
40
- dialect: :yaml,
41
- backend: DELEGATED_BACKEND,
42
- profile_id: DEFAULT_PROFILE
43
- )
44
- end
45
-
46
- def workflow_identity(request, delegated)
47
- {
48
- provider_id: provider_id,
49
- family: family,
50
- dialect: request[:dialect] || :yaml,
51
- backend: request[:backend] || BACKEND,
52
- package: PACKAGE_NAME,
53
- package_version: Yaml::Merge::Version::VERSION,
54
- delegated_provider: delegated_identity(delegated.fetch(:provider))
55
- }
56
- end
57
-
58
- def delegated_identity(identity)
59
- {
60
- provider_id: identity.fetch(:provider_id),
61
- backend: identity.fetch(:backend),
62
- package: identity[:package],
63
- package_version: identity[:package_version]
64
- }.compact
65
- end
66
- end
67
-
68
- class << self
69
- def merge_provider
70
- @merge_provider ||= Provider.new
71
- end
72
-
73
- def register_provider!(replace: false)
74
- Ast::Merge.register_provider(merge_provider, replace: replace)
75
- end
76
- end
77
- end
78
- end
data/sig/yaml/merge.rbs DELETED
@@ -1,21 +0,0 @@
1
- module Yaml
2
- module Merge
3
- class Provider
4
- def provider_id: () -> String
5
- def family: () -> String
6
- def capabilities: () -> Hash[Symbol, untyped]
7
- def analyze: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
8
- def diff2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
9
- def merge2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
10
- def merge3: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
11
- end
12
-
13
- def self.merge_provider: () -> Provider
14
- def self.register_provider!: (?replace: bool) -> untyped
15
-
16
- module Version
17
- VERSION: String
18
- end
19
- VERSION: String
20
- end
21
- end