Class: Psych::Merge::SmartMerger

Inherits:
Ast::Merge::SmartMergerBase
  • Object
show all
Includes:
Ast::Merge::Runtime::RootSessionSupport
Defined in:
lib/psych/merge/smart_merger.rb

Overview

Main entry point for intelligent YAML file merging. SmartMerger orchestrates the merge process using FileAnalysis, ConflictResolver, and MergeResult to merge two YAML files intelligently.

Examples:

Basic merge (destination customizations preserved)

merger = SmartMerger.new(template_yaml, dest_yaml)
result = merger.merge
File.write("output.yml", result)

Template updates win

merger = SmartMerger.new(
  template_yaml,
  dest_yaml,
  preference: :template,
  add_template_only_nodes: true
)
result = merger.merge

Recursive merge with template additions

merger = SmartMerger.new(
  template_yaml,
  dest_yaml,
  recursive: true,
  add_template_only_nodes: true
)
# Nested structures are merged recursively, template-only items added

With custom signature generator

sig_gen = ->(node) {
  if node.is_a?(MappingEntry) && node.key_name == "version"
    [:special_version, node.key_name]
  else
    node # Fall through to default
  end
}
merger = SmartMerger.new(template, dest, signature_generator: sig_gen)

With regions (embedded content)

merger = SmartMerger.new(template, dest,
  regions: [{ detector: SomeDetector.new, merger_class: SomeMerger }])

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(template_content, dest_content, signature_generator: nil, preference: :destination, add_template_only_nodes: false, add_template_only_sequence_items: nil, remove_template_missing_nodes: false, corruption_handling: :heal, recursive: true, freeze_token: FileAnalysis::DEFAULT_FREEZE_TOKEN, match_refiner: nil, regions: nil, region_placeholder: nil, node_typing: nil, comment_merge_policy: :preserve_destination, **options) ⇒ SmartMerger

Creates a new SmartMerger for intelligent YAML file merging.

