module Ast
module Merge
VERSION: String
# Base error class for all merge operations
class Error < StandardError
end
# Raised when parsing fails (template or destination)
class ParseError < Error
attr_reader errors: untyped
attr_reader content: String?
def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
end
# Raised when parsing the template (source) content fails
class TemplateParseError < ParseError
def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
end
# Raised when parsing the destination content fails
class DestinationParseError < ParseError
def initialize: (?String? message, ?errors: untyped, ?content: String?) -> void
end
# Base class for freeze block nodes in AST merge libraries.
class FreezeNode
# Pattern configuration for freeze block markers
MARKER_PATTERNS: Hash[Symbol, Hash[Symbol, Regexp]]
# Default pattern when none specified
DEFAULT_PATTERN: Symbol
# Error raised when a freeze block has invalid structure
class InvalidStructureError < StandardError
attr_reader start_line: Integer?
attr_reader end_line: Integer?
attr_reader unclosed_nodes: Array[untyped]
def initialize: (
String message,
?start_line: Integer?,
?end_line: Integer?,
?unclosed_nodes: Array[untyped]
) -> void
end
# Simple location struct for compatibility with AST nodes
class Location < Struct[Integer]
attr_accessor start_line: Integer
attr_accessor end_line: Integer
def cover?: (Integer line) -> bool
end
# Class methods
def self.register_pattern: (Symbol name, start: Regexp, end_pattern: Regexp) -> Hash[Symbol, Regexp]
def self.start_pattern: (?Symbol pattern_type) -> Regexp
def self.end_pattern: (?Symbol pattern_type) -> Regexp
def self.freeze_start?: (String? line, ?Symbol pattern_type) -> bool
def self.freeze_end?: (String? line, ?Symbol pattern_type) -> bool
def self.pattern_types: () -> Array[Symbol]
# Instance attributes
attr_reader start_line: Integer
attr_reader end_line: Integer
attr_reader content: String?
attr_reader start_marker: String?
attr_reader end_marker: String?
attr_reader pattern_type: Symbol
def initialize: (
start_line: Integer,
end_line: Integer,
?start_marker: String?,
?end_marker: String?,
?pattern_type: Symbol
) -> void
def location: () -> Location
def slice: () -> String?
def freeze_node?: () -> bool
def signature: () -> Array[untyped]
def inspect: () -> String
def to_s: () -> String
private
def validate_line_order!: () -> void
end
# Debug logging module
module DebugLogger
def self.env_var_name: () -> String
def self.env_var_name=: (String name) -> String
def self.log_prefix: () -> String
def self.log_prefix=: (String prefix) -> String
def self.enabled?: () -> bool
def self.debug: (*untyped args) -> void
def self.info: (*untyped args) -> void
def self.warning: (*untyped args) -> void
def self.time: (String label) { () -> untyped } -> untyped
def self.log_node: (untyped node, ?label: String) -> void
def self.extract_lines: (untyped node) -> String
def self.safe_type_name: (untyped node) -> String
end
# Base module for file analysis classes
module FileAnalysisBase
# Required instance attributes (must be defined by including class)
attr_reader statements: Array[untyped]
attr_reader lines: Array[String]
attr_reader signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?
# Get all freeze blocks from statements
def freeze_blocks: () -> Array[FreezeNode]
# Check if a line is within a freeze block
def in_freeze_block?: (Integer line_num) -> bool
# Get the freeze block containing the given line
def freeze_block_at: (Integer line_num) -> FreezeNode?
# Get structural signature for a statement at given index
def signature_at: (Integer index) -> Array[untyped]?
# Get a specific line (1-indexed)
def line_at: (Integer line_num) -> String?
# Get a normalized line (whitespace-trimmed)
def normalized_line: (Integer line_num) -> String?
# Generate signature for a node
def generate_signature: (untyped node) -> Array[untyped]?
# Check if a value represents a fallthrough node
def fallthrough_node?: (untyped value) -> bool
# Compute default signature for a node (abstract - must be implemented)
def compute_node_signature: (untyped node) -> Array[untyped]?
end
# Base merge result tracking
class MergeResult
DECISION_KEPT_TEMPLATE: Symbol
DECISION_KEPT_DEST: Symbol
DECISION_MERGED: Symbol
DECISION_ADDED: Symbol
DECISION_FREEZE_BLOCK: Symbol
DECISION_REPLACED: Symbol
DECISION_APPENDED: Symbol
attr_reader decisions: Array[Hash[Symbol, untyped]]
def initialize: () -> void
def track_decision: (
untyped node,
Symbol decision,
?reason: String?
) -> void
end
# Configuration object for SmartMerger options
class MergerConfig
VALID_PREFERENCES: Array[Symbol]
attr_reader signature_match_preference: Symbol | Hash[Symbol, Symbol]
attr_reader node_splitter: Hash[Symbol, untyped]?
attr_reader add_template_only_nodes: add_template_only_nodes_type
attr_reader freeze_token: String?
attr_reader signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?
def initialize: (
?signature_match_preference: (Symbol | Hash[Symbol, Symbol]),
?add_template_only_nodes: add_template_only_nodes_type,
?freeze_token: String?,
?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
?node_splitter: Hash[Symbol, untyped]?
) -> void
def prefer_destination?: () -> bool
def prefer_template?: () -> bool
def to_h: (?default_freeze_token: String?) -> Hash[Symbol, untyped]
def with: (**untyped options) -> MergerConfig
def self.destination_wins: (?freeze_token: String?, ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?, ?node_splitter: Hash[Symbol, untyped]?) -> MergerConfig
def self.template_wins: (?freeze_token: String?, ?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?, ?node_splitter: Hash[Symbol, untyped]?) -> MergerConfig
private
def validate_preference!: (Symbol preference) -> void
end
# Type alias for node typing callables
type node_typing_callable = ^(untyped) -> untyped?
type node_typing_hash = Hash[Symbol | String, node_typing_callable]
type preference_type = Symbol | Hash[Symbol, Symbol]
# Type alias for add_template_only_nodes filter
# Can be: Boolean, or callable that receives (node, entry) and returns truthy/falsey
# Entry hash contains: { template_node:, signature:, template_index:, dest_index: nil }
type add_template_only_filter = ^(untyped node, Hash[Symbol, untyped] entry) -> boolish
type add_template_only_nodes_type = bool | add_template_only_filter
# Abstract base class for SmartMerger implementations
class SmartMergerBase
include Detector::Mergeable
attr_reader template_content: String
attr_reader dest_content: String
attr_reader template_analysis: untyped
attr_reader dest_analysis: untyped
attr_reader resolver: untyped
attr_reader result: untyped
attr_reader preference: preference_type
attr_reader add_template_only_nodes: add_template_only_nodes_type
attr_reader freeze_token: String
attr_reader signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?
attr_reader match_refiner: untyped?
attr_reader node_typing: node_typing_hash?
def initialize: (
String template_content,
String dest_content,
?signature_generator: (^(untyped) -> (Array[untyped] | untyped | nil))?,
?preference: preference_type,
?add_template_only_nodes: add_template_only_nodes_type,
?freeze_token: String?,
?match_refiner: untyped?,
?regions: Array[Hash[Symbol, untyped]]?,
?region_placeholder: String?,
?node_typing: node_typing_hash?,
**untyped format_options
) -> void
def merge: () -> String
def merge_result: () -> untyped
# Class method for convenient merging
def self.merge: (
String template_content,
String dest_content,
**untyped options
) -> String
private
# Abstract methods that subclasses must implement
def analysis_class: () -> Class
def perform_merge: () -> untyped
# Optional hooks for subclasses
def default_freeze_token: () -> String
def resolver_class: () -> Class?
def result_class: () -> Class?
def aligner_class: () -> Class?
def build_analysis_options: () -> Hash[Symbol, untyped]
def build_resolver_options: () -> Hash[Symbol, untyped]
def build_full_analysis_options: (Symbol source) -> Hash[Symbol, untyped]
def update_result_content: (untyped result, String content) -> void
def template_parse_error_class: () -> Class
def destination_parse_error_class: () -> Class
end
# Abstract base class for ConflictResolver implementations
class ConflictResolverBase
# Decision constants
DECISION_KEPT_TEMPLATE: Symbol
DECISION_KEPT_DEST: Symbol
DECISION_MERGED: Symbol
DECISION_ADDED: Symbol
DECISION_FREEZE_BLOCK: Symbol
DECISION_APPENDED: Symbol
DECISION_REPLACED: Symbol
attr_reader strategy: Symbol
attr_reader preference: preference_type
attr_reader template_analysis: untyped
attr_reader dest_analysis: untyped
attr_reader add_template_only_nodes: bool
attr_reader match_refiner: untyped?
def initialize: (
strategy: Symbol,
preference: preference_type,
template_analysis: untyped,
dest_analysis: untyped,
?add_template_only_nodes: bool,
?match_refiner: untyped?,
**untyped options
) -> void
def resolve: (*untyped args, **untyped kwargs) -> untyped
# Get preference for a specific node (supports Hash preference)
def preference_for: (untyped node) -> Symbol
private
def validate_preference!: (preference_type preference) -> void
def resolve_node_pair: (untyped template_node, untyped dest_node, **untyped kwargs) -> untyped
def resolve_batch: (*untyped args) -> untyped
def resolve_boundary: (*untyped args) -> untyped
end
# Abstract base class for MergeResult implementations
class MergeResultBase
# Decision constants
DECISION_KEPT_TEMPLATE: Symbol
DECISION_KEPT_DEST: Symbol
DECISION_MERGED: Symbol
DECISION_ADDED: Symbol
DECISION_FREEZE_BLOCK: Symbol
DECISION_APPENDED: Symbol
DECISION_REPLACED: Symbol
attr_reader template_analysis: untyped?
attr_reader dest_analysis: untyped?
attr_reader lines: Array[String]
attr_reader decisions: Array[Hash[Symbol, untyped]]
attr_reader conflicts: Array[Hash[Symbol, untyped]]
attr_reader frozen_blocks: Array[untyped]
attr_reader stats: Hash[Symbol, untyped]
def initialize: (
?template_analysis: untyped?,
?dest_analysis: untyped?,
?conflicts: Array[Hash[Symbol, untyped]],
?frozen_blocks: Array[untyped],
?stats: Hash[Symbol, untyped],
**untyped options
) -> void
def content: () -> Array[String]
def content?: () -> bool
def content_string: () -> String
def to_s: () -> String
def success?: () -> bool
def conflicts?: () -> bool
def track_decision: (Symbol decision, Symbol source, **untyped metadata) -> void
end
# Abstract base class for MatchRefiner implementations
class MatchRefinerBase
# Default similarity threshold
DEFAULT_THRESHOLD: Float
attr_reader threshold: Float
attr_reader node_types: Array[Symbol]?
def initialize: (
?threshold: Float,
?node_types: Array[Symbol]?,
**untyped options
) -> void
# Find matches between unmatched nodes
def call: (
Array[untyped] template_nodes,
Array[untyped] dest_nodes,
?Hash[Symbol, untyped] context
) -> Array[MatchResult]
# Compute similarity score between two nodes
def similarity: (untyped template_node, untyped dest_node) -> Float
# Check if a node matches the configured types
def matches_type?: (untyped node) -> bool
private
# Levenshtein distance for string similarity
def levenshtein_distance: (String s1, String s2) -> Integer
def string_similarity: (String s1, String s2) -> Float
end
# Result of a match refinement operation
class MatchResult
attr_reader template_node: untyped
attr_reader dest_node: untyped
attr_reader score: Float
attr_reader metadata: Hash[Symbol, untyped]
def initialize: (
template_node: untyped,
dest_node: untyped,
score: Float,
?metadata: Hash[Symbol, untyped]
) -> void
def to_h: () -> Hash[Symbol, untyped]
end
# Detector namespace for region detection and merging
module Detector
# Represents a detected region within a document
class Region < Struct[untyped]
attr_accessor type: Symbol
attr_accessor content: String
attr_accessor start_line: Integer
attr_accessor end_line: Integer
attr_accessor delimiters: Array[String]?
attr_accessor metadata: Hash[Symbol, untyped]?
def line_range: () -> Range[Integer]
def line_count: () -> Integer
def full_text: () -> String
def contains_line?: (Integer line) -> bool
def overlaps?: (Region other) -> bool
def to_s: () -> String
def inspect: () -> String
end
# Abstract base class for region detectors
class Base
def region_type: () -> Symbol
def detect_all: (String source) -> Array[Region]
def strip_delimiters?: () -> bool
def name: () -> String
def inspect: () -> String
private
def build_region: (
type: Symbol,
content: String,
start_line: Integer,
end_line: Integer,
?delimiters: Array[String]?,
?metadata: Hash[Symbol, untyped]?
) -> Region
end
# Detects fenced code blocks
class FencedCodeBlock < Base
attr_reader language: String
attr_reader aliases: Array[String]
def initialize: (String language, ?aliases: Array[String]) -> void
def self.ruby: () -> FencedCodeBlock
def self.yaml: () -> FencedCodeBlock
def self.json: () -> FencedCodeBlock
def self.bash: () -> FencedCodeBlock
end
# Detects YAML frontmatter
class YamlFrontmatter < Base
FRONTMATTER_PATTERN: Regexp
end
# Detects TOML frontmatter
class TomlFrontmatter < Base
FRONTMATTER_PATTERN: Regexp
end
# Mixin for region-aware merging
module Mergeable
DEFAULT_PLACEHOLDER_PREFIX: String
DEFAULT_PLACEHOLDER_SUFFIX: String
# Configuration for a region type
class Config < Struct[untyped]
attr_accessor detector: Base
attr_accessor merger_class: Class?
attr_accessor merger_options: Hash[Symbol, untyped]
attr_accessor regions: Array[Hash[Symbol, untyped]]
end
# Extracted region with placeholder
class ExtractedRegion < Struct[untyped]
attr_accessor region: Region
attr_accessor config: Config
attr_accessor placeholder: String
attr_accessor merged_content: String?
end
def regions_configured?: () -> bool
def setup_regions: (regions: Array[Hash[Symbol, untyped]], ?region_placeholder: String?) -> void
def extract_template_regions: (String content) -> String
def extract_dest_regions: (String content) -> String
def substitute_merged_regions: (String content) -> String
end
end
# Recipe namespace for YAML-based merge recipes
module Recipe
# Recipe configuration loaded from YAML
class Config
attr_reader name: String
attr_reader description: String?
attr_reader template_path: String
attr_reader targets: Array[String]
attr_reader injection: Hash[String, untyped]
attr_reader merge_options: Hash[String, untyped]
attr_reader when_missing: Symbol
attr_reader recipe_path: String?
def self.load: (String path) -> Config
def initialize: (Hash[String, untyped] config, ?recipe_path: String?) -> void
def anchor_config: () -> Hash[String, untyped]
def boundary_config: () -> Hash[String, untyped]?
def position: () -> Symbol
def preference: () -> Symbol
def add_missing?: () -> bool
def replace_mode?: () -> bool
end
# Executes recipes against target files
class Runner
# Result of processing a single file
class Result < Struct[untyped]
attr_accessor path: String
attr_accessor relative_path: String
attr_accessor status: Symbol
attr_accessor changed: bool
attr_accessor has_anchor: bool
attr_accessor message: String?
attr_accessor stats: Hash[Symbol, untyped]?
attr_accessor error: String?
end
attr_reader recipe: Config
attr_reader dry_run: bool
attr_reader verbose: bool
attr_reader parser: Symbol
attr_reader base_dir: String
attr_reader results: Array[Result]
def initialize: (
Config recipe,
?dry_run: bool,
?verbose: bool,
?parser: Symbol,
?base_dir: String
) -> void
def run: () ?{ (Result) -> void } -> Array[Result]
def summary: () -> Hash[Symbol, Integer]
def summary_table: () -> Array[Hash[Symbol, untyped]]
end
# Loads Ruby scripts referenced by recipes
class ScriptLoader
attr_reader base_dir: String?
attr_reader cache: Hash[String, untyped]
def initialize: (?recipe_path: String?, ?base_dir: String?) -> void
def load_callable: (String script_ref) -> (^(*untyped) -> untyped)?
def resolve_path: (String script_ref) -> String?
private
def load_from_file: (String path) -> untyped
def parse_inline_lambda: (String code) -> (^(*untyped) -> untyped)?
end
end
# Module for node typing support
module NodeTyping
# Wrapper class for typed nodes
class Wrapper
attr_reader node: untyped
attr_reader merge_type: Symbol
def initialize: (untyped node, Symbol merge_type) -> void
def method_missing: (Symbol method, *untyped args) ?{ (*untyped) -> untyped } -> untyped
def respond_to_missing?: (Symbol method, ?bool include_private) -> bool
def typed_node?: () -> bool
def unwrap: () -> untyped
def ==: (untyped other) -> bool
def hash: () -> Integer
def eql?: (untyped other) -> bool
def inspect: () -> String
end
# Wrapper for frozen AST nodes that includes Freezable behavior
class FrozenWrapper < Wrapper
include Freezable
def initialize: (untyped node, ?Symbol merge_type) -> void
def frozen_node?: () -> bool
def slice: () -> String
def signature: () -> Array[untyped]
def inspect: () -> String
end
# Thread-safe backend registration and type normalization module.
# Extended by format-specific normalizers (e.g., Toml::Merge::NodeTypeNormalizer).
module Normalizer
@normalizer_mutex: Mutex
@backend_mappings: Hash[Symbol, Hash[Symbol, Symbol]]
# Called when this module is extended into another module
def self.extended: (Module base) -> void
# Configure initial backend mappings
# @param mappings Keyword args where keys are backend symbols and values are type mapping hashes
def configure_normalizer: (**untyped) -> void
# Register type mappings for a new backend (thread-safe)
def register_backend: (Symbol backend, Hash[Symbol, Symbol] mappings) -> void
# Get the canonical type for a backend-specific type
def canonical_type: (Symbol | String | nil backend_type, ?Symbol? backend) -> Symbol?
# Wrap a node with its canonical type as merge_type
def wrap: (untyped node, Symbol backend) -> Wrapper
# Get all registered backends
def registered_backends: () -> Array[Symbol]
# Check if a backend is registered
def backend_registered?: (Symbol | String backend) -> bool
# Get the mappings for a specific backend
def mappings_for: (Symbol backend) -> Hash[Symbol, Symbol]?
# Get all canonical types across all backends
def canonical_types: () -> Array[Symbol]
end
def self.with_merge_type: (untyped node, Symbol merge_type) -> Wrapper
def self.frozen: (untyped node, ?Symbol merge_type) -> FrozenWrapper
def self.frozen_node?: (untyped node) -> bool
def self.typed_node?: (untyped node) -> bool
def self.merge_type_for: (untyped node) -> Symbol?
def self.unwrap: (untyped node) -> untyped
def self.process: (untyped node, node_typing_hash? typing_config) -> untyped?
def self.validate!: (node_typing_hash? typing_config) -> void
end end end