toml-merge v2.0.0 released!
2.0.0 - 2026-01-09
- TAG: v2.0.0
- COVERAGE: 88.38% – 563/637 lines in 11 files
- BRANCH COVERAGE: 64.13% – 177/276 branches in 11 files
- 97.03% documented
Added
- FFI backend isolation for test suite
- Added
bin/rspec-ffiscript to run FFI specs in isolation (before MRI backend loads) - Added
spec/spec_ffi_helper.rbfor FFI-specific test configuration - Updated Rakefile with
ffi_specsandremaining_specstasks - The
:testtask now runs FFI specs first, then remaining specs
- Added
- Emitter autoload - Added
Emitterto module autoload list- Previously missing, causing
NameError: uninitialized constant Emitterin ConflictResolver - Now properly autoloaded via
autoload :Emitter, "toml/merge/emitter"
- Previously missing, causing
Backendsmodule with constants for backend selectionBackends::TREE_SITTER(:tree_sitter_toml) - Native tree-sitter parserBackends::CITRUS(:citrus_toml) - Pure Ruby toml-rb parserBackends::AUTO(:auto) - Auto-detect available backendBackends.validate!andBackends.valid?for validation
SmartMergernow acceptsbackend:parameter for explicit backend selection- Follows same pattern as markdown-merge
- Auto-detects backend by default, or use
backend: Backends::CITRUSto force pure Ruby
FileAnalysisnow acceptsbackend:parameter and exposes resolved backend via#backendattrNodeWrappernow acceptsbackend:anddocument_root:parameters for correct normalization- Structural normalization for Citrus backend: Tree-sitter and Citrus backends produce different AST structures for tables:
- Tree-sitter: Table nodes contain pairs as children
- Citrus: Table nodes only contain header; pairs are siblings at document level
NodeWrapper#pairsnow finds associated pairs regardless of AST structureNodeWrapper#contentnow returns full table content on both backendsNodeWrapper#effective_end_linecalculates correct end line including pairsFileAnalysispassesdocument_rootto all NodeWrappers for sibling lookups- This enables the merge logic to work identically across backends
NodeTypeNormalizermodule for backend-agnostic node type handling- Maps backend-specific types (e.g.,
table_array_element) to canonical types (e.g.,array_of_tables) - Supports both
tree_sitter_tomlandcitrus_tomlbackends with comprehensive type mappings - Provides helper methods:
table_type?,value_type?,key_type?,container_type? - Extensible via
register_backendfor custom TOML parsers - Follows the same pattern as
markdown-merge’sNodeTypeNormalizer
- Maps backend-specific types (e.g.,
NodeWrapper#canonical_typemethod returns the normalized type for a node- Comprehensive test suite for
NodeTypeNormalizerwith 26 new specs spec/support/dependency_tags.rbfor conditional test execution based on backend availability
Changed
- ast-merge v3.1.0
- tree_haver v4.0.3, adds error handling for FFI backend
- Test suite now explicitly tests all available backend modes - Tests previously ran with
whatever backend was auto-selected. Now specs explicitly test up to five backend configurations:
:autobackend - Tests default user experience (backend-agnostic):mribackend viaTreeHaver.with_backend(:mri)- Tests explicit tree-sitter MRI behavior:citrusbackend viaTreeHaver.with_backend(:citrus)- Tests explicit toml-rb behavior:rustbackend viaTreeHaver.with_backend(:rust)- Tests explicit tree-sitter Rust behavior:javabackend viaTreeHaver.with_backend(:java)- Tests explicit tree-sitter Java behavior
This ensures consistent behavior is verified across all backends, rather than relying on auto-selection which may vary by platform. Each shared example group is included in all contexts with appropriate dependency tags (
:toml_grammar,:toml_rb,:toml_parsing,:rust_backend,:java_backend). Tests for unavailable backends are automatically skipped.Note: The
:java_backendtag now correctly detects whether the Java backend can actually load grammars. Standard.sofiles built for MRI’s tree-sitter C bindings are NOT compatible with java-tree-sitter. Tests will be skipped on JRuby unless grammar JARs from Maven Central (built for java-tree-sitter’s Foreign Function Memory API) are available. - Backend handling simplified - Let TreeHaver handle all backend selection:
- Removed
backend:parameter fromSmartMergerandFileAnalysis - Removed
Backendsmodule entirely (was unused after removingbackend:parameter) - Users control backend via TreeHaver directly (
TREE_HAVER_BACKENDenv var,TreeHaver.backend=, orTreeHaver.with_backend) - This ensures compatibility with all TreeHaver backends (mri, rust, ffi, java, citrus)
- Removed
- Backend naming simplified to align with TreeHaver:
NodeTypeNormalizermappings now keyed by:tree_sitterand:citrus- All native TreeHaver backends (mri, rust, ffi, java) produce tree-sitter AST format
- See
.github/COPILOT_INSTRUCTIONS.mdfor comprehensive TreeHaver backend documentation - NodeWrapper: Now inherits from
Ast::Merge::NodeWrapperBase- Removes ~80 lines of duplicated code (initialization, line extraction, basic methods)
- Uses
process_additional_optionshook for TOML-specific options (backend,document_root) - Keeps TOML-specific type predicates using
NodeTypeNormalizer - Keeps Citrus structural normalization logic for
#pairs,#content,#effective_end_line - Adds
#node_wrapper?method for distinguishing fromNodeTyping::Wrapper
- citrus_toml mappings: Updated to match actual Citrus/toml-rb node types
table_array→:array_of_tables(Citrus produces:table_array, not:table_array_element)keyvalue→:pair(Citrus produces:keyvalue, not:pair)- Added all Citrus-specific integer types:
decimal_integer,hexadecimal_integer,octal_integer,binary_integer - Added all Citrus-specific string types:
basic_string,literal_string,multiline_string,multiline_literal - Added all Citrus-specific datetime types:
local_date,local_time,local_datetime,offset_datetime - Added Citrus-specific boolean types:
true,false - Added whitespace types:
space,line_break,indent,repeat
- FileAnalysis error handling: Now rescues
TreeHaver::Errorinstead ofTreeHaver::NotAvailableTreeHaver::Errorinherits fromException, notStandardErrorTreeHaver::NotAvailableis a subclass ofTreeHaver::Error, so it’s also caught- Fixes parse error handling on TruffleRuby where Citrus backend raises
TreeHaver::Error
- Dependency tags: Refactored to use shared
TreeHaver::RSpec::DependencyTagsfrom tree_haver gem- All dependency detection is now centralized in tree_haver
- Use
require "tree_haver/rspec"for shared RSpec configuration TomlMergeDependenciesis now an alias toTreeHaver::RSpec::DependencyTags- Enables
TOML_MERGE_DEBUG=1for dependency summary output
- FileAnalysis: Error handling now follows the standard pattern
- Parse errors are collected but not re-raised from FileAnalysis
valid?returns false when there are errors or no AST- SmartMergerBase handles raising the appropriate parse error
- Consistent with json-merge, jsonc-merge, and bash-merge implementations
- SmartMerger: Added
**optionsfor forward compatibility- Accepts additional options that may be added to base class in future
- Passes all options through to
SmartMergerBase node_typingparameter for per-node-type merge preferences- Enables
preference: { default: :destination, special_type: :template }pattern - Works with custom merge_types assigned via node_typing lambdas
- Enables
regionsandregion_placeholderparameters for nested content merging
- ConflictResolver: Added
**optionsfor forward compatibility- Now passes
match_refinerto base class instead of storing locally
- Now passes
- MergeResult: Added
**optionsfor forward compatibility - FileAnalysis: Simplified to use
TreeHaver.parser_forAPI- Removed 40+ lines of grammar loading boilerplate
- Now relies on tree_haver for auto-discovery and Citrus fallback
:tree_sitter_tomlRSpec tag for tree-sitter-toml grammar tests:toml_rbRSpec tag for toml-rb/Citrus backend tests:toml_backendRSpec tag for tests requiring any TOML backend
- BREAKING:
NodeWrappertype predicates now useNodeTypeNormalizerfor backend-agnostic type checkingarray_of_tables?now correctly identifies bothtable_array_element(tree-sitter) andarray_of_tablesnodes- All predicates (
table?,pair?,string?, etc.) use canonical types type?method checks both raw and canonical types
FileAnalysis#tablesnow usesNodeTypeNormalizer.table_type?for type detectionFileAnalysis#root_pairsand#integrate_nodesuse canonical type checksTableMatchRefiner#table_node?usesNodeTypeNormalizerfor backend-agnostic table detectioncompute_signaturemethod uses canonical types for consistent signatures across backends- Rewrote
node_wrapper_spec.rbwith proper tests (removed placeholder/pending tests) - Rewrote
table_match_refiner_spec.rbwith working tests using:toml_backendtag - Updated
spec_helper.rbload order to ensureTreeHaveris available for dependency detection - BREAKING: Migrate from direct
TreeSitter::Language.loadtoTreeHaverAPI- Changed
require "tree_sitter"torequire "tree_haver"in main module file - Added automatic grammar registration via
TreeHaver::GrammarFinder#register! FileAnalysis#find_parser_pathnow exclusively usesTreeHaver::GrammarFinderFileAnalysis#parse_tomlnow usesTreeHaver::ParserandTreeHaver::Language- Removed legacy fallback path search (TreeHaver is now a hard requirement)
- Updated documentation to reference
TreeHaver::Nodeinstead ofTreeSitter::Node - Environment variable
TREE_SITTER_TOML_PATHis still supported via TreeHaver - This enables support for multiple tree-sitter backends (MRI, Rust, FFI, Java) and Citrus fallback
- Changed
Removed
- Load-time grammar registration - TreeHaver’s
parser_fornow handles grammar discovery and registration automatically. Removed manualGrammarFindercalls and warnings fromlib/toml/merge.rb.
Fixed
- Citrus backend normalization improvements for TruffleRuby compatibility:
NodeWrapper#key_namenow strips whitespace from key text (Citrus includes trailing spaces)NodeWrapper#table_namenow strips whitespace from table header textNodeWrapper#extract_inline_table_keysnow recursively handles Citrus’s deeply nested structure (inline_table -> optional -> keyvalue -> keyvalue -> stripped_key -> key -> bare_key)NodeWrapper#elementsnow recursively handles Citrus’s array structure where elements are nested inarray_elements -> repeat -> indent -> decimal_integerchains- Both methods now correctly extract all values instead of just the first one
NodeWrapper#value_nodenow skips Citrus internal nodes (whitespace,unknown,space)- All
NodeTypeNormalizer.canonical_type()calls now pass@backendparameter for correct type mapping FileAnalysis#root_pairsnow correctly filters pairs to only include those BEFORE the first table (Citrus has flat AST structure where all pairs are document siblings)MergeResult#add_nodenow useseffective_end_lineto include table pairs on Citrus backendTableMatchRefiner#table_node?now uses node’s backend for correct type checking- Test helper
parse_tomlnow usesFileAnalysisfor proper backend detection
NodeTypeNormalizer.canonical_typenow defaults to:tree_sitter_tomlbackend when no backend is specified- Added
DEFAULT_BACKENDconstant and overrodecanonical_typeandwrapmethods - Fixes issue where calling
canonical_type(:table_array_element)without a backend argument would passthrough instead of mapping to:array_of_tables - Value type predicates (
string?,integer?,float?,boolean?,array?,inline_table?,datetime?) now work correctly
- Added
- Consolidated duplicate
describeblocks in spec files (file_analysis_spec.rb,merge_result_spec.rb,node_wrapper_spec.rb) - Fixed lint violations: added missing expectations to tests, used safe navigation where appropriate
- No longer warns about missing TOML grammar when the grammar file exists but tree-sitter runtime is unavailable
- This is expected behavior when using non-tree-sitter backends (Citrus, Prism, etc.)
- Warning now only appears when the grammar file is actually missing
Many paths lead to being a sponsor or a backer of this project. Are you on such a path?