tree_haver 5.0.5 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +299 -660
  5. data/lib/tree_haver/backend_api.rb +78 -35
  6. data/lib/tree_haver/backend_context.rb +28 -0
  7. data/lib/tree_haver/backend_registry.rb +119 -382
  8. data/lib/tree_haver/backends/citrus.rb +55 -53
  9. data/lib/tree_haver/backends/ffi.rb +105 -101
  10. data/lib/tree_haver/backends/java.rb +92 -76
  11. data/lib/tree_haver/backends/mri.rb +44 -39
  12. data/lib/tree_haver/backends/parslet.rb +53 -48
  13. data/lib/tree_haver/backends/prism.rb +140 -43
  14. data/lib/tree_haver/backends/psych.rb +30 -26
  15. data/lib/tree_haver/backends/rust.rb +21 -17
  16. data/lib/tree_haver/backends/tslp.rb +274 -0
  17. data/lib/tree_haver/base/comment.rb +320 -0
  18. data/lib/tree_haver/base/language.rb +2 -2
  19. data/lib/tree_haver/base/node.rb +39 -31
  20. data/lib/tree_haver/base/parser.rb +4 -0
  21. data/lib/tree_haver/base/point.rb +3 -3
  22. data/lib/tree_haver/base/tree.rb +1 -1
  23. data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
  24. data/lib/tree_haver/contracts.rb +1025 -0
  25. data/lib/tree_haver/grammar_finder.rb +125 -70
  26. data/lib/tree_haver/kaitai_backend.rb +30 -0
  27. data/lib/tree_haver/language.rb +36 -37
  28. data/lib/tree_haver/language_pack.rb +41 -0
  29. data/lib/tree_haver/language_registry.rb +34 -3
  30. data/lib/tree_haver/library_path_utils.rb +7 -7
  31. data/lib/tree_haver/node.rb +40 -31
  32. data/lib/tree_haver/parser.rb +44 -37
  33. data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
  34. data/lib/tree_haver/path_validator.rb +39 -36
  35. data/lib/tree_haver/peg_backends.rb +76 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
  37. data/lib/tree_haver/rspec.rb +1 -31
  38. data/lib/tree_haver/tree.rb +16 -7
  39. data/lib/tree_haver/version.rb +5 -14
  40. data/lib/tree_haver.rb +536 -1240
  41. data/sig/tree_haver.rbs +1 -229
  42. data.tar.gz.sig +0 -0
  43. metadata +154 -70
  44. metadata.gz.sig +0 -0
  45. data/CHANGELOG.md +0 -1393
  46. data/CITATION.cff +0 -20
  47. data/CODE_OF_CONDUCT.md +0 -134
  48. data/CONTRIBUTING.md +0 -359
  49. data/FUNDING.md +0 -74
  50. data/LICENSE.txt +0 -21
  51. data/REEK +0 -0
  52. data/RUBOCOP.md +0 -71
  53. data/SECURITY.md +0 -21
  54. data/lib/tree_haver/base.rb +0 -12
  55. data/lib/tree_haver/compat.rb +0 -43
  56. data/lib/tree_haver/rspec/testable_node.rb +0 -217
  57. data/sig/tree_haver/backends.rbs +0 -352
  58. data/sig/tree_haver/grammar_finder.rbs +0 -29
  59. data/sig/tree_haver/path_validator.rbs +0 -32
