Class: Markdown::Merge::CodeBlockMerger

Inherits:
Object
  • Object
show all
Defined in:
lib/markdown/merge/code_block_merger.rb

Overview

Merges fenced code blocks using language-specific *-merge gems.

When two code blocks with the same signature are matched, this class delegates the merge to the appropriate language-specific merger:

  • Ruby code → prism-merge
  • YAML code → psych-merge
  • JSON code → json-merge
  • TOML code → toml-merge

Examples:

Basic usage

merger = CodeBlockMerger.new
result = merger.merge_code_blocks(template_node, dest_node, preference: :destination)
if result[:merged]
  puts result[:content]
else
  # Fall back to standard resolution
end

With custom mergers

merger = CodeBlockMerger.new(
  mergers: {
    "ruby" => ->(template, dest, pref) { MyCustomRubyMerger.merge(template, dest, pref) },
  }
)

See Also:

Constant Summary collapse

DEFAULT_MERGERS =

Default language-to-merger mapping Each merger is a lambda that takes (template_content, dest_content, preference) and returns { merged: true/false, content: String, stats: Hash } simplecov:disable integration - DEFAULT_MERGERS lambdas require external gems

{
  # Ruby code blocks
  'ruby' => lambda { |template, dest, preference, **opts|
    require 'prism/merge'
    CodeBlockMerger.merge_with_prism(template, dest, preference, **opts)
  },
  'rb' => lambda { |template, dest, preference, **opts|
    require 'prism/merge'
    CodeBlockMerger.merge_with_prism(template, dest, preference, **opts)
  },

  # YAML code blocks
  'yaml' => lambda { |template, dest, preference, **opts|
    require 'psych/merge'
    CodeBlockMerger.merge_with_psych(template, dest, preference, **opts)
  },
  'yml' => lambda { |template, dest, preference, **opts|
    require 'psych/merge'
    CodeBlockMerger.merge_with_psych(template, dest, preference, **opts)
  },

  # JSON code blocks
  'json' => lambda { |template, dest, preference, **opts|
    require 'json/merge'
    CodeBlockMerger.merge_with_json(template, dest, preference, **opts)
  },

  # Markdown code blocks
  'markdown' => lambda { |template, dest, preference, **opts|
    require 'markdown/merge'
    CodeBlockMerger.merge_with_markdown(template, dest, preference, **opts)
  },
  'md' => lambda { |template, dest, preference, **opts|
    require 'markdown/merge'
    CodeBlockMerger.merge_with_markdown(template, dest, preference, **opts)
  },

  # TOML code blocks
  'toml' => lambda { |template, dest, preference, **opts|
    require 'toml/merge'
    CodeBlockMerger.merge_with_toml(template, dest, preference, **opts)
  }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mergers: {}, enabled: true) ⇒ CodeBlockMerger

Creates a new CodeBlockMerger.

Parameters:

  • mergers (Hash<String, Proc>) (defaults to: {})

    Custom language-to-merger mapping. Mergers are merged with defaults, allowing selective overrides.

  • enabled (Boolean) (defaults to: true)

    Whether to enable inner-merge (default: true)



96
97
98
99
100
# File 'lib/markdown/merge/code_block_merger.rb', line 96

def initialize(mergers: {}, enabled: true)
  @mergers = DEFAULT_MERGERS.merge(mergers)
  @enabled = enabled
  @runtime_delegates = build_runtime_delegates.freeze
end

Instance Attribute Details

#enabledBoolean (readonly)

Returns Whether inner-merge is enabled.

Returns:

  • (Boolean)

    Whether inner-merge is enabled



86
87
88
# File 'lib/markdown/merge/code_block_merger.rb', line 86

def enabled
  @enabled
end

#mergersHash<String, Proc> (readonly)

Returns Language to merger mapping.

Returns:

  • (Hash<String, Proc>)

    Language to merger mapping



83
84
85
# File 'lib/markdown/merge/code_block_merger.rb', line 83

def mergers
  @mergers
end

#runtime_delegatesArray<Ast::Merge::Runtime::Delegate> (readonly)

Returns Runtime delegates exposed by this merger.