Parameters:

  • template_content (String)

    Template YAML source code

  • dest_content (String)

    Destination YAML source code

  • signature_generator (Proc, nil) (defaults to: nil)

    Custom signature generator

  • preference (Symbol) (defaults to: :destination)

    Which version to prefer when nodes have matching signatures:

    • :destination (default) - Keep destination version (customizations)
    • :template - Use template version (updates)
  • add_template_only_nodes (Boolean) (defaults to: false)

    Whether to add nodes only in template

  • remove_template_missing_nodes (Boolean) (defaults to: false)

    Whether to remove destination nodes not in template

  • recursive (Boolean, Integer) (defaults to: true)

    Whether to merge nested structures recursively

    • true: unlimited depth (default)
    • false: disabled
    • Integer > 0: max depth
  • freeze_token (String) (defaults to: FileAnalysis::DEFAULT_FREEZE_TOKEN)

    Token for freeze block markers

  • match_refiner (#call, nil) (defaults to: nil)

    Optional match refiner for fuzzy matching of unmatched nodes. Default: nil (fuzzy matching disabled). Set to MappingMatchRefiner.new to enable fuzzy key matching.

  • regions (Array<Hash>, nil) (defaults to: nil)

    Region configurations for nested merging

  • region_placeholder (String, nil) (defaults to: nil)

    Custom placeholder for regions

  • node_typing (Hash{Symbol,String => #call}, nil) (defaults to: nil)

    Node typing configuration for per-node-type merge preferences

  • comment_merge_policy (Symbol, String) (defaults to: :preserve_destination)

    How matched-node comments are selected. :preserve_destination is conservative for git-style merges; :template_fallback_when_missing restores template documentation comments when the destination node has no comments.

  • options (Hash)

    Additional options for forward compatibility

Raises:



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/psych/merge/smart_merger.rb', line 81

def initialize(
  template_content,
  dest_content,
  signature_generator: nil,
  preference: :destination,
  add_template_only_nodes: false,
  add_template_only_sequence_items: nil,
  remove_template_missing_nodes: false,
  corruption_handling: :heal,
  recursive: true,
  freeze_token: FileAnalysis::DEFAULT_FREEZE_TOKEN,
  match_refiner: nil,
  regions: nil,
  region_placeholder: nil,
  node_typing: nil,
  comment_merge_policy: :preserve_destination,
  **options
)
  @remove_template_missing_nodes = remove_template_missing_nodes
  @corruption_handling = ::Ast::Merge::Healer.normalize_mode(corruption_handling)
  @recursive = recursive
  @add_template_only_sequence_items = add_template_only_sequence_items
  @comment_merge_policy = comment_merge_policy
  super(
    template_content,
    dest_content,
    signature_generator: signature_generator,
    preference: preference,
    add_template_only_nodes: add_template_only_nodes,
    freeze_token: freeze_token,
    match_refiner: match_refiner,
    regions: regions,
    region_placeholder: region_placeholder,
    node_typing: node_typing,
    **options
  )
end

Instance Attribute Details

#comment_merge_policySymbol, String (readonly)

Returns Matched-node comment selection policy.

Returns:

  • (Symbol, String)

    Matched-node comment selection policy



126
127
128
# File 'lib/psych/merge/smart_merger.rb', line 126

def comment_merge_policy
  @comment_merge_policy
end

#corruption_handlingObject (readonly)

Returns the value of attribute corruption_handling.



48
49
50
# File 'lib/psych/merge/smart_merger.rb', line 48

def corruption_handling
  @corruption_handling
end

#recursiveBoolean, Integer (readonly)

Returns Whether to merge nested structures recursively.

Returns:

  • (Boolean, Integer)

    Whether to merge nested structures recursively



123
124
125
# File 'lib/psych/merge/smart_merger.rb', line 123

def recursive
  @recursive
end

#remove_template_missing_nodesBoolean (readonly)

Returns Whether to remove destination nodes not in template.

Returns:

  • (Boolean)

    Whether to remove destination nodes not in template



120
121
122
# File 'lib/psych/merge/smart_merger.rb', line 120

def remove_template_missing_nodes
  @remove_template_missing_nodes
end

#runtime_sessionObject (readonly)

Returns the value of attribute runtime_session.



48
49
50
# File 'lib/psych/merge/smart_merger.rb', line 48

def runtime_session
  @runtime_session
end

Instance Method Details

#errorsArray

Get any parse errors from template or destination.

Returns:

  • (Array)

    Array of errors



199
200
201
202
203
204
# File 'lib/psych/merge/smart_merger.rb', line 199

def errors
  errors = []
  errors.concat(@template_analysis.errors.map { |e| { source: :template, error: e } })
  errors.concat(@dest_analysis.errors.map { |e| { source: :destination, error: e } })
  errors
end

#mergeString

Perform the merge and return the result as a YAML string.

Returns:

  • (String)

    Merged YAML content



131
132
133
# File 'lib/psych/merge/smart_merger.rb', line 131

def merge
  merge_result.to_yaml
end

#merge_resultMergeResult

Perform the merge operation and return the full MergeResult object.

Returns:

  • (MergeResult)

    The merge result containing merged YAML content and metadata



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/psych/merge/smart_merger.rb', line 138

def merge_result
  return @merge_result if @merge_result

  root_operation = start_runtime_session!
  @merge_result = super
  complete_runtime_session!(root_operation, @merge_result)
  @merge_result
rescue StandardError => e
  fail_runtime_session!(root_operation, e)
  raise
end

#merge_with_debugHash

Perform the merge and return detailed results including debug info.

Returns:

  • (Hash)

    Hash containing :content, :statistics, :decisions



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/psych/merge/smart_merger.rb', line 153

def merge_with_debug
  result_obj = merge_result
  template_analysis_debug = {
    valid: @template_analysis.valid?,
    statements: @template_analysis.statements.size,
    freeze_blocks: @template_analysis.freeze_blocks.size
  }
  dest_analysis_debug = {
    valid: @dest_analysis.valid?,
    statements: @dest_analysis.statements.size,
    freeze_blocks: @dest_analysis.freeze_blocks.size
  }

  {
    content: result_obj.to_yaml,
    debug: {
      template_statements: template_analysis_debug[:statements],
      dest_statements: dest_analysis_debug[:statements],
      preference: @preference,
      add_template_only_nodes: @add_template_only_nodes,
      remove_template_missing_nodes: @remove_template_missing_nodes,
      resolution_mode: @resolution_mode,
      corruption_handling: @corruption_handling,
      recursive: @recursive,
      freeze_token: @freeze_token,
      runtime_operation_count: runtime_session&.operations&.size || 0,
      runtime_diagnostic_count: runtime_session&.diagnostics&.size || 0
    },
    runtime: runtime_session&.to_h,
    statistics: result_obj.statistics,
    decisions: result_obj.decision_summary,
    template_analysis: template_analysis_debug,
    dest_analysis: dest_analysis_debug
  }
end

#valid?Boolean

Check if both files were parsed successfully.

Returns:

  • (Boolean)


192
193
194
# File 'lib/psych/merge/smart_merger.rb', line 192

def valid?
  @template_analysis.valid? && @dest_analysis.valid?
end