data/README.md CHANGED
@@ -1,32 +1,25 @@
1
- [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![kettle-rb Logo by Aboling0, CC BY-SA 4.0][🖼️kettle-rb-i]][🖼️kettle-rb]
2
-
3
- [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
4
- [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
5
- [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
6
- [🖼️ruby-lang]: https://www.ruby-lang.org/
7
- [🖼️kettle-rb-i]: https://logos.galtzo.com/assets/images/kettle-rb/avatar-192px.svg
8
- [🖼️kettle-rb]: https://github.com/kettle-rb
1
+ <a href="https://github.com/structuredmerge"><img alt="structuredmerge Logo by GitHub" src="https://github.com/structuredmerge.png?size=192" width="12%" align="right"/></a> <a href="https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/tree_haver"><img alt="tree_haver Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/structuredmerge/structuredmerge-ruby/tree_haver/avatar-128px.svg" width="12%" align="right"/></a>
9
2
 
10
3
  # 🌴 TreeHaver
11
4
 
12
- [![Version][👽versioni]][👽dl-rank] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
5
+ [![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Total downloads][👽dl-ranki]][👽dl-rank] [![CI Current][🚎11-c-wfi]][🚎11-c-wf]
13
6
 
14
- `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
7
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know on Discord][✉️discord-invite] or [RubyForum][✉️ruby-forum], as I may have missed the notification.
15
8
 
16
- -----
9
+ ---
17
10
 
18
11
  `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
19
12
 
20
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
13
+ [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]
21
14
 
22
- <details>
23
- <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
15
+ <details markdown="1">
16
+ <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
24
17
 
25
18
  I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
26
19
 
27
20
  </details>
28
21
 
29
- ## 🌻 Synopsis
22
+ ## 🌻 Synopsis <a href="https://discord.gg/3qme4XHNKN"><img alt="Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px.svg" width="8%" align="right"/></a> <a href="https://ruby-toolbox.com"><img alt="ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5" src="https://logos.galtzo.com/assets/images/ruby-lang/avatar-128px.svg" width="8%" align="right"/></a>
30
23
 
31
24
  TreeHaver is a cross-Ruby adapter for the [tree-sitter](https://tree-sitter.github.io/tree-sitter/), [Citrus][citrus], and [Parslet][parslet] parsing libraries and other dedicated parsing tools that works seamlessly across MRI Ruby, JRuby, and TruffleRuby. It provides a unified API for parsing source code using grammars, regardless of your Ruby implementation.
32
25
 
@@ -39,13 +32,13 @@ If you've used [Faraday](https://github.com/lostisland/faraday), [multi\_json](h
39
32
  | **Faraday** | HTTP requests | Net::HTTP, Typhoeus, Patron, Excon |
40
33
  | **multi\_json** | JSON parsing | Oj, Yajl, JSON gem |
41
34
  | **multi\_xml** | XML parsing | Nokogiri, LibXML, Ox |
42
- | **TreeHaver** | Code parsing | MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet |
35
+ | **TreeHaver** | Code parsing | TSLP, MRI, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, RBS, Citrus, Parslet, Kaitai |
43
36
 
44
37
  **Learn once, write anywhere.**
45
38
 
46
39
  **Write once, run anywhere.**
47
40
 
48
- Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets you swap tree-sitter backends. Your parsing code remains the same whether you're running on MRI with native C extensions, JRuby with FFI, or TruffleRuby.
41
+ Just as Faraday lets you swap HTTP adapters without changing your code, TreeHaver lets merge providers report and select parsing backends through one registry. The high-level StructuredMerge providers default to the tree-sitter language-pack path when it supports the requested language, while the lower-level `TreeHaver::Parser` facade can still use native tree-sitter adapters and Ruby parser backends directly.
49
42
 
50
43
  ```ruby
51
44
  # Your code stays the same regardless of backend
@@ -53,20 +46,20 @@ parser = TreeHaver::Parser.new
53
46
  parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
54
47
  tree = parser.parse(source_code)
55
48
 
56
- # TreeHaver automatically picks the best available backend:
57
- # - MRI: ruby_tree_sitter, tree_stump, ffi, prism, psych, commonmarker, markly, citrus, parslet
58
- # - JRuby: ffi, java-tree-sitter (not a gem, but the jtreesitter maven package), prism, psych, commonmarker, markly, citrus, parslet
59
- # - TruffleRuby: prism, psych, commonmarker, markly, citrus, parslet
60
- # (tree-sitter backends don't work on Truffleruby with ffi gem due to FFI STRUCT_BY_VALUE limitation)
49
+ # TreeHaver::Parser automatically picks the best available parser facade backend:
50
+ # - MRI: ruby_tree_sitter, tree_stump, ffi, prism, psych, citrus, parslet
51
+ # - JRuby: java-tree-sitter / jtreesitter, ffi, prism, psych, citrus, parslet
52
+ # - TruffleRuby: prism, psych, citrus, parslet
53
+ # (the FFI tree-sitter adapter is not used on TruffleRuby because of struct-by-value limits)
61
54
  ```
62
55
 
63
56
  ### Key Features
64
57
 
65
58
  - **Universal Ruby Support**: Works on MRI Ruby, JRuby, and TruffleRuby
66
- - **10 Parsing Backends** - Choose the right backend for your needs:
59
+ - **Backend Registry** - Choose the right backend for your needs:
67
60
  - **Tree-sitter Backends** (high-performance, incremental parsing):
61
+ - **Tree-sitter Language Pack (TSLP)**: Default provider path for StructuredMerge language-family gems that use `tree-sitter-language-pack`
68
62
  - **MRI Backend**: Leverages [`ruby_tree_sitter`][ruby_tree_sitter] gem (C extension, fastest on MRI)
69
- - **Note**: `ruby_tree_sitter` currently requires unreleased fixes in the `pboling` fork, `tree_haver` branch.
70
63
  - **Rust Backend**: Uses [`tree_stump`][tree_stump] gem (Rust with precompiled binaries)
71
64
  - **Note**: Use `tree_stump` v0.2.0 or newer (fixes are released).
72
65
  - **FFI Backend**: Pure Ruby FFI bindings to `libtree-sitter` (JRuby only; TruffleRuby's FFI doesn't support tree-sitter's struct-by-value returns)
@@ -76,12 +69,15 @@ tree = parser.parse(source_code)
76
69
  - **Psych Backend**: Ruby's YAML parser ([Psych][psych], stdlib)
77
70
  - **Commonmarker Backend**: Fast Markdown parser ([Commonmarker][commonmarker], comrak Rust)
78
71
  - **Markly Backend**: GitHub Flavored Markdown ([Markly][markly], cmark-gfm C)
79
- - **Pure Ruby Fallback**:
72
+ - **RBS Backend**: Official RBS parser integration registered by `rbs-merge`
73
+ - **Pure Ruby Provider Backends**:
80
74
  - **Citrus Backend**: Pure Ruby PEG parsing via [`citrus`][citrus] (no native dependencies)
81
75
  - **Parslet Backend**: Pure Ruby PEG parsing via [`parslet`][parslet] (no native dependencies)
76
+ - **Binary Schema Support**:
77
+ - **Kaitai Struct Backend**: Backend reference and capability profile for binary schema analysis
82
78
  - **Automatic Backend Selection**: Intelligently selects the best backend for your Ruby implementation
83
79
  - **Language Agnostic**: Parse any language - Ruby, Markdown, YAML, JSON, Bash, TOML, JavaScript, etc.
84
- - **Grammar Discovery**: Built-in `GrammarFinder` utility for platform-aware grammar library discovery
80
+ - **Grammar Discovery**: Built-in `GrammarFinder` utility for registration-first tree-sitter grammar resolution
85
81
  - **Unified Position API**: Consistent `start_line`, `end_line`, `source_position` across all backends
86
82
  - **Thread-Safe**: Built-in language registry with thread-safe caching
87
83
  - **Minimal API Surface**: Simple, focused API that covers the most common use cases
@@ -94,9 +90,9 @@ TreeHaver has minimal dependencies and automatically selects the best backend fo
94
90
 
95
91
  **Requires `ruby_tree_sitter` v2.0+**
96
92
 
97
- In ruby\_tree\_sitter v2.0, all TreeSitter exceptions were changed to inherit from `Exception` (not `StandardError`). This was an intentional breaking change made for thread-safety and signal handling reasons.
93
+ TreeHaver normalizes backend failures behind `TreeHaver::Error` subclasses, which inherit from `StandardError`.
98
94
 
99
- **Exception Mapping**: TreeHaver catches `TreeSitter::TreeSitterError` and its subclasses, converting them to `TreeHaver::NotAvailable` while preserving the original error message. This provides a consistent exception API across all backends:
95
+ **Exception Mapping**: TreeHaver catches `TreeSitter::TreeSitterError` and its subclasses where the MRI adapter exposes them, converting them to `TreeHaver::NotAvailable` while preserving the original error message. This provides a consistent exception API across backends:
100
96
 
101
97
  | ruby\_tree\_sitter Exception | TreeHaver Exception | When It Occurs |
102
98
  |-----------------------------------|---------------------------|----------------------------------------------|
@@ -107,11 +103,8 @@ In ruby\_tree\_sitter v2.0, all TreeSitter exceptions were changed to inherit fr
107
103
  | `TreeSitter::QueryCreationError` | `TreeHaver::NotAvailable` | Query creation fails |
108
104
 
109
105
  ```ruby
110
- # MRI tree-sitter Backend
111
- gem "ruby_tree_sitter",
112
- github: "pboling/ruby-tree-sitter",
113
- branch: "tree_haver",
114
- require: false # DO NOT LOAD, because conflicts with FFI
106
+ # MRI tree-sitter backend
107
+ gem "ruby_tree_sitter", "~> 2.0", require: false
115
108
  ```
116
109
 
117
110
  #### Rust Backend (tree\_stump)
@@ -292,153 +285,6 @@ tree-sitter is a powerful parser generator that creates incremental parsers for
292
285
  - Managing different backends for different Ruby implementations is cumbersome
293
286
  TreeHaver solves these problems by providing a unified API that automatically selects the appropriate backend for your Ruby implementation, allowing you to write code once and run it anywhere.
294
287
 
295
- ### The `*-merge` Gem Family
296
-
297
- The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
298
-
299
- | Gem | Version / CI | Language<br>/ Format | Parser Backend(s) | Description |
300
- |------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------:|----------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
301
- | [tree_haver][tree_haver] | [![Version][tree_haver-gem-i]][tree_haver-gem] <br/> [![CI][tree_haver-ci-i]][tree_haver-ci] | Multi | Supported Backends: MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus, Parslet | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
302
- | [ast-merge][ast-merge] | [![Version][ast-merge-gem-i]][ast-merge-gem] <br/> [![CI][ast-merge-ci-i]][ast-merge-ci] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
303
- | [bash-merge][bash-merge] | [![Version][bash-merge-gem-i]][bash-merge-gem] <br/> [![CI][bash-merge-ci-i]][bash-merge-ci] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
304
- | [commonmarker-merge][commonmarker-merge] | [![Version][commonmarker-merge-gem-i]][commonmarker-merge-gem] <br/> [![CI][commonmarker-merge-ci-i]][commonmarker-merge-ci] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
305
- | [dotenv-merge][dotenv-merge] | [![Version][dotenv-merge-gem-i]][dotenv-merge-gem] <br/> [![CI][dotenv-merge-ci-i]][dotenv-merge-ci] | Dotenv | internal | Smart merge for `.env` files |
306
- | [json-merge][json-merge] | [![Version][json-merge-gem-i]][json-merge-gem] <br/> [![CI][json-merge-ci-i]][json-merge-ci] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
307
- | [jsonc-merge][jsonc-merge] | [![Version][jsonc-merge-gem-i]][jsonc-merge-gem] <br/> [![CI][jsonc-merge-ci-i]][jsonc-merge-ci] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
308
- | [markdown-merge][markdown-merge] | [![Version][markdown-merge-gem-i]][markdown-merge-gem] <br/> [![CI][markdown-merge-ci-i]][markdown-merge-ci] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver), [Parslet][parslet] | **Foundation**: Shared base for Markdown mergers with inner code block merging |
309
- | [markly-merge][markly-merge] | [![Version][markly-merge-gem-i]][markly-merge-gem] <br/> [![CI][markly-merge-ci-i]][markly-merge-ci] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
310
- | [prism-merge][prism-merge] | [![Version][prism-merge-gem-i]][prism-merge-gem] <br/> [![CI][prism-merge-ci-i]][prism-merge-ci] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
311
- | [psych-merge][psych-merge] | [![Version][psych-merge-gem-i]][psych-merge-gem] <br/> [![CI][psych-merge-ci-i]][psych-merge-ci] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
312
- | [rbs-merge][rbs-merge] | [![Version][rbs-merge-gem-i]][rbs-merge-gem] <br/> [![CI][rbs-merge-ci-i]][rbs-merge-ci] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
313
- | [toml-merge][toml-merge] | [![Version][toml-merge-gem-i]][toml-merge-gem] <br/> [![CI][toml-merge-ci-i]][toml-merge-ci] | TOML | [Parslet + toml][toml], [Citrus + toml-rb][toml-rb], [tree-sitter-toml][ts-toml] (all via tree_haver) | Smart merge for TOML files |
314
-
315
- #### Backend Platform Compatibility
316
-
317
- tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
318
-
319
- | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
320
- |-------------------------------------------------|:---:|:-----:|:-----------:|----------------------------------------------------------------------------|
321
- | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
322
- | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
323
- | **FFI** ([ffi][ffi]) | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
324
- | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
325
- | **Prism** ([prism][prism]) | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
326
- | **Psych** ([psych][psych]) | ✅ | ✅ | ✅ | YAML parsing, stdlib |
327
- | **Citrus** ([citrus][citrus]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
328
- | **Parslet** ([parslet][parslet]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
329
- | **Commonmarker** ([commonmarker][commonmarker]) | ✅ | ❌ | ❓ | Rust extension for Markdown (via [commonmarker-merge][commonmarker-merge]) |
330
- | **Markly** ([markly][markly]) | ✅ | ❌ | ❓ | C extension for Markdown (via [markly-merge][markly-merge]) |
331
-
332
- **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
333
-
334
- **Why some backends don't work on certain platforms**:
335
-
336
- - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
337
- - **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
338
- - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
339
-
340
- **Example implementations** for the gem templating use case:
341
-
342
- | Gem | Purpose | Description |
343
- |--------------------------|-----------------|-----------------------------------------------|
344
- | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
345
- | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
346
-
347
- [tree_haver]: https://github.com/kettle-rb/tree_haver
348
- [ast-merge]: https://github.com/kettle-rb/ast-merge
349
- [prism-merge]: https://github.com/kettle-rb/prism-merge
350
- [psych-merge]: https://github.com/kettle-rb/psych-merge
351
- [json-merge]: https://github.com/kettle-rb/json-merge
352
- [jsonc-merge]: https://github.com/kettle-rb/jsonc-merge
353
- [bash-merge]: https://github.com/kettle-rb/bash-merge
354
- [rbs-merge]: https://github.com/kettle-rb/rbs-merge
355
- [dotenv-merge]: https://github.com/kettle-rb/dotenv-merge
356
- [toml-merge]: https://github.com/kettle-rb/toml-merge
357
- [markdown-merge]: https://github.com/kettle-rb/markdown-merge
358
- [markly-merge]: https://github.com/kettle-rb/markly-merge
359
- [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
360
- [kettle-dev]: https://github.com/kettle-rb/kettle-dev
361
- [kettle-jem]: https://github.com/kettle-rb/kettle-jem
362
- [tree_haver-gem]: https://bestgems.org/gems/tree_haver
363
- [ast-merge-gem]: https://bestgems.org/gems/ast-merge
364
- [prism-merge-gem]: https://bestgems.org/gems/prism-merge
365
- [psych-merge-gem]: https://bestgems.org/gems/psych-merge
366
- [json-merge-gem]: https://bestgems.org/gems/json-merge
367
- [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
368
- [bash-merge-gem]: https://bestgems.org/gems/bash-merge
369
- [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
370
- [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
371
- [toml-merge-gem]: https://bestgems.org/gems/toml-merge
372
- [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
373
- [markly-merge-gem]: https://bestgems.org/gems/markly-merge
374
- [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
375
- [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
376
- [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
377
- [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
378
- [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
379
- [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
380
- [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
381
- [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
382
- [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
383
- [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
384
- [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
385
- [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
386
- [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
387
- [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
388
- [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
389
- [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
390
- [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
391
- [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
392
- [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
393
- [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
394
- [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
395
- [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
396
- [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
397
- [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
398
- [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
399
- [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
400
- [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
401
- [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
402
- [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
403
- [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
404
- [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
405
- [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
406
- [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
407
- [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
408
- [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
409
- [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
410
- [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
411
- [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
412
- [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
413
- [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
414
- [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
415
- [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
416
- [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
417
- [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
418
- [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
419
- [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
420
- [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
421
- [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
422
- [prism]: https://github.com/ruby/prism
423
- [psych]: https://github.com/ruby/psych
424
- [ffi]: https://github.com/ffi/ffi
425
- [ts-json]: https://github.com/tree-sitter/tree-sitter-json
426
- [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
427
- [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
428
- [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
429
- [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
430
- [dotenv]: https://github.com/bkeepers/dotenv
431
- [rbs]: https://github.com/ruby/rbs
432
- [toml-rb]: https://github.com/emancu/toml-rb
433
- [toml]: https://github.com/jm/toml
434
- [markly]: https://github.com/ioquatix/markly
435
- [commonmarker]: https://github.com/gjtorikian/commonmarker
436
- [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
437
- [tree_stump]: https://github.com/joker1007/tree_stump
438
- [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
439
- [citrus]: https://github.com/mjackson/citrus
440
- [parslet]: https://github.com/kschiess/parslet
441
-
442
288
  ### Comparison with Other Ruby AST / Parser Bindings
443
289
 
444
290
  | Feature | [tree\_haver][📜src-gh] (this gem) | [ruby\_tree\_sitter][ruby_tree_sitter] | [tree\_stump][tree_stump] | [citrus][citrus] | [parslet][parslet] |
@@ -495,69 +341,58 @@ tree_haver supports multiple parsing backends, but not all backends work on all
495
341
  - Performance is less critical than portability
496
342
  - You don't need TreeHaver's unified API
497
343
 
344
+ [citrus]: https://github.com/mjackson/citrus
345
+ [parslet]: https://github.com/kschiess/parslet
346
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
347
+ [tree_stump]: https://github.com/joker1007/tree_stump
348
+ [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
349
+ [prism]: https://github.com/ruby/prism
350
+ [psych]: https://github.com/ruby/psych
351
+ [commonmarker]: https://github.com/gjtorikian/commonmarker
352
+ [markly]: https://github.com/ioquatix/markly
353
+ [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
354
+ [ts-json]: https://github.com/tree-sitter/tree-sitter-json
355
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
356
+ [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
357
+
498
358
  ## 💡 Info you can shake a stick at
499
359
 
500
- | Tokens to Remember | [![Gem name][⛳️name-img]][👽dl-rank] [![Gem namespace][⛳️namespace-img]][📜src-gh] |
501
- |-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
502
- | Works with JRuby | [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
503
- | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎9-t-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎9-t-wf] [![Truffle Ruby 33.0 Compat][💎truby-c-i]][🚎11-c-wf] |
504
- | Works with MRI Ruby 4 | [![Ruby 4.0 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
505
- | Works with MRI Ruby 3 | [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎6-s-wf] |
506
- | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
507
- | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ\!](https://img.shields.io/badge/KLOC-2.484-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue)][🧮kloc] |
508
- | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
509
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img][📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
510
- | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
511
- | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
512
- | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
360
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
361
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
362
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
363
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Get help from RubyForum][✉️ruby-forum-img]][✉️ruby-forum] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
364
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on GitHub.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
365
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
366
+ | Compliance | [![License: AGPL-3.0-only OR PolyForm-Small-Business-1.0.0][📄license-img]][📄license] [![Apache license compatibility: Category X][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
367
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
368
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
369
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
513
370
 
514
371
  ### Compatibility
515
372
 
516
- Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
517
-
518
- | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
519
- |------------------------------------------------|--------------------------------------------------------|
520
- | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
521
-
522
- ### Federated DVCS
523
-
524
- <details markdown="1">
525
- <summary>Find this repo on federated forges (Coming soon!)</summary>
526
-
527
- | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
528
- |--------------------------------------------------|------------------------------------------------------------------------------------|----------------------------|---------------------------|----------------------------|---------------------------|--------------------------------|
529
- | 🧪 [kettle-rb/tree\_haver on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
530
- | 🧊 [kettle-rb/tree\_haver on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
531
- | 🐙 [kettle-rb/tree\_haver on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
532
- | 🎮️ [Discord Server][🖼️galtzo-discord] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord] | [Let's][🖼️galtzo-discord] | [talk][🖼️galtzo-discord] | [about][🖼️galtzo-discord] | [this][🖼️galtzo-discord] | [library\!][🖼️galtzo-discord] |
533
-
534
- </details>
535
-
536
- [gh-discussions]: https://github.com/kettle-rb/tree_haver/discussions
373
+ Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
374
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
375
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
376
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
377
+ not practical for the current toolchain.
537
378
 
538
- ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/tree_haver)][🏙️entsup-tidelift]
379
+ <a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
539
380
 
540
- Available as part of the Tidelift Subscription.
381
+ The _amazing_ test matrix is powered by the kettle-dev stack.
541
382
 
542
383
  <details markdown="1">
543
- <summary>Need enterprise-level guarantees?</summary>
544
-
545
- The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
546
-
547
- [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
548
-
549
- - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
550
-
551
- - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
552
-
553
- - 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
554
- Alternatively:
555
-
556
- - [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
557
-
558
- - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
559
-
560
- - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
384
+ <summary>How kettle-dev manages complexity in tests</summary>
385
+
386
+ | Gem | Source | Role | Total downloads |
387
+ |-----|--------|------|---------------------|
388
+ | [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [![Total downloads for appraisal2](https://img.shields.io/gem/dt/appraisal2.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/appraisal2) |
389
+ | [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [![Total downloads for appraisal2-rubocop](https://img.shields.io/gem/dt/appraisal2-rubocop.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
390
+ | [kettle-dev](https://clickgems.clickhouse.com/dashboard/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [![Total downloads for kettle-dev](https://img.shields.io/gem/dt/kettle-dev.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-dev) |
391
+ | [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [![Total downloads for kettle-jem](https://img.shields.io/gem/dt/kettle-jem.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
392
+ | [kettle-soup-cover](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [![Total downloads for kettle-soup-cover](https://img.shields.io/gem/dt/kettle-soup-cover.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
393
+ | [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [![Total downloads for kettle-test](https://img.shields.io/gem/dt/kettle-test.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/kettle-test) |
394
+ | [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [![Total downloads for rubocop-lts](https://img.shields.io/gem/dt/rubocop-lts.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
395
+ | [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [![Total downloads for turbo_tests2](https://img.shields.io/gem/dt/turbo_tests2.svg?style=flat-square)](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
561
396
 
562
397
  </details>
563
398
 
@@ -575,69 +410,50 @@ If bundler is not being used to manage dependencies, install the gem by executin
575
410
  gem install tree_haver
576
411
  ```
577
412
 
578
- ### 🔒 Secure Installation
579
-
580
- <details markdown="1">
581
- <summary>For Medium or High Security Installations</summary>
582
-
583
- This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
584
- [stone\_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
585
- by following the instructions below.
586
-
587
- Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
588
-
589
- ```console
590
- gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
591
- ```
592
-
593
- You only need to do that once. Then proceed to install with:
594
-
595
- ```console
596
- gem install tree_haver -P HighSecurity
597
- ```
598
-
599
- The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
600
-
601
- If you want to up your security game full-time:
602
-
603
- ```console
604
- bundle config set --global trust-policy MediumSecurity
605
- ```
606
-
607
- `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
608
-
609
- NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
610
-
611
- </details>
612
-
613
413
  ## ⚙️ Configuration
614
414
 
615
415
  ### Available Backends
616
416
 
617
- TreeHaver supports 10 parsing backends, each with different trade-offs. The `auto` backend automatically selects the best available option.
417
+ TreeHaver exposes a backend registry for parser facades, language-family providers, and binary schema support. The high-level StructuredMerge providers default to `tslp` / `kreuzberg-language-pack` when the language pack supports the requested format; the low-level `TreeHaver::Parser` facade uses `auto` to select among parser modules that expose the Parser/Language/Tree API.
618
418
 
619
419
  #### Tree-sitter Backends (Universal Parsing)
620
420
 
621
- | Backend | Description | Performance | Portability | Examples |
622
- |----------|---------------------------------------|-------------|-------------|---------------------------------------------------------------------------------------------------------------------------------|
623
- | **Auto** | Auto-selects best backend | Varies | Universal | [JSON](examples/auto_json.rb) · [JSONC](examples/auto_jsonc.rb) · [Bash](examples/auto_bash.rb) · [TOML](examples/auto_toml.rb) |
624
- | **MRI** | C extension via ruby\_tree\_sitter | Fastest | MRI only | [JSON](examples/mri_json.rb) · [JSONC](examples/mri_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/mri_toml.rb) |
625
- | **Rust** | Precompiled via tree\_stump | Very Fast | Good | [JSON](examples/rust_json.rb) · [JSONC](examples/rust_jsonc.rb) · \~\~Bash\~\~\* · [TOML](examples/rust_toml.rb) |
626
- | **FFI** | Dynamic linking via FFI | 🔵 Fast | Universal | [JSON](examples/ffi_json.rb) · [JSONC](examples/ffi_jsonc.rb) · [Bash](examples/ffi_bash.rb) · [TOML](examples/ffi_toml.rb) |
627
- | **Java** | JNI bindings (jtreesitter \>= 0.26.0) | Very Fast | JRuby only | [JSON](examples/java_json.rb) · [JSONC](examples/java_jsonc.rb) · [Bash](examples/java_bash.rb) · [TOML](examples/java_toml.rb) |
421
+ | Backend | Description | Performance | Portability |
422
+ |---------|-------------|-------------|-------------|
423
+ | **TSLP** | `tree-sitter-language-pack`; default StructuredMerge provider path | Fast | Universal where the gem supports the language |
424
+ | **Kreuzberg Language Pack** | Stable provider id alias used by StructuredMerge family gems | Fast | Universal where `tree-sitter-language-pack` is available |
425
+ | **Auto** | Auto-selects the best `TreeHaver::Parser` facade backend | Varies | Universal |
426
+ | **MRI** | C extension via ruby\_tree\_sitter | Fastest | MRI only |
427
+ | **Rust** | Precompiled via tree\_stump | Very fast | MRI only |
428
+ | **FFI** | Dynamic linking via FFI | Fast | MRI/JRuby where `libtree-sitter` is available |
429
+ | **Java** | JNI bindings (jtreesitter \>= 0.26.0) | Very fast | JRuby only |
628
430
 
629
431
  #### Language-Specific Backends (Native Parser Integration)
630
432
 
631
- | Backend | Description | Performance | Portability | Examples |
632
- |------------------|-----------------------------|-------------|-------------|--------------------------------------------------------------------------------------------------------------|
633
- | **Prism** | Ruby's official parser | Very Fast | Universal | [Ruby](examples/prism_ruby.rb) |
634
- | **Psych** | Ruby's YAML parser (stdlib) | Very Fast | Universal | [YAML](examples/psych_yaml.rb) |
635
- | **Commonmarker** | Markdown via comrak (Rust) | Very Fast | Good | [Markdown](examples/commonmarker_markdown.rb) · [commonmarker-merge](examples/commonmarker_merge_example.rb) |
636
- | **Markly** | GFM via cmark-gfm (C) | Very Fast | Good | [Markdown](examples/markly_markdown.rb) · [Merge](examples/markly_merge_example.rb) |
637
- | **Citrus** | Pure Ruby parsing | 🟡 Slower | Universal | [TOML](examples/citrus_toml.rb) · [Finitio](examples/citrus_finitio.rb) · [Dhall](examples/citrus_dhall.rb) |
638
- | **Parslet** | Pure Ruby parsing | 🟡 Slower | Universal | [TOML](examples/parslet_toml.rb) |
639
-
640
- **Selection Priority (Auto mode):** MRI → Rust → FFI → Java → Prism → Psych → Commonmarker → Markly → Citrus → Parslet
433
+ | Backend | Description | Performance | Portability |
434
+ |---------|-------------|-------------|-------------|
435
+ | **Prism** | Ruby's official parser | Very fast | Universal |
436
+ | **Psych** | Ruby's YAML parser (stdlib) | Very fast | Universal |
437
+ | **Commonmarker** | Markdown via comrak (Rust) | Very fast | MRI/JRuby/TruffleRuby where the gem is available |
438
+ | **Markly** | GFM via cmark-gfm (C) | Very fast | MRI/JRuby/TruffleRuby where the gem is available |
439
+ | **RBS** | Official RBS parser integration | Fast | Universal where the `rbs` gem is available |
440
+ | **Citrus** | Pure Ruby parsing | Slower | Universal |
441
+ | **Parslet** | Pure Ruby parsing | Slower | Universal |
442
+
443
+ #### Binary Schema Support
444
+
445
+ | Backend | Description | Performance | Portability |
446
+ |---------|-------------|-------------|-------------|
447
+ | **Kaitai Struct** | Backend reference and feature profile for binary schema analysis | Varies | Universal once a schema adapter is supplied |
448
+
449
+ **`TreeHaver::Parser` Auto-selection contract:** `:auto` never means
450
+ unregistered parser discovery. It selects the first registered TreeHaver backend
451
+ module whose availability check passes. Built-in tree-sitter facade priority is
452
+ MRI/Rust/FFI on MRI and Java/FFI on JRuby, followed by registered provider
453
+ modules such as Prism, Psych, Citrus, and Parslet when those providers have been
454
+ registered for the requested language. Explicit backend requests fail closed if
455
+ that backend is unavailable or has no parser registered for the requested
456
+ language.
641
457
 
642
458
  **Known Issues:**
643
459
 
@@ -647,6 +463,7 @@ TreeHaver supports 10 parsing backends, each with different trade-offs. The `aut
647
463
 
648
464
  ```ruby
649
465
  # Tree-sitter backends
466
+ gem "tree-sitter-language-pack" # TSLP / Kreuzberg language-pack provider
650
467
  gem "ruby_tree_sitter", "~> 2.0" # MRI backend
651
468
  gem "tree_stump" # Rust backend
652
469
  gem "ffi", ">= 1.15", "< 2.0" # FFI backend
@@ -657,6 +474,7 @@ gem "prism", "~> 1.0" # Ruby parsing (stdlib in Ruby 3.4+)
657
474
  # Psych: no gem required (Ruby stdlib)
658
475
  gem "commonmarker", ">= 0.23" # Markdown parsing (comrak)
659
476
  gem "markly", "~> 0.11" # GFM parsing (cmark-gfm)
477
+ gem "rbs" # RBS parsing
660
478
 
661
479
  # Pure Ruby fallbacks
662
480
  gem "citrus", "~> 3.0" # Citrus backend
@@ -668,6 +486,7 @@ gem "parslet", "~> 2.0" # Parslet backend
668
486
 
669
487
  ```ruby
670
488
  # Tree-sitter backends
489
+ TreeHaver.backend = :tslp # Force tree-sitter-language-pack where supported
671
490
  TreeHaver.backend = :mri # Force MRI backend (ruby_tree_sitter)
672
491
  TreeHaver.backend = :rust # Force Rust backend (tree_stump)
673
492
  TreeHaver.backend = :ffi # Force FFI backend
@@ -678,6 +497,7 @@ TreeHaver.backend = :prism # Force Prism (Ruby parsing)
678
497
  TreeHaver.backend = :psych # Force Psych (YAML parsing)
679
498
  TreeHaver.backend = :commonmarker # Force Commonmarker (Markdown)
680
499
  TreeHaver.backend = :markly # Force Markly (GFM Markdown)
500
+ TreeHaver.backend = :rbs # Force RBS parser integration
681
501
  TreeHaver.backend = :citrus # Force Citrus (Pure Ruby PEG)
682
502
  TreeHaver.backend = :parslet # Force Parslet (Pure Ruby PEG)
683
503
 
@@ -721,7 +541,7 @@ This is particularly useful for:
721
541
 
722
542
  - **Testing**: Test the same code with different backends
723
543
  - **Performance comparison**: Benchmark different backends
724
- - **Fallback scenarios**: Try one backend, fall back to another
544
+ - **Backend-selection scenarios**: Exercise each registered backend explicitly
725
545
  - **Thread isolation**: Each thread can use a different backend safely
726
546
 
727
547
  ```ruby
@@ -743,7 +563,7 @@ TreeHaver.backend_module # => TreeHaver::Backends::FFI
743
563
  TreeHaver.capabilities # => { backend: :ffi, parse: true, query: false, ... }
744
564
  ```
745
565
 
746
- See [examples/](examples/) directory for **26 complete working examples** demonstrating all 10 backends with multiple languages (JSON, JSONC, Bash, TOML, Ruby, YAML, Markdown) plus markdown-merge integration examples.
566
+ For runnable scenario examples, see the implementation-level [examples directory](../../examples/). Those examples are user-level scripts rather than backend-count smoke tests.
747
567
 
748
568
  ### Security Considerations
749
569
 
@@ -828,7 +648,8 @@ export TREE_SITTER_TOML_PATH=~/.local/share/mise/installs/lua/5.4.8/luarocks/lib
828
648
 
829
649
  ### Backend Selection
830
650
 
831
- TreeHaver automatically selects the best backend for your Ruby implementation, but you can override this behavior:
651
+ TreeHaver can select an available registered backend for you, but you can
652
+ override this behavior:
832
653
 
833
654
  ```ruby
834
655
  # Automatic backend selection (default)
@@ -839,7 +660,7 @@ TreeHaver.backend = :mri # Use ruby_tree_sitter (MRI only, C extension)
839
660
  TreeHaver.backend = :rust # Use tree_stump (MRI, Rust extension with precompiled binaries)
840
661
  # Note: Use tree_stump v0.2.0 or newer (fixes are released).
841
662
  TreeHaver.backend = :ffi # Use FFI bindings (works on MRI and JRuby)
842
- TreeHaver.backend = :java # Use Java bindings (JRuby only, coming soon)
663
+ TreeHaver.backend = :java # Use Java bindings (JRuby only)
843
664
  TreeHaver.backend = :citrus # Use Citrus pure Ruby parser
844
665
  # NOTE: Portable, all Ruby implementations
845
666
  # CAVEAT: few major language grammars, but many esoteric grammars
@@ -848,7 +669,13 @@ TreeHaver.backend = :parslet # Use Parslet pure Ruby parser
848
669
  # CAVEAT: few major language grammars, but many esoteric grammars
849
670
  ```
850
671
 
851
- **Auto-selection priority on MRI:** MRI Rust FFI Citrus → Parslet
672
+ **Auto-selection rule:** `:auto` uses the registered backend list and chooses
673
+ the first backend module that is allowed by environment configuration and reports
674
+ itself available. It does not call parser libraries directly, search for
675
+ language-specific fallback parsers outside the registry, or hide an explicit
676
+ backend failure by silently switching to a different backend.
677
+
678
+ **Built-in facade priority on MRI:** MRI → Rust → FFI → Citrus → Parslet
852
679
 
853
680
  You can also set the backend via environment variable:
854
681
 
@@ -1023,7 +850,12 @@ language = TreeHaver::Language.toml(
1023
850
 
1024
851
  ### Grammar Discovery with GrammarFinder
1025
852
 
1026
- For libraries that need to automatically locate tree-sitter grammars (like the `*-merge` family of gems), TreeHaver provides the `GrammarFinder` utility class. It handles platform-aware grammar discovery without requiring language-specific code in TreeHaver itself.
853
+ For libraries that need to automatically locate tree-sitter grammars (like the
854
+ `*-merge` family of gems), TreeHaver provides the `GrammarFinder` utility
855
+ class. It resolves explicit registrations first and then uses
856
+ `tree_sitter_language_pack` as the normalized on-demand provisioning path for
857
+ tree-sitter grammars. Parser-specific non-tree-sitter backends should be
858
+ registered by the owning merge gem rather than hardcoded in TreeHaver.
1027
859
 
1028
860
  ```ruby
1029
861
  # Create a finder for any language
@@ -1034,7 +866,7 @@ if finder.available?
1034
866
  puts "TOML grammar found at: #{finder.find_library_path}"
1035
867
  else
1036
868
  puts finder.not_found_message
1037
- # => "tree-sitter toml grammar not found. Searched: /usr/lib/libtree-sitter-toml.so, ..."
869
+ # => "tree-sitter toml grammar not found. Searched: /.../libtree_sitter_toml.so, ..."
1038
870
  end
1039
871
 
1040
872
  # Register the language if available
@@ -1044,47 +876,109 @@ finder.register! if finder.available?
1044
876
  language = TreeHaver::Language.toml
1045
877
  ```
1046
878
 
879
+ #### Registration Bootstrap
880
+
881
+ TreeHaver is the shared registry. It is not the owner of parser-family policy.
882
+
883
+ - Tree-sitter grammars should be normalized through `GrammarFinder` and
884
+ `tree_sitter_language_pack` or an explicit registration.
885
+ - Non-tree-sitter backends should be registered by the merge gem that owns
886
+ that parser family.
887
+ - Tools that load multiple merge gems should invoke each gem's registration
888
+ bootstrap so TreeHaver sees the full set of available grammars before
889
+ `parser_for` is called.
890
+
891
+ ```ruby
892
+ # In a tool that uses several merge gems
893
+ require "tree_haver"
894
+ require "toml-merge"
895
+ require "markdown-merge"
896
+
897
+ Toml::Merge.register_backend!
898
+ Markdown::Merge.register_backend!
899
+
900
+ parser = TreeHaver.parser_for(:toml)
901
+ ```
902
+
903
+ Once those registrations have run, `TreeHaver.parser_for` can resolve any
904
+ registered tree-sitter grammar plus any registered backend-specific grammar for
905
+ the active backend mode. If a merge depends on a grammar that has not been
906
+ registered and cannot be provisioned through `tree_sitter_language_pack`,
907
+ TreeHaver raises `TreeHaver::NotAvailable`.
908
+
1047
909
  #### GrammarFinder Automatic Derivation
1048
910
 
1049
911
  Given just the language name, `GrammarFinder` automatically derives:
1050
912
 
1051
- | Property | Derived Value (for `:toml`) |
1052
- |------------------|------------------------------------------------------|
1053
- | ENV var | `TREE_SITTER_TOML_PATH` |
1054
- | Library filename | `libtree-sitter-toml.so` (Linux) or `.dylib` (macOS) |
1055
- | Symbol name | `tree_sitter_toml` |
913
+ | Property | Derived Value (for `:toml`) |
914
+ |------------------|-----------------------------------------------------|
915
+ | ENV var | `TREE_SITTER_TOML_PATH` |
916
+ | Library filename | `libtree_sitter_toml.so` (Linux) or `.dylib` (macOS) |
917
+ | Symbol name | `tree_sitter_toml` |
1056
918
 
1057
919
  #### Search Order
1058
920
 
1059
921
  `GrammarFinder` searches for grammars in this order:
1060
922
 
1061
923
  1. **Environment variable**: `TREE_SITTER_<LANG>_PATH` (highest priority)
1062
- 2. **Extra paths**: Custom paths provided at initialization
1063
- 3. **System paths**: Common installation directories (`/usr/lib`, `/usr/local/lib`, `/opt/homebrew/lib`, etc.)
924
+ 2. **Existing TreeHaver registration**: previously-registered tree-sitter grammar path
925
+ 3. **Extra paths**: explicit paths provided at initialization
926
+ 4. **`tree_sitter_language_pack`**: parser API availability through its on-demand grammar loader
1064
927
 
1065
- #### Usage in \*-merge Gems
928
+ #### TSLP Cold-Cache Troubleshooting
1066
929
 
1067
- The `GrammarFinder` pattern enables clean integration in language-specific merge gems:
930
+ `tree_sitter_language_pack` loads bundled grammars on demand. A cold install may
931
+ not have a populated shared-library cache before the first parse, so
932
+ `GrammarFinder` must not treat cache contents as the source of truth for TSLP
933
+ availability.
934
+
935
+ For TSLP-backed languages, check availability through the TSLP parser API and
936
+ then register the language with the TreeHaver TSLP backend:
1068
937
 
1069
938
  ```ruby
1070
- # In toml-merge
1071
939
  finder = TreeHaver::GrammarFinder.new(:toml)
1072
940
  finder.register! if finder.available?
1073
941
 
1074
- # In json-merge
1075
- finder = TreeHaver::GrammarFinder.new(:json)
1076
- finder.register! if finder.available?
942
+ TreeHaver.with_backend("kreuzberg-language-pack") do
943
+ TreeHaver.parser_for(:toml).parse("title = \"example\"\n")
944
+ end
945
+ ```
946
+
947
+ If this fails from a cold start, inspect `finder.search_info` and confirm that
948
+ the `tree-sitter-language-pack` gem is in the bundle being used. Do not add a
949
+ merge-gem parser fallback to hide the failure; fix the TreeHaver registration
950
+ or the TSLP grammar binding instead.
951
+
952
+ #### Usage in \*-merge Gems
953
+
954
+ The `GrammarFinder` pattern enables clean integration in language-specific
955
+ merge gems:
1077
956
 
1078
- # In bash-merge
1079
- finder = TreeHaver::GrammarFinder.new(:bash)
957
+ ```ruby
958
+ # In a substrate merge gem
959
+ finder = TreeHaver::GrammarFinder.new(:toml)
1080
960
  finder.register! if finder.available?
1081
961
  ```
1082
962
 
1083
- Each gem uses the same API—only the language name changes.
963
+ Parser-specific gems should register their own TreeHaver backend wrappers:
964
+
965
+ ```ruby
966
+ # In a TOML parser provider gem
967
+ TreeHaver.register_language(
968
+ :toml,
969
+ backend_module: Citrus::Toml::Merge::Backend,
970
+ backend_type: :citrus,
971
+ gem_name: "toml-rb",
972
+ )
973
+ ```
974
+
975
+ Each gem uses the same API. TreeHaver owns the shared registration surface;
976
+ merge gems own parser-specific backend registrations and any explicit bootstrap
977
+ hook they expose to register them.
1084
978
 
1085
979
  #### Adding Custom Search Paths
1086
980
 
1087
- For non-standard installations, provide extra search paths:
981
+ For non-standard standalone grammar builds, provide extra search paths:
1088
982
 
1089
983
  ```ruby
1090
984
  finder = TreeHaver::GrammarFinder.new(:toml, extra_paths: [
@@ -1105,9 +999,9 @@ puts finder.search_info
1105
999
  # env_var: "TREE_SITTER_TOML_PATH",
1106
1000
  # env_value: nil,
1107
1001
  # symbol: "tree_sitter_toml",
1108
- # library_filename: "libtree-sitter-toml.so",
1109
- # search_paths: ["/usr/lib/libtree-sitter-toml.so", ...],
1110
- # found_path: "/usr/lib/libtree-sitter-toml.so",
1002
+ # library_filename: "libtree_sitter_toml.so",
1003
+ # search_paths: ["/custom/lib/libtree_sitter_toml.so", "/.../tree-sitter-language-pack/..."],
1004
+ # found_path: "/.../libtree_sitter_toml.so",
1111
1005
  # available: true
1112
1006
  # }
1113
1007
  ```
@@ -1127,80 +1021,20 @@ TreeHaver.capabilities
1127
1021
  # => { backend: :parslet, parse: true, query: false, bytes_field: false }
1128
1022
  ```
1129
1023
 
1130
- ### Compatibility Mode
1131
-
1132
- For codebases migrating from `ruby_tree_sitter`, TreeHaver provides a compatibility shim:
1133
-
1134
- ```ruby
1135
- require "tree_haver/compat"
1136
-
1137
- # Now TreeSitter constants map to TreeHaver
1138
- parser = TreeSitter::Parser.new # Actually creates TreeHaver::Parser
1139
- ```
1140
-
1141
- This is safe and idempotent—if the real `TreeSitter` module is already loaded, the shim does nothing.
1142
-
1143
- #### ⚠️ Important: Exception Hierarchy
1144
-
1145
- **Both ruby\_tree\_sitter v2+ and TreeHaver exceptions inherit from `Exception` (not `StandardError`).**
1146
-
1147
- This design decision follows ruby\_tree\_sitter's lead for thread-safety and signal handling reasons. See [ruby\_tree\_sitter PR \#83](https://github.com/Faveod/ruby-tree-sitter/pull/83) for the rationale.
1024
+ ### Error Handling Model
1148
1025
 
1149
- **What this means for exception handling:**
1150
-
1151
- ```ruby
1152
- # ⚠️ This will NOT catch TreeHaver errors
1153
- begin
1154
- TreeHaver::Language.from_library("/nonexistent.so")
1155
- rescue => e
1156
- puts "Caught!" # Never reached - TreeHaver::Error inherits Exception
1157
- end
1158
-
1159
- # ✅ Explicit rescue is required
1160
- begin
1161
- TreeHaver::Language.from_library("/nonexistent.so")
1162
- rescue TreeHaver::Error => e
1163
- puts "Caught!" # This works
1164
- end
1165
-
1166
- # ✅ Or rescue specific exceptions
1167
- begin
1168
- TreeHaver::Language.from_library("/nonexistent.so")
1169
- rescue TreeHaver::NotAvailable => e
1170
- puts "Grammar not available: #{e.message}"
1171
- end
1172
- ```
1026
+ TreeHaver does not ship a `TreeSitter::*` compatibility namespace. Use the `TreeHaver::*` API directly, and rescue `TreeHaver::Error` or a more specific subclass when parser setup can fail.
1173
1027
 
1174
1028
  **TreeHaver Exception Hierarchy:**
1175
1029
 
1176
- Exception
1030
+ StandardError
1177
1031
  └── TreeHaver::Error # Base error class
1178
1032
  ├── TreeHaver::NotAvailable # Backend/grammar not available
1179
1033
  └── TreeHaver::BackendConflict # Backend incompatibility detected
1180
1034
 
1181
- **Compatibility Mode Behavior:**
1182
-
1183
- The compat mode (`require "tree_haver/compat"`) creates aliases but **does not change the exception hierarchy**:
1184
-
1185
- ```ruby
1186
- require "tree_haver/compat"
1187
-
1188
- # TreeSitter constants are now aliases to TreeHaver
1189
- TreeSitter::Error # => TreeHaver::Error (still inherits Exception)
1190
- TreeSitter::Parser # => TreeHaver::Parser
1191
- TreeSitter::Language # => TreeHaver::Language
1192
-
1193
- # Exception handling remains the same
1194
- begin
1195
- TreeSitter::Language.load("missing", "/nonexistent.so")
1196
- rescue TreeSitter::Error => e # Still requires explicit rescue
1197
- puts "Error: #{e.message}"
1198
- end
1199
- ```
1200
-
1201
1035
  **Best Practices:**
1202
1036
 
1203
- 1. **Always use explicit rescue** for TreeHaver errors:
1037
+ 1. **Rescue TreeHaver errors explicitly when backend setup can fail:**
1204
1038
 
1205
1039
  ```ruby
1206
1040
  begin
@@ -1209,29 +1043,24 @@ end
1209
1043
  language = TreeHaver::Language.toml
1210
1044
  rescue TreeHaver::NotAvailable => e
1211
1045
  warn("TOML grammar not available: #{e.message}")
1212
- # Fallback to another backend or fail gracefully
1046
+ # Select another registered TreeHaver backend explicitly, or fail gracefully
1213
1047
  end
1214
1048
  ```
1215
1049
 
1216
- 2. **Never rely on `rescue => e`** to catch TreeHaver errors (it won't work)
1217
- **Why inherit from Exception?**
1218
- Following ruby\_tree\_sitter's reasoning:
1219
-
1220
- - **Thread safety**: Prevents accidental catching in thread cleanup code
1221
- - **Signal handling**: Ensures parsing errors don't interfere with SIGTERM/SIGINT
1222
- - **Intentional handling**: Forces developers to explicitly handle parsing errors
1223
- See `lib/tree_haver/compat.rb` for compatibility layer documentation.
1050
+ 2. **Prefer specific handling** for `TreeHaver::NotAvailable` when a backend or grammar is optional.
1224
1051
 
1225
1052
  ## 🔧 Basic Usage
1226
1053
 
1227
1054
  ### Quick Start
1228
1055
 
1229
- The simplest way to parse code is with `TreeHaver.parser_for`, which handles all the complexity of language loading, grammar discovery, and backend selection:
1056
+ The simplest way to parse code is with `TreeHaver.parser_for`, which handles
1057
+ language loading, grammar resolution, and backend selection:
1230
1058
 
1231
1059
  ```ruby
1232
1060
  require "tree_haver"
1233
1061
 
1234
- # Parse TOML - auto-discovers grammar and falls back to Citrus if needed
1062
+ # Parse TOML - resolves any registered tree-sitter grammar and any registered
1063
+ # non-tree-sitter backend for the active backend mode
1235
1064
  parser = TreeHaver.parser_for(:toml)
1236
1065
  tree = parser.parse("[package]\nname = \"my-app\"")
1237
1066
 
@@ -1246,7 +1075,7 @@ tree = parser.parse("#!/bin/bash\necho hello")
1246
1075
  # With explicit library path
1247
1076
  parser = TreeHaver.parser_for(:toml, library_path: "/custom/path/libtree-sitter-toml.so")
1248
1077
 
1249
- # With Citrus fallback configuration
1078
+ # With explicit Citrus provider configuration
1250
1079
  parser = TreeHaver.parser_for(
1251
1080
  :toml,
1252
1081
  citrus_config: {gem_name: "toml-rb", grammar_const: "TomlRB::Document"},
@@ -1257,7 +1086,7 @@ parser = TreeHaver.parser_for(
1257
1086
 
1258
1087
  1. Checking if the language is already registered
1259
1088
  2. Auto-discovering tree-sitter grammar via `GrammarFinder`
1260
- 3. Falling back to Citrus grammar if tree-sitter is unavailable
1089
+ 3. Using any registered backend-specific grammar for the active backend
1261
1090
  4. Creating and configuring the parser
1262
1091
  5. Raising `NotAvailable` with a helpful message if nothing works
1263
1092
 
@@ -1265,7 +1094,7 @@ parser = TreeHaver.parser_for(
1265
1094
 
1266
1095
  For more control, you can create parsers manually:
1267
1096
 
1268
- TreeHaver works with any language through its 10 backends. Here are examples for different parsing needs:
1097
+ TreeHaver works with many languages through its registry of tree-sitter adapters, language-pack providers, native parsers, PEG parsers, and binary schema support. Here are examples for different parsing needs:
1269
1098
 
1270
1099
  #### Parsing with Tree-sitter (Universal Languages)
1271
1100
 
@@ -1529,9 +1358,7 @@ tree.edit(
1529
1358
  new_tree = parser.parse_string(tree, "x = 42")
1530
1359
  ```
1531
1360
 
1532
- **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter` / `jtreesitter`) backend. The FFI, Citrus, and Parslet backends do not currently support incremental parsing. You can check support with:
1533
-
1534
- **Note:** `tree_stump` currently requires unreleased fixes in the `main` branch.
1361
+ **Note:** Incremental parsing requires the MRI (`ruby_tree_sitter`), Rust (`tree_stump`), or Java (`java-tree-sitter` / `jtreesitter`) backend. The FFI, Citrus, and Parslet backends do not support incremental parsing. You can check support with:
1535
1362
 
1536
1363
  ```ruby
1537
1364
  tree.supports_editing? # => true if edit() is available
@@ -1549,7 +1376,7 @@ end
1549
1376
  # Check if a backend is available
1550
1377
  if TreeHaver.backend_module.nil?
1551
1378
  puts "No TreeHaver backend is available!"
1552
- puts "Install ruby_tree_sitter (MRI), ffi gem with libtree-sitter, citrus gem, or parslet gem"
1379
+ puts "Install tree-sitter-language-pack, ruby_tree_sitter, tree_stump, ffi with libtree-sitter, or a Ruby parser backend such as prism, psych, citrus, or parslet"
1553
1380
  end
1554
1381
  ```
1555
1382
 
@@ -1595,8 +1422,8 @@ parser = TreeHaver::Parser.new
1595
1422
  ```bash
1596
1423
  # 1. Download java-tree-sitter JAR from Maven Central
1597
1424
  mkdir -p vendor/jars
1598
- curl -fSL -o vendor/jars/jtreesitter-0.23.2.jar \
1599
- "https://repo1.maven.org/maven2/io/github/tree-sitter/jtreesitter/0.23.2/jtreesitter-0.23.2.jar"
1425
+ curl -fSL -o vendor/jars/jtreesitter-0.26.0.jar \
1426
+ "https://repo1.maven.org/maven2/io/github/tree-sitter/jtreesitter/0.26.0/jtreesitter-0.26.0.jar"
1600
1427
 
1601
1428
  # 2. Set environment variables
1602
1429
  export CLASSPATH="$(pwd)/vendor/jars:$CLASSPATH"
@@ -1692,16 +1519,16 @@ end
1692
1519
 
1693
1520
  #### TruffleRuby
1694
1521
 
1695
- TruffleRuby can use the MRI, FFI, Citrus, or Parslet backend:
1522
+ TruffleRuby can use Ruby-native parser backends such as Prism, Psych, Citrus, or Parslet. The FFI tree-sitter backend is not selected on TruffleRuby because tree-sitter's struct-by-value API is incompatible with the current FFI path.
1696
1523
 
1697
1524
  ```ruby
1698
- # Use FFI backend (recommended for tree-sitter grammars)
1699
- TreeHaver.backend = :ffi
1525
+ # Use Prism for Ruby source
1526
+ TreeHaver.backend = :prism
1700
1527
 
1701
- # Or try MRI backend if ruby_tree_sitter compiles on your TruffleRuby version
1702
- TreeHaver.backend = :mri
1528
+ # Or use Psych for YAML
1529
+ TreeHaver.backend = :psych
1703
1530
 
1704
- # Or use Citrus backend for zero native dependencies
1531
+ # Use Citrus backend for zero native dependencies
1705
1532
  TreeHaver.backend = :citrus
1706
1533
 
1707
1534
  # Or use Parslet backend for zero native dependencies
@@ -1791,23 +1618,15 @@ TreeHaver.with_backend(:rust) do
1791
1618
  end
1792
1619
  ```
1793
1620
 
1794
- #### Fallback Pattern
1621
+ #### Explicit Backend Selection Pattern
1795
1622
 
1796
- Try one backend, fall back to another on failure:
1623
+ Try registered TreeHaver backends explicitly when you want controlled provider
1624
+ selection. Structured merge gems should fail closed when no registered TreeHaver
1625
+ backend can parse the requested language.
1797
1626
 
1798
1627
  ```ruby
1799
- def parse_with_fallback(source)
1800
- TreeHaver.with_backend(:mri) do
1801
- TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
1802
- end
1803
- rescue TreeHaver::NotAvailable
1804
- # Fall back to Citrus if MRI backend unavailable
1805
- TreeHaver.with_backend(:citrus) do
1806
- TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
1807
- end
1808
- rescue TreeHaver::NotAvailable
1809
- # Fall back to Parslet if Citrus backend unavailable
1810
- TreeHaver.with_backend(:parslet) do
1628
+ def parse_with_backend(source, backend_name)
1629
+ TreeHaver.with_backend(backend_name) do
1811
1630
  TreeHaver::Parser.new.tap { |p| p.language = load_language }.parse(source)
1812
1631
  end
1813
1632
  end
@@ -1897,7 +1716,7 @@ end
1897
1716
 
1898
1717
  Tags follow a naming convention:
1899
1718
 
1900
- - `*_backend` = TreeHaver backends (mri, rust, ffi, java, prism, psych, commonmarker, markly, citrus, parslet, rbs)
1719
+ - `*_backend` = TreeHaver backend availability checks (tslp, mri, rust, ffi, java, prism, psych, commonmarker, markly, citrus, parslet, rbs)
1901
1720
  - `*_engine` = Ruby engines (mri, jruby, truffleruby)
1902
1721
  - `*_grammar` = tree-sitter grammar files (.so)
1903
1722
  - `*_parsing` = any parsing capability for a language (combines multiple backends/grammars)
@@ -1906,7 +1725,7 @@ Tags follow a naming convention:
1906
1725
  | Tag | Description |
1907
1726
  |-------------------------|---------------------------------------------------------------------------|
1908
1727
  | **Backend Tags** | |
1909
- | `:ffi_backend` | FFI backend available (dynamic check, legacy alias: `:ffi`) |
1728
+ | `:ffi_backend` | FFI backend available (dynamic check) |
1910
1729
  | `:ffi_backend_only` | FFI backend in isolation (won't trigger MRI check) |
1911
1730
  | `:mri_backend` | ruby\_tree\_sitter gem available |
1912
1731
  | `:mri_backend_only` | MRI backend in isolation (won't trigger FFI check) |
@@ -1918,7 +1737,8 @@ Tags follow a naming convention:
1918
1737
  | `:markly_backend` | markly gem available |
1919
1738
  | `:citrus_backend` | Citrus gem available |
1920
1739
  | `:parslet_backend` | Parslet gem available |
1921
- | `:rbs_backend` | RBS gem available (official RBS parser, MRI only) |
1740
+ | `:tslp_backend` | tree-sitter-language-pack available |
1741
+ | `:rbs_backend` | RBS gem available (official RBS parser) |
1922
1742
  | **Engine Tags** | |
1923
1743
  | `:mri_engine` | Running on MRI (CRuby) |
1924
1744
  | `:jruby_engine` | Running on JRuby |
@@ -1950,63 +1770,6 @@ Set `TREE_HAVER_DEBUG=1` to print a dependency summary at the start of your test
1950
1770
  TREE_HAVER_DEBUG=1 bundle exec rspec
1951
1771
  ```
1952
1772
 
1953
- ## 🦷 FLOSS Funding
1954
-
1955
- While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
1956
- Raising a monthly budget of... "dollars" would make the project more sustainable.
1957
-
1958
- We welcome both individual and corporate sponsors\! We also offer a
1959
- wide array of funding channels to account for your preferences
1960
- (although currently [Open Collective][🖇osc] is our preferred funding platform).
1961
-
1962
- **If you're working in a company that's making significant use of kettle-rb tools we'd
1963
- appreciate it if you suggest to your company to become a kettle-rb sponsor.**
1964
-
1965
- You can support the development of kettle-rb tools via
1966
- [GitHub Sponsors][🖇sponsor],
1967
- [Liberapay][⛳liberapay],
1968
- [PayPal][🖇paypal],
1969
- [Open Collective][🖇osc]
1970
- and [Tidelift][🏙️entsup-tidelift].
1971
-
1972
- | 📍 NOTE |
1973
- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1974
- | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
1975
-
1976
- ### Open Collective for Individuals
1977
-
1978
- Support us with a monthly donation and help us continue our activities. \[[Become a backer][🖇osc-backers]\]
1979
-
1980
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1981
-
1982
- <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
1983
- No backers yet. Be the first!
1984
- <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
1985
-
1986
- ### Open Collective for Organizations
1987
-
1988
- Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor][🖇osc-sponsors]\]
1989
-
1990
- NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
1991
-
1992
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
1993
- No sponsors yet. Be the first!
1994
- <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
1995
-
1996
- [kettle-readme-backers]: https://github.com/kettle-rb/tree_haver/blob/main/exe/kettle-readme-backers
1997
-
1998
- ### Another way to support open-source
1999
-
2000
- I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
2001
-
2002
- If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
2003
-
2004
- I’m developing a new library, [floss\_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
2005
-
2006
- **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
2007
-
2008
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
2009
-
2010
1773
  ## 🔐 Security
2011
1774
 
2012
1775
  See [SECURITY.md][🔐security].
@@ -2014,78 +1777,43 @@ See [SECURITY.md][🔐security].
2014
1777
  ## 🤝 Contributing
2015
1778
 
2016
1779
  If you need some ideas of where to help, you could work on adding more code coverage,
2017
- or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
1780
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
2018
1781
  or use the gem and think about how it could be better.
2019
1782
 
2020
1783
  We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
2021
1784
 
2022
1785
  See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
2023
1786
 
2024
- ### 🚀 Release Instructions
2025
-
2026
- See [CONTRIBUTING.md][🤝contributing].
2027
-
2028
1787
  ### Code Coverage
2029
1788
 
2030
- [![Coverage Graph][🏀codecov-g]][🏀codecov]
2031
-
2032
- [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
1789
+ Run the test suite with coverage enabled:
2033
1790
 
2034
- [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
2035
-
2036
- ### 🪇 Code of Conduct
2037
-
2038
- Everyone interacting with this project's codebases, issue trackers,
2039
- chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
2040
-
2041
- ## 🌈 Contributors
2042
-
2043
- [![Contributors][🖐contributors-img]][🖐contributors]
2044
-
2045
- Made with [contributors-img][🖐contrib-rocks].
2046
-
2047
- Also see GitLab Contributors: <https://gitlab.com/kettle-rb/tree_haver/-/graphs/main>
2048
-
2049
- <details>
2050
- <summary>⭐️ Star History</summary>
1791
+ ```bash
1792
+ bundle exec kettle-test
1793
+ ```
2051
1794
 
2052
- <a href="https://star-history.com/#kettle-rb/tree_haver&Date">
2053
- <picture>
2054
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date&theme=dark" />
2055
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date" />
2056
- <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kettle-rb/tree_haver&type=Date" />
2057
- </picture>
2058
- </a>
1795
+ Inspect the generated report with:
2059
1796
 
2060
- </details>
1797
+ ```bash
1798
+ kettle-soup-cover -d
1799
+ ```
2061
1800
 
2062
1801
  ## 📌 Versioning
2063
1802
 
2064
- This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
2065
- Violations of this scheme should be reported as bugs.
2066
- Specifically, if a minor or patch version is released that breaks backward compatibility,
2067
- a new version should be immediately released that restores compatibility.
2068
- Breaking changes to the public API will only be introduced with new major versions.
2069
-
2070
- > dropping support for a platform is both obviously and objectively a breaking change <br/>
2071
- > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
2072
-
2073
- I understand that policy doesn't work universally ("exceptions to every rule\!"),
2074
- but it is the policy here.
2075
- As such, in many cases it is good to specify a dependency on this library using
2076
- the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
1803
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
1804
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
2077
1805
 
2078
1806
  For example:
2079
1807
 
2080
1808
  ```ruby
2081
- spec.add_dependency("tree_haver", "~> 5.0")
1809
+ spec.add_dependency("tree_haver", "~> 7.0")
2082
1810
  ```
2083
1811
 
2084
1812
  <details markdown="1">
2085
1813
  <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
2086
1814
 
2087
- SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
2088
- is a *breaking change* to an API, and for that reason the bike shedding is endless.
1815
+ Dropping support for a platform can be a breaking change for affected users.
1816
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
2089
1817
 
2090
1818
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
2091
1819
  read this article from the creator of SemVer:
@@ -2098,68 +1826,19 @@ See [CHANGELOG.md][📌changelog] for a list of releases.
2098
1826
 
2099
1827
  ## 📄 License
2100
1828
 
2101
- The gem is available as open source under the terms of
2102
- the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
2103
- See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
2104
-
2105
- ### © Copyright
2106
-
2107
- <ul>
2108
- <li>
2109
- Copyright (c) 2025-2026 Peter H. Boling, of
2110
- <a href="https://discord.gg/3qme4XHNKN">
2111
- Galtzo.com
2112
- <picture>
2113
- <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
2114
- </picture>
2115
- </a>, and tree_haver contributors.
2116
- </li>
2117
- </ul>
2118
-
2119
- ## 🤑 A request for help
2120
-
2121
- Maintainers have teeth and need to pay their dentists.
2122
- After getting laid off in an RIF in March, and encountering difficulty finding a new one,
2123
- I began spending most of my time building open source tools.
2124
- I'm hoping to be able to pay for my kids' health insurance this month,
2125
- so if you value the work I am doing, I need your support.
2126
- Please consider sponsoring me or the project.
2127
-
2128
- To join the community or get help 👇️ Join the Discord.
2129
-
2130
- [![Live Chat on Discord][✉️discord-invite-img-ftb]][🖼️galtzo-discord]
2131
-
2132
- To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
2133
-
2134
- [![Sponsor kettle-rb/tree\_haver on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
2135
-
2136
- ### Please give the project a star ⭐ ♥.
1829
+ The gem is available under the following licenses: [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md), [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md).
1830
+ See [LICENSE.md][📄license] for details.
2137
1831
 
2138
- Thanks for RTFM. ☺️
1832
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
2139
1833
 
2140
1834
  [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
2141
1835
  [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
2142
1836
  [⛳liberapay]: https://liberapay.com/pboling/donate
2143
- [🖇osc-all-img]: https://img.shields.io/opencollective/all/kettle-rb
2144
- [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/kettle-rb
2145
- [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/kettle-rb
2146
- [🖇osc-backers]: https://opencollective.com/kettle-rb#backer
2147
- [🖇osc-backers-i]: https://opencollective.com/kettle-rb/backers/badge.svg?style=flat
2148
- [🖇osc-sponsors]: https://opencollective.com/kettle-rb#sponsor
2149
- [🖇osc-sponsors-i]: https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat
2150
- [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge
2151
- [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/kettle-rb?style=for-the-badge
2152
- [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/kettle-rb?style=for-the-badge
2153
- [🖇osc]: https://opencollective.com/kettle-rb
2154
1837
  [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
2155
1838
  [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
2156
1839
  [🖇sponsor]: https://github.com/sponsors/pboling
2157
- [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
2158
- [🖇polar]: https://polar.sh/pboling
2159
1840
  [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
2160
- [🖇kofi]: https://ko-fi.com/O5O86SNP4
2161
- [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
2162
- [🖇patreon]: https://patreon.com/galtzo
1841
+ [🖇kofi]: https://ko-fi.com/pboling
2163
1842
  [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
2164
1843
  [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
2165
1844
  [🖇buyme]: https://www.buymeacoffee.com/pboling
@@ -2172,19 +1851,22 @@ Thanks for RTFM. ☺️
2172
1851
  [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
2173
1852
  [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
2174
1853
  [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
1854
+ [✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
1855
+ [✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
1856
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/tree_haver
2175
1857
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
2176
- [⛳️gem-namespace]: https://github.com/kettle-rb/tree_haver
1858
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/tree_haver
2177
1859
  [⛳️namespace-img]: https://img.shields.io/badge/namespace-TreeHaver-3C2D2D.svg?style=square&logo=ruby&logoColor=white
2178
- [⛳️gem-name]: https://bestgems.org/gems/tree_haver
1860
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/tree_haver
2179
1861
  [⛳️name-img]: https://img.shields.io/badge/name-tree__haver-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
2180
- [⛳️tag-img]: https://img.shields.io/github/tag/kettle-rb/tree_haver.svg
2181
- [⛳️tag]: http://github.com/kettle-rb/tree_haver/releases
1862
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
1863
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
2182
1864
  [🚂maint-blog]: http://www.railsbling.com/tags/tree_haver
2183
1865
  [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
2184
1866
  [🚂maint-contact]: http://www.railsbling.com/contact
2185
1867
  [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
2186
1868
  [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
2187
- [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
1869
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
2188
1870
  [💖✌️wellfound]: https://wellfound.com/u/peter-boling
2189
1871
  [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
2190
1872
  [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
@@ -2213,106 +1895,63 @@ Thanks for RTFM. ☺️
2213
1895
  [💁🏼‍♂️peterboling]: http://www.peterboling.com
2214
1896
  [🚂railsbling]: http://www.railsbling.com
2215
1897
  [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
2216
- [📜src-gl]: https://gitlab.com/kettle-rb/tree_haver/
1898
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/tree_haver
2217
1899
  [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
2218
- [📜src-cb]: https://codeberg.org/kettle-rb/tree_haver
2219
- [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
2220
- [📜src-gh]: https://github.com/kettle-rb/tree_haver
1900
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/tree_haver
1901
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
1902
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/tree_haver
2221
1903
  [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
2222
1904
  [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
2223
- [📜gl-wiki]: https://gitlab.com/kettle-rb/tree_haver/-/wikis/home
2224
- [📜gh-wiki]: https://github.com/kettle-rb/tree_haver/wiki
2225
- [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
2226
- [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
2227
- [👽dl-rank]: https://bestgems.org/gems/tree_haver
2228
- [👽dl-ranki]: https://img.shields.io/gem/rd/tree_haver.svg
2229
- [👽oss-help]: https://www.codetriage.com/kettle-rb/tree_haver
2230
- [👽oss-helpi]: https://www.codetriage.com/kettle-rb/tree_haver/badges/users.svg
2231
- [👽version]: https://bestgems.org/gems/tree_haver
1905
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
1906
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
1907
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
1908
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
1909
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/tree_haver
1910
+ [👽dl-ranki]: https://img.shields.io/gem/dt/tree_haver.svg
1911
+ [👽version]: https://clickgems.clickhouse.com/dashboard/tree_haver
2232
1912
  [👽versioni]: https://img.shields.io/gem/v/tree_haver.svg
2233
- [🏀qlty-mnt]: https://qlty.sh/gh/kettle-rb/projects/tree_haver
2234
- [🏀qlty-mnti]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/maintainability.svg
2235
- [🏀qlty-cov]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/metrics/code?sort=coverageRating
2236
- [🏀qlty-covi]: https://qlty.sh/gh/kettle-rb/projects/tree_haver/coverage.svg
2237
- [🏀codecov]: https://codecov.io/gh/kettle-rb/tree_haver
2238
- [🏀codecovi]: https://codecov.io/gh/kettle-rb/tree_haver/graph/badge.svg
2239
- [🏀coveralls]: https://coveralls.io/github/kettle-rb/tree_haver?branch=main
2240
- [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-rb/tree_haver/badge.svg?branch=main
2241
- [🖐codeQL]: https://github.com/kettle-rb/tree_haver/security/code-scanning
2242
- [🖐codeQL-img]: https://github.com/kettle-rb/tree_haver/actions/workflows/codeql-analysis.yml/badge.svg
2243
- [🚎2-cov-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml
2244
- [🚎2-cov-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/coverage.yml/badge.svg
2245
- [🚎3-hd-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml
2246
- [🚎3-hd-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/heads.yml/badge.svg
2247
- [🚎5-st-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml
2248
- [🚎5-st-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/style.yml/badge.svg
2249
- [🚎6-s-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml
2250
- [🚎6-s-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/supported.yml/badge.svg
2251
- [🚎9-t-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml
2252
- [🚎9-t-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/truffle.yml/badge.svg
2253
- [🚎11-c-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
2254
- [🚎11-c-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
2255
- [🚎12-crh-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml
2256
- [🚎12-crh-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/dep-heads.yml/badge.svg
2257
- [🚎13-🔒️-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml
2258
- [🚎13-🔒️-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/locked_deps.yml/badge.svg
2259
- [🚎14-🔓️-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml
2260
- [🚎14-🔓️-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/unlocked_deps.yml/badge.svg
2261
- [🚎15-🪪-wf]: https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml
2262
- [🚎15-🪪-wfi]: https://github.com/kettle-rb/tree_haver/actions/workflows/license-eye.yml/badge.svg
2263
- [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
2264
- [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
2265
- [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1913
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
1914
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
2266
1915
  [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
2267
- [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
2268
- [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
2269
- [💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
2270
- [💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
2271
- [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
2272
- [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
2273
- [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
2274
- [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
2275
- [🤝gh-issues]: https://github.com/kettle-rb/tree_haver/issues
2276
- [🤝gh-pulls]: https://github.com/kettle-rb/tree_haver/pulls
2277
- [🤝gl-issues]: https://gitlab.com/kettle-rb/tree_haver/-/issues
2278
- [🤝gl-pulls]: https://gitlab.com/kettle-rb/tree_haver/-/merge_requests
2279
- [🤝cb-issues]: https://codeberg.org/kettle-rb/tree_haver/issues
2280
- [🤝cb-pulls]: https://codeberg.org/kettle-rb/tree_haver/pulls
1916
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
1917
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
1918
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
1919
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
1920
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
1921
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
2281
1922
  [🤝cb-donate]: https://donate.codeberg.org/
2282
- [🤝contributing]: CONTRIBUTING.md
2283
- [🏀codecov-g]: https://codecov.io/gh/kettle-rb/tree_haver/graphs/tree.svg
1923
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
2284
1924
  [🖐contrib-rocks]: https://contrib.rocks
2285
- [🖐contributors]: https://github.com/kettle-rb/tree_haver/graphs/contributors
2286
- [🖐contributors-img]: https://contrib.rocks/image?repo=kettle-rb/tree_haver
2287
- [🚎contributors-gl]: https://gitlab.com/kettle-rb/tree_haver/-/graphs/main
2288
- [🪇conduct]: CODE_OF_CONDUCT.md
1925
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
1926
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
1927
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
2289
1928
  [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
2290
1929
  [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
2291
1930
  [📌semver]: https://semver.org/spec/v2.0.0.html
2292
1931
  [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
2293
1932
  [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
2294
1933
  [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
2295
- [📌changelog]: CHANGELOG.md
1934
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
2296
1935
  [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
2297
1936
  [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
2298
1937
  [📌gitmoji]: https://gitmoji.dev
2299
1938
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
2300
1939
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
2301
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.543-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
2302
- [🔐security]: SECURITY.md
1940
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.053-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1941
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
2303
1942
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
2304
1943
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
2305
- [📄license]: LICENSE.txt
2306
- [📄license-ref]: https://opensource.org/licenses/MIT
2307
- [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
2308
- [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
2309
- [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
1944
+ [📄license]: LICENSE.md
1945
+ [📄license-ref]: LICENSE.md
1946
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
1947
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
1948
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
2310
1949
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
2311
1950
  [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
2312
1951
  [🚎yard-current]: http://rubydoc.info/gems/tree_haver
2313
1952
  [🚎yard-head]: https://tree-haver.galtzo.com
2314
1953
  [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
2315
- [💎SHA_checksums]: https://gitlab.com/kettle-rb/tree_haver/-/tree/main/checksums
1954
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
2316
1955
  [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
2317
1956
  [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
2318
1957
  [💎appraisal2]: https://github.com/appraisal-rb/appraisal2