Returns:

  • (Array<Ast::Merge::Runtime::Delegate>)

    Runtime delegates exposed by this merger



89
90
91
# File 'lib/markdown/merge/code_block_merger.rb', line 89

def runtime_delegates
  @runtime_delegates
end

Class Method Details

.merge_with_json(template, dest, preference, **opts) ⇒ Hash

Note:

Errors are handled by merge_code_blocks when called via DEFAULT_MERGERS

Merge JSON code using json-merge.

Parameters:

  • template (String)

    Template JSON code

  • dest (String)

    Destination JSON code

  • preference (Symbol)

    :destination or :template

Returns:

  • (Hash)

    Merge result

Raises:

  • (Json::Merge::ParseError)

    If template or dest has syntax errors



652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
# File 'lib/markdown/merge/code_block_merger.rb', line 652

def merge_with_json(template, dest, preference, **opts)
  merger = ::Json::Merge::SmartMerger.new(
    template,
    dest,
    preference: preference,
    add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
    resolution_mode: opts.fetch(:resolution_mode, :eager),
    unresolved_policy: opts[:unresolved_policy]
  )
  merge_result = merger.merge_result
  if opts[:apply_unresolved_resolutions]
    merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
  end

  {
    merged: true,
    content: merge_result.to_json,
    stats: merger.stats,
    unresolved_cases: merge_result.unresolved_cases
  }
end

.merge_with_markdown(template, dest, preference, **opts) ⇒ Hash

Merge Markdown code using markdown-merge.

Parameters:

  • template (String)

    Template Markdown code

  • dest (String)

    Destination Markdown code

  • preference (Symbol)

    :destination or :template

Returns:

  • (Hash)

    Merge result



680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/markdown/merge/code_block_merger.rb', line 680

def merge_with_markdown(template, dest, preference, **opts)
  nested_code_block_merger = CodeBlockMerger.new(
    mergers: opts.fetch(:nested_mergers, {}),
    enabled: opts.fetch(:inner_merge_code_blocks, true)
  )
  merger = ::Markdown::Merge::SmartMerger.new(
    template,
    dest,
    preference: preference,
    add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
    inner_merge_code_blocks: nested_code_block_merger,
    resolution_mode: opts.fetch(:resolution_mode, :eager),
    unresolved_policy: opts[:unresolved_policy]
  )
  merge_result = merger.merge_result
  if opts[:apply_unresolved_resolutions]
    merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
  end

  {
    merged: true,
    content: merge_result.to_s,
    stats: merger.stats,
    unresolved_cases: merge_result.unresolved_cases,
    metadata: {
      nested_runtime_session: merger.runtime_session&.to_h
    }
  }
end

.merge_with_prism(template, dest, preference, **opts) ⇒ Hash

Note:

Errors are handled by merge_code_blocks when called via DEFAULT_MERGERS

Merge Ruby code using prism-merge.

Parameters:

  • template (String)

    Template Ruby code

  • dest (String)

    Destination Ruby code

  • preference (Symbol)

    :destination or :template

Returns:

  • (Hash)

    Merge result

Raises:

  • (Prism::Merge::ParseError)

    If template or dest has syntax errors



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/markdown/merge/code_block_merger.rb', line 592

def merge_with_prism(template, dest, preference, **opts)
  merger = ::Prism::Merge::SmartMerger.new(
    template,
    dest,
    preference: preference,
    add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
    resolution_mode: opts.fetch(:resolution_mode, :eager),
    unresolved_policy: opts[:unresolved_policy]
  )
  merge_result = merger.merge_result
  if opts[:apply_unresolved_resolutions]
    merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
  end

  {
    merged: true,
    content: merge_result.to_s,
    stats: merger.stats,
    unresolved_cases: merge_result.unresolved_cases
  }
end

.merge_with_psych(template, dest, preference, **opts) ⇒ Hash

Note:

Errors are handled by merge_code_blocks when called via DEFAULT_MERGERS

Merge YAML code using psych-merge.

Parameters:

  • template (String)

    Template YAML code

  • dest (String)

    Destination YAML code

  • preference (Symbol)

    :destination or :template

