Class: Markdown::Merge::FileAligner
- Inherits:
-
Object
- Object
- Markdown::Merge::FileAligner
- Defined in:
- lib/markdown/merge/file_aligner.rb
Overview
Aligns Markdown block elements between template and destination files.
Uses structural signatures to match headings, paragraphs, lists, code blocks, and other block elements. The alignment is then used by SmartMerger to determine how to combine the files.
Instance Attribute Summary collapse
-
#dest_analysis ⇒ FileAnalysisBase
readonly
Destination file analysis.
-
#match_refiner ⇒ #call?
readonly
Optional match refiner for fuzzy matching.
-
#template_analysis ⇒ FileAnalysisBase
readonly
Template file analysis.
Instance Method Summary collapse
-
#align ⇒ Array<Hash>
Perform alignment between template and destination statements.
-
#initialize(template_analysis, dest_analysis, match_refiner: nil) ⇒ FileAligner
constructor
Initialize a file aligner.
Constructor Details
#initialize(template_analysis, dest_analysis, match_refiner: nil) ⇒ FileAligner
Initialize a file aligner
42 43 44 45 46 |
# File 'lib/markdown/merge/file_aligner.rb', line 42 def initialize(template_analysis, dest_analysis, match_refiner: nil) @template_analysis = template_analysis @dest_analysis = dest_analysis @match_refiner = match_refiner end |
Instance Attribute Details
#dest_analysis ⇒ FileAnalysisBase (readonly)
Returns Destination file analysis.
32 33 34 |
# File 'lib/markdown/merge/file_aligner.rb', line 32 def dest_analysis @dest_analysis end |
#match_refiner ⇒ #call? (readonly)
Returns Optional match refiner for fuzzy matching.
35 36 37 |
# File 'lib/markdown/merge/file_aligner.rb', line 35 def match_refiner @match_refiner end |
#template_analysis ⇒ FileAnalysisBase (readonly)
Returns Template file analysis.
29 30 31 |
# File 'lib/markdown/merge/file_aligner.rb', line 29 def template_analysis @template_analysis end |
Instance Method Details
#align ⇒ Array<Hash>
Perform alignment between template and destination statements
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/markdown/merge/file_aligner.rb', line 51 def align template_statements = @template_analysis.statements dest_statements = @dest_analysis.statements # Build signature maps template_by_sig = build_signature_map(template_statements, @template_analysis) dest_by_sig = build_signature_map(dest_statements, @dest_analysis) # Track which indices have been matched matched_template = Set.new matched_dest = Set.new alignment = [] # First pass: find matches by signature template_by_sig.each do |sig, template_indices| next unless dest_by_sig.key?(sig) dest_indices = dest_by_sig[sig] # Match indices pairwise (first template with first dest, etc.) template_indices.zip(dest_indices).each do |t_idx, d_idx| next unless t_idx && d_idx alignment << { type: :match, template_index: t_idx, dest_index: d_idx, signature: sig, template_node: template_statements[t_idx], dest_node: dest_statements[d_idx], } matched_template << t_idx matched_dest << d_idx end end # Apply match refiner to find additional fuzzy matches if @match_refiner unmatched_t_nodes = template_statements.each_with_index.reject { |_, i| matched_template.include?(i) }.map(&:first) unmatched_d_nodes = dest_statements.each_with_index.reject { |_, i| matched_dest.include?(i) }.map(&:first) unless unmatched_t_nodes.empty? || unmatched_d_nodes.empty? refiner_matches = @match_refiner.call(unmatched_t_nodes, unmatched_d_nodes, { template_analysis: @template_analysis, dest_analysis: @dest_analysis, }) refiner_matches.each do |match| t_idx = template_statements.index(match.template_node) d_idx = dest_statements.index(match.dest_node) next unless t_idx && d_idx next if matched_template.include?(t_idx) || matched_dest.include?(d_idx) alignment << { type: :match, template_index: t_idx, dest_index: d_idx, signature: [:refined_match, match.score], template_node: match.template_node, dest_node: match.dest_node, } matched_template << t_idx matched_dest << d_idx end end end # Second pass: add template-only entries template_statements.each_with_index do |stmt, idx| next if matched_template.include?(idx) alignment << { type: :template_only, template_index: idx, dest_index: nil, signature: @template_analysis.signature_at(idx), template_node: stmt, dest_node: nil, } end # Third pass: add dest-only entries dest_statements.each_with_index do |stmt, idx| next if matched_dest.include?(idx) alignment << { type: :dest_only, template_index: nil, dest_index: idx, signature: @dest_analysis.signature_at(idx), template_node: nil, dest_node: stmt, } end # Sort by appearance order (destination order for matched/dest-only, then template-only) alignment.sort_by! do |entry| case entry[:type] when :match [0, entry[:dest_index]] when :dest_only [0, entry[:dest_index]] when :template_only [1, entry[:template_index]] else # :nocov: defensive - only :match, :dest_only, :template_only types are created [2, 0] # Unknown types sort last # :nocov: end end DebugLogger.debug("Alignment complete", { total: alignment.size, matches: alignment.count { |e| e[:type] == :match }, template_only: alignment.count { |e| e[:type] == :template_only }, dest_only: alignment.count { |e| e[:type] == :dest_only }, }) alignment end |