Returns:

  • (Hash)

    Merge result

Raises:

  • (Psych::Merge::ParseError)

    If template or dest has syntax errors



622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/markdown/merge/code_block_merger.rb', line 622

def merge_with_psych(template, dest, preference, **opts)
  merger = ::Psych::Merge::SmartMerger.new(
    template,
    dest,
    preference: preference,
    add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
    resolution_mode: opts.fetch(:resolution_mode, :eager),
    unresolved_policy: opts[:unresolved_policy]
  )
  merge_result = merger.merge_result
  if opts[:apply_unresolved_resolutions]
    merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
  end

  {
    merged: true,
    content: merge_result.to_yaml,
    stats: merger.stats,
    unresolved_cases: merge_result.unresolved_cases
  }
end

.merge_with_toml(template, dest, preference, **opts) ⇒ Hash

Note:

Errors are handled by merge_code_blocks when called via DEFAULT_MERGERS

Merge TOML code using toml-merge.

Parameters:

  • template (String)

    Template TOML code

  • dest (String)

    Destination TOML code

  • preference (Symbol)

    :destination or :template

Returns:

  • (Hash)

    Merge result

Raises:

  • (Toml::Merge::ParseError)

    If template or dest has syntax errors



718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
# File 'lib/markdown/merge/code_block_merger.rb', line 718

def merge_with_toml(template, dest, preference, **opts)
  merger = ::Toml::Merge::SmartMerger.new(
    template,
    dest,
    preference: preference,
    add_template_only_nodes: opts.fetch(:add_template_only_nodes, false),
    resolution_mode: opts.fetch(:resolution_mode, :eager),
    unresolved_policy: opts[:unresolved_policy]
  )
  merge_result = merger.merge_result
  if opts[:apply_unresolved_resolutions]
    merge_result.apply_unresolved_resolutions!(opts[:apply_unresolved_resolutions])
  end

  {
    merged: true,
    content: merge_result.to_toml,
    stats: merger.stats,
    unresolved_cases: merge_result.unresolved_cases
  }
end

Instance Method Details

#merge_code_blocks(template_node, dest_node, preference:, runtime_session: nil, parent_operation: nil, **opts) ⇒ Hash

Merge two code blocks using the appropriate language-specific merger.

Parameters:

  • template_node (Object)

    Template code block node

  • dest_node (Object)

    Destination code block node

  • preference (Symbol)

    :destination or :template

  • opts (Hash)

    Additional options passed to the merger

Returns:

  • (Hash)

    { merged: Boolean, content: String, stats: Hash }



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/markdown/merge/code_block_merger.rb', line 120

def merge_code_blocks(template_node, dest_node, preference:, runtime_session: nil, parent_operation: nil, **opts)
  if runtime_session && parent_operation
    return merge_code_blocks_with_runtime(
      template_node,
      dest_node,
      preference: preference,
      runtime_session: runtime_session,
      parent_operation: parent_operation,
      **opts
    )
  end

  merge_code_blocks_without_runtime(template_node, dest_node, preference: preference, **opts)
end

#merge_code_blocks_without_runtime(template_node, dest_node, preference:, **opts) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/markdown/merge/code_block_merger.rb', line 135

def merge_code_blocks_without_runtime(template_node, dest_node, preference:, **opts)
  return not_merged('inner-merge disabled') unless @enabled

  language = extract_language(template_node) || extract_language(dest_node)
  return not_merged('no language specified') unless language

  template_content = extract_content(template_node)
  dest_content = extract_content(dest_node)

  perform_code_block_merge(
    language: language,
    template_content: template_content,
    dest_content: dest_content,
    preference: preference,
    reference_node: dest_node,
    **opts
  )
end

#supports_language?(language) ⇒ Boolean

Check if inner-merge is available for a language.

Parameters:

  • language (String)

    The language identifier from fence_info

Returns:

  • (Boolean)

    true if a merger exists for this language



106
107
108
109
110
111
# File 'lib/markdown/merge/code_block_merger.rb', line 106

def supports_language?(language)
  return false unless @enabled
  return false if language.nil? || language.empty?

  @mergers.key?(language.downcase)
end