markly-merge 1.0.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.
data/README.md ADDED
@@ -0,0 +1,815 @@
1
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][✉️discord-invite] [![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
9
+
10
+ # ☯️ Markly::Merge
11
+
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]
13
+
14
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
15
+
16
+ -----
17
+
18
+ `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
+
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]
21
+
22
+ <details>
23
+ <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
24
+
25
+ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
26
+
27
+ </details>
28
+
29
+ ## 🌻 Synopsis
30
+
31
+ `markly-merge` is a thin wrapper around [markdown-merge][markdown-merge] that provides:
32
+
33
+ - **Hard dependency on Markly** - Ensures the libcmark-gfm (C) parser is installed
34
+ - **Markly-specific defaults** - Freeze token: `"markly-merge"`, `inner_merge_code_blocks: true`
35
+ - **Markly parse options** - Pass `flags:` and `extensions:` parameters
36
+ For an alternative using Comrak (Rust), see [commonmarker-merge][commonmarker-merge].
37
+
38
+ ### Features (via markdown-merge)
39
+
40
+ - **Markly-Powered**: Uses libcmark-gfm for accurate CommonMark + GFM parsing
41
+ - **Structure-Aware**: Understands headings, lists, code blocks, tables, and more
42
+ - **Intelligent**: Matches sections by heading structure and content signatures
43
+ - **Comment-Preserving**: HTML comments are preserved in context
44
+ - **Freeze Block Support**: Respects freeze markers (default: `markly-merge:freeze` / `markly-merge:unfreeze`) for merge control - customizable to match your project's conventions
45
+ - **Type Normalization**: Canonical node types work across all markdown backends
46
+ - **Full Provenance**: Tracks origin of every node
47
+ - **Inner-Merge Code Blocks**: Enabled by default - merges fenced code blocks using language-specific mergers
48
+ - **Customizable**:
49
+ - `signature_generator` - callable custom signature generators
50
+ - `preference` - setting of `:template`, `:destination`, or a Hash for per-node-type preferences
51
+ - `add_template_only_nodes` - setting to retain sections that do not exist in destination
52
+ - `freeze_token` - customize freeze block markers (default: `"markly-merge"`)
53
+ - `flags` - Markly parse flags (e.g., `Markly::FOOTNOTES`, `Markly::SMART`)
54
+ - `extensions` - GFM extensions (`:table`, `:strikethrough`, `:autolink`, `:tagfilter`, `:tasklist`)
55
+
56
+ ### Supported Node Types
57
+
58
+ | Node Type | Signature Format | Matching Behavior |
59
+ |----------------|---------------------------------|----------------------------------------------------|
60
+ | Heading | `[:heading, level, text]` | Headings match by level and text content |
61
+ | Paragraph | `[:paragraph, text_hash]` | Paragraphs match by content hash |
62
+ | List | `[:list, type, item_count]` | Lists match by type (bullet/ordered) and structure |
63
+ | List Item | `[:list_item, content_sig]` | Items match by content signature |
64
+ | Code Block | `[:code_block, language, info]` | Code blocks match by language and info string |
65
+ | Block Quote | `[:block_quote, content_sig]` | Block quotes match by content signature |
66
+ | Table | `[:table, header_sig]` | Tables match by header structure |
67
+ | HTML Block | `[:html_block, content]` | HTML blocks match by content |
68
+ | Thematic Break | `[:thematic_break]` | Horizontal rules always match |
69
+
70
+ ### Example
71
+
72
+ ```ruby
73
+ require "markly/merge"
74
+
75
+ template = File.read("template.md")
76
+ destination = File.read("destination.md")
77
+
78
+ merger = Markly::Merge::SmartMerger.new(template, destination)
79
+ result = merger.merge
80
+
81
+ File.write("merged.md", result.to_markdown)
82
+ ```
83
+
84
+ ### The `*-merge` Gem Family
85
+
86
+ 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.
87
+
88
+ | Gem | Version / CI | Language<br>/ Format | Parser Backend(s) | Description |
89
+ |------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------:|----------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
90
+ | [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) |
91
+ | [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 |
92
+ | [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 |
93
+ | [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) |
94
+ | [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 |
95
+ | [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 |
96
+ | [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 |
97
+ | [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 |
98
+ | [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) |
99
+ | [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 |
100
+ | [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 |
101
+ | [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 |
102
+ | [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 |
103
+
104
+ #### Backend Platform Compatibility
105
+
106
+ tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
107
+
108
+ | Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
109
+ |-------------------------------------------------|:---:|:-----:|:-----------:|----------------------------------------------------------------------------|
110
+ | **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
111
+ | **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
112
+ | **FFI** ([ffi][ffi]) | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
113
+ | **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
114
+ | **Prism** ([prism][prism]) | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
115
+ | **Psych** ([psych][psych]) | ✅ | ✅ | ✅ | YAML parsing, stdlib |
116
+ | **Citrus** ([citrus][citrus]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
117
+ | **Parslet** ([parslet][parslet]) | ✅ | ✅ | ✅ | Pure Ruby PEG parser, no native dependencies |
118
+ | **Commonmarker** ([commonmarker][commonmarker]) | ✅ | ❌ | ❓ | Rust extension for Markdown (via [commonmarker-merge][commonmarker-merge]) |
119
+ | **Markly** ([markly][markly]) | ✅ | ❌ | ❓ | C extension for Markdown (via [markly-merge][markly-merge]) |
120
+
121
+ **Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
122
+
123
+ **Why some backends don't work on certain platforms**:
124
+
125
+ - **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
126
+ - **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`)
127
+ - **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
128
+
129
+ **Example implementations** for the gem templating use case:
130
+
131
+ | Gem | Purpose | Description |
132
+ |--------------------------|-----------------|-----------------------------------------------|
133
+ | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
134
+ | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
135
+
136
+ [tree_haver]: https://github.com/kettle-rb/tree_haver
137
+ [ast-merge]: https://github.com/kettle-rb/ast-merge
138
+ [prism-merge]: https://github.com/kettle-rb/prism-merge
139
+ [psych-merge]: https://github.com/kettle-rb/psych-merge
140
+ [json-merge]: https://github.com/kettle-rb/json-merge
141
+ [jsonc-merge]: https://github.com/kettle-rb/jsonc-merge
142
+ [bash-merge]: https://github.com/kettle-rb/bash-merge
143
+ [rbs-merge]: https://github.com/kettle-rb/rbs-merge
144
+ [dotenv-merge]: https://github.com/kettle-rb/dotenv-merge
145
+ [toml-merge]: https://github.com/kettle-rb/toml-merge
146
+ [markdown-merge]: https://github.com/kettle-rb/markdown-merge
147
+ [markly-merge]: https://github.com/kettle-rb/markly-merge
148
+ [commonmarker-merge]: https://github.com/kettle-rb/commonmarker-merge
149
+ [kettle-dev]: https://github.com/kettle-rb/kettle-dev
150
+ [kettle-jem]: https://github.com/kettle-rb/kettle-jem
151
+ [tree_haver-gem]: https://bestgems.org/gems/tree_haver
152
+ [ast-merge-gem]: https://bestgems.org/gems/ast-merge
153
+ [prism-merge-gem]: https://bestgems.org/gems/prism-merge
154
+ [psych-merge-gem]: https://bestgems.org/gems/psych-merge
155
+ [json-merge-gem]: https://bestgems.org/gems/json-merge
156
+ [jsonc-merge-gem]: https://bestgems.org/gems/jsonc-merge
157
+ [bash-merge-gem]: https://bestgems.org/gems/bash-merge
158
+ [rbs-merge-gem]: https://bestgems.org/gems/rbs-merge
159
+ [dotenv-merge-gem]: https://bestgems.org/gems/dotenv-merge
160
+ [toml-merge-gem]: https://bestgems.org/gems/toml-merge
161
+ [markdown-merge-gem]: https://bestgems.org/gems/markdown-merge
162
+ [markly-merge-gem]: https://bestgems.org/gems/markly-merge
163
+ [commonmarker-merge-gem]: https://bestgems.org/gems/commonmarker-merge
164
+ [kettle-dev-gem]: https://bestgems.org/gems/kettle-dev
165
+ [kettle-jem-gem]: https://bestgems.org/gems/kettle-jem
166
+ [tree_haver-gem-i]: https://img.shields.io/gem/v/tree_haver.svg
167
+ [ast-merge-gem-i]: https://img.shields.io/gem/v/ast-merge.svg
168
+ [prism-merge-gem-i]: https://img.shields.io/gem/v/prism-merge.svg
169
+ [psych-merge-gem-i]: https://img.shields.io/gem/v/psych-merge.svg
170
+ [json-merge-gem-i]: https://img.shields.io/gem/v/json-merge.svg
171
+ [jsonc-merge-gem-i]: https://img.shields.io/gem/v/jsonc-merge.svg
172
+ [bash-merge-gem-i]: https://img.shields.io/gem/v/bash-merge.svg
173
+ [rbs-merge-gem-i]: https://img.shields.io/gem/v/rbs-merge.svg
174
+ [dotenv-merge-gem-i]: https://img.shields.io/gem/v/dotenv-merge.svg
175
+ [toml-merge-gem-i]: https://img.shields.io/gem/v/toml-merge.svg
176
+ [markdown-merge-gem-i]: https://img.shields.io/gem/v/markdown-merge.svg
177
+ [markly-merge-gem-i]: https://img.shields.io/gem/v/markly-merge.svg
178
+ [commonmarker-merge-gem-i]: https://img.shields.io/gem/v/commonmarker-merge.svg
179
+ [kettle-dev-gem-i]: https://img.shields.io/gem/v/kettle-dev.svg
180
+ [kettle-jem-gem-i]: https://img.shields.io/gem/v/kettle-jem.svg
181
+ [tree_haver-ci-i]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml/badge.svg
182
+ [ast-merge-ci-i]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml/badge.svg
183
+ [prism-merge-ci-i]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
184
+ [psych-merge-ci-i]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml/badge.svg
185
+ [json-merge-ci-i]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml/badge.svg
186
+ [jsonc-merge-ci-i]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml/badge.svg
187
+ [bash-merge-ci-i]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml/badge.svg
188
+ [rbs-merge-ci-i]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml/badge.svg
189
+ [dotenv-merge-ci-i]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml/badge.svg
190
+ [toml-merge-ci-i]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml/badge.svg
191
+ [markdown-merge-ci-i]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml/badge.svg
192
+ [markly-merge-ci-i]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml/badge.svg
193
+ [commonmarker-merge-ci-i]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml/badge.svg
194
+ [kettle-dev-ci-i]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml/badge.svg
195
+ [kettle-jem-ci-i]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml/badge.svg
196
+ [tree_haver-ci]: https://github.com/kettle-rb/tree_haver/actions/workflows/current.yml
197
+ [ast-merge-ci]: https://github.com/kettle-rb/ast-merge/actions/workflows/current.yml
198
+ [prism-merge-ci]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
199
+ [psych-merge-ci]: https://github.com/kettle-rb/psych-merge/actions/workflows/current.yml
200
+ [json-merge-ci]: https://github.com/kettle-rb/json-merge/actions/workflows/current.yml
201
+ [jsonc-merge-ci]: https://github.com/kettle-rb/jsonc-merge/actions/workflows/current.yml
202
+ [bash-merge-ci]: https://github.com/kettle-rb/bash-merge/actions/workflows/current.yml
203
+ [rbs-merge-ci]: https://github.com/kettle-rb/rbs-merge/actions/workflows/current.yml
204
+ [dotenv-merge-ci]: https://github.com/kettle-rb/dotenv-merge/actions/workflows/current.yml
205
+ [toml-merge-ci]: https://github.com/kettle-rb/toml-merge/actions/workflows/current.yml
206
+ [markdown-merge-ci]: https://github.com/kettle-rb/markdown-merge/actions/workflows/current.yml
207
+ [markly-merge-ci]: https://github.com/kettle-rb/markly-merge/actions/workflows/current.yml
208
+ [commonmarker-merge-ci]: https://github.com/kettle-rb/commonmarker-merge/actions/workflows/current.yml
209
+ [kettle-dev-ci]: https://github.com/kettle-rb/kettle-dev/actions/workflows/current.yml
210
+ [kettle-jem-ci]: https://github.com/kettle-rb/kettle-jem/actions/workflows/current.yml
211
+ [prism]: https://github.com/ruby/prism
212
+ [psych]: https://github.com/ruby/psych
213
+ [ffi]: https://github.com/ffi/ffi
214
+ [ts-json]: https://github.com/tree-sitter/tree-sitter-json
215
+ [ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
216
+ [ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
217
+ [ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
218
+ [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
219
+ [dotenv]: https://github.com/bkeepers/dotenv
220
+ [rbs]: https://github.com/ruby/rbs
221
+ [toml-rb]: https://github.com/emancu/toml-rb
222
+ [toml]: https://github.com/jm/toml
223
+ [markly]: https://github.com/ioquatix/markly
224
+ [commonmarker]: https://github.com/gjtorikian/commonmarker
225
+ [ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
226
+ [tree_stump]: https://github.com/joker1007/tree_stump
227
+ [jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
228
+ [citrus]: https://github.com/mjackson/citrus
229
+ [parslet]: https://github.com/kschiess/parslet
230
+
231
+ ## 💡 Info you can shake a stick at
232
+
233
+ | Tokens to Remember | [![Gem name][⛳️name-img]][👽dl-rank] [![Gem namespace][⛳️namespace-img]][📜src-gh] |
234
+ |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
235
+ | Works with MRI Ruby 4 | [![Ruby 4.0 Compat][💎ruby-c-i]][markly-merge-ci] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
236
+ | 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] |
237
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![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] |
238
+ | 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] |
239
+ | 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] |
240
+ | 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] |
241
+ | 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] |
242
+ | 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] |
243
+ | `...` 💖 | [![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] |
244
+
245
+ ### Compatibility
246
+
247
+ Compatible with MRI Ruby 3.2.0+, and concordant releases of JRuby, and TruffleRuby.
248
+
249
+ | 🚚 *Amazing* test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
250
+ |------------------------------------------------|--------------------------------------------------------|
251
+ | 👟 Check it out\! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
252
+
253
+ ### Federated DVCS
254
+
255
+ <details markdown="1">
256
+ <summary>Find this repo on federated forges (Coming soon!)</summary>
257
+
258
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
259
+ |---------------------------------------------------|-----------------------------------------------------------------------|----------------------------|---------------------------|----------------------------|---------------------------|--------------------------------|
260
+ | 🧪 [kettle-rb/markly-merge on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
261
+ | 🧊 [kettle-rb/markly-merge on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
262
+ | 🐙 [kettle-rb/markly-merge on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
263
+ | 🎮️ [Discord Server][🖼️galtzo-discord] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][🖼️galtzo-discord] | [talk][🖼️galtzo-discord] | [about][🖼️galtzo-discord] | [this][🖼️galtzo-discord] | [library\!][🖼️galtzo-discord] |
264
+
265
+ </details>
266
+
267
+ [gh-discussions]: https://github.com/kettle-rb/markly-merge/discussions
268
+
269
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/markly-merge)][🏙️entsup-tidelift]
270
+
271
+ Available as part of the Tidelift Subscription.
272
+
273
+ <details markdown="1">
274
+ <summary>Need enterprise-level guarantees?</summary>
275
+
276
+ 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.
277
+
278
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
279
+
280
+ - 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
281
+
282
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
283
+
284
+ - 💡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
285
+ Alternatively:
286
+
287
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
288
+
289
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
290
+
291
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
292
+
293
+ </details>
294
+
295
+ ## ✨ Installation
296
+
297
+ Install the gem and add to the application's Gemfile by executing:
298
+
299
+ ```console
300
+ bundle add markly-merge
301
+ ```
302
+
303
+ If bundler is not being used to manage dependencies, install the gem by executing:
304
+
305
+ ```console
306
+ gem install markly-merge
307
+ ```
308
+
309
+ ### 🔒 Secure Installation
310
+
311
+ <details markdown="1">
312
+ <summary>For Medium or High Security Installations</summary>
313
+
314
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
315
+ [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
316
+ by following the instructions below.
317
+
318
+ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
319
+
320
+ ```console
321
+ gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
322
+ ```
323
+
324
+ You only need to do that once. Then proceed to install with:
325
+
326
+ ```console
327
+ gem install markly-merge -P HighSecurity
328
+ ```
329
+
330
+ The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
331
+
332
+ If you want to up your security game full-time:
333
+
334
+ ```console
335
+ bundle config set --global trust-policy MediumSecurity
336
+ ```
337
+
338
+ `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
339
+
340
+ NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
341
+
342
+ </details>
343
+
344
+ ## ⚙️ Configuration
345
+
346
+ ```ruby
347
+ merger = Markly::Merge::SmartMerger.new(
348
+ template_content,
349
+ dest_content,
350
+ # Which version to prefer when nodes match
351
+ # :destination (default) - keep destination content
352
+ # :template - use template content
353
+ preference: :destination,
354
+
355
+ # Whether to add template-only nodes to the result
356
+ # false (default) - only include sections that exist in destination
357
+ # true - include all template sections
358
+ add_template_only_nodes: false,
359
+
360
+ # Token for freeze block markers
361
+ # Default: "markly-merge"
362
+ # Looks for: <!-- markly-merge:freeze --> / <!-- markly-merge:unfreeze -->
363
+ freeze_token: "markly-merge",
364
+
365
+ # Custom signature generator (optional)
366
+ # Receives a node, returns a signature array or nil
367
+ signature_generator: ->(node) { [:heading, node.header_level, node.string_content] if node.type == :heading },
368
+ )
369
+ ```
370
+
371
+ ## 🔧 Basic Usage
372
+
373
+ ### Simple Merge
374
+
375
+ ````ruby
376
+ require "markly/merge"
377
+
378
+ # Template defines the structure
379
+ template = <<~MD
380
+ # My Project
381
+
382
+ ## Installation
383
+
384
+ Run `gem install my-project`.
385
+
386
+ ## Usage
387
+
388
+ Basic usage instructions.
389
+
390
+ ## Contributing
391
+
392
+ Please read CONTRIBUTING.md.
393
+ MD
394
+
395
+ # Destination has customizations
396
+ destination = <<~MD
397
+ # My Project
398
+
399
+ ## Installation
400
+
401
+ Use bundler for better dependency management:
402
+
403
+ ```bash
404
+ bundle add my-project
405
+ ```
406
+
407
+ ## Custom Section
408
+
409
+ This section only exists in destination.
410
+ MD
411
+
412
+ merger = Markly::Merge::SmartMerger.new(template, destination)
413
+ result = merger.merge
414
+ puts result.to_markdown
415
+ ````
416
+
417
+ ### Using Freeze Blocks
418
+
419
+ Freeze blocks protect sections from being overwritten during merge:
420
+
421
+ ````markdown
422
+ # My Project
423
+
424
+ ## Installation
425
+
426
+ <!-- markly-merge:freeze Custom install instructions -->
427
+ This installation section has been customized and will be preserved
428
+ during template merges, regardless of what the template contains.
429
+ <!-- markly-merge:unfreeze -->
430
+
431
+ ## Usage
432
+
433
+ Standard usage section from template.
434
+ ````
435
+
436
+ Content between `<!-- markly-merge:freeze -->` and `<!-- markly-merge:unfreeze -->` markers is preserved from the destination file.
437
+
438
+ ### Adding Template-Only Sections
439
+
440
+ ```ruby
441
+ merger = Markly::Merge::SmartMerger.new(
442
+ template,
443
+ destination,
444
+ add_template_only_nodes: true,
445
+ )
446
+ result = merger.merge
447
+ # Result includes sections from template that don't exist in destination
448
+ ```
449
+
450
+ ## 🦷 FLOSS Funding
451
+
452
+ While kettle-rb tools are free software and will always be, the project would benefit immensely from some funding.
453
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
454
+
455
+ We welcome both individual and corporate sponsors! We also offer a
456
+ wide array of funding channels to account for your preferences
457
+ (although currently [Open Collective][🖇osc] is our preferred funding platform).
458
+
459
+ **If you're working in a company that's making significant use of kettle-rb tools we'd
460
+ appreciate it if you suggest to your company to become a kettle-rb sponsor.**
461
+
462
+ You can support the development of kettle-rb tools via
463
+ [GitHub Sponsors][🖇sponsor],
464
+ [Liberapay][⛳liberapay],
465
+ [PayPal][🖇paypal],
466
+ [Open Collective][🖇osc]
467
+ and [Tidelift][🏙️entsup-tidelift].
468
+
469
+ | 📍 NOTE |
470
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
471
+ | 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. |
472
+
473
+ ### Open Collective for Individuals
474
+
475
+ Support us with a monthly donation and help us continue our activities. \[[Become a backer][🖇osc-backers]\]
476
+
477
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
478
+
479
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
480
+ No backers yet. Be the first!
481
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
482
+
483
+ ### Open Collective for Organizations
484
+
485
+ Become a sponsor and get your logo on our README on GitHub with a link to your site. \[[Become a sponsor][🖇osc-sponsors]\]
486
+
487
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
488
+
489
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
490
+ No sponsors yet. Be the first!
491
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
492
+
493
+ [kettle-readme-backers]: https://github.com/kettle-rb/markly-merge/blob/main/exe/kettle-readme-backers
494
+
495
+ ### Another way to support open-source
496
+
497
+ 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).
498
+
499
+ 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`.
500
+
501
+ 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.
502
+
503
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
504
+
505
+ [![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]
506
+
507
+ ## 🔐 Security
508
+
509
+ See [SECURITY.md][🔐security].
510
+
511
+ ## 🤝 Contributing
512
+
513
+ If you need some ideas of where to help, you could work on adding more code coverage,
514
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
515
+ or use the gem and think about how it could be better.
516
+
517
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
518
+
519
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
520
+
521
+ ### 🚀 Release Instructions
522
+
523
+ See [CONTRIBUTING.md][🤝contributing].
524
+
525
+ ### Code Coverage
526
+
527
+ [![Coverage Graph][🏀codecov-g]][🏀codecov]
528
+
529
+ [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
530
+
531
+ [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
532
+
533
+ ### 🪇 Code of Conduct
534
+
535
+ Everyone interacting with this project's codebases, issue trackers,
536
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
537
+
538
+ ## 🌈 Contributors
539
+
540
+ [![Contributors][🖐contributors-img]][🖐contributors]
541
+
542
+ Made with [contributors-img][🖐contrib-rocks].
543
+
544
+ Also see GitLab Contributors: <https://gitlab.com/kettle-rb/markly-merge/-/graphs/main>
545
+
546
+ <details>
547
+ <summary>⭐️ Star History</summary>
548
+
549
+ <a href="https://star-history.com/#kettle-rb/markly-merge&Date">
550
+ <picture>
551
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kettle-rb/markly-merge&type=Date&theme=dark" />
552
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kettle-rb/markly-merge&type=Date" />
553
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kettle-rb/markly-merge&type=Date" />
554
+ </picture>
555
+ </a>
556
+
557
+ </details>
558
+
559
+ ## 📌 Versioning
560
+
561
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
562
+ Violations of this scheme should be reported as bugs.
563
+ Specifically, if a minor or patch version is released that breaks backward compatibility,
564
+ a new version should be immediately released that restores compatibility.
565
+ Breaking changes to the public API will only be introduced with new major versions.
566
+
567
+ > dropping support for a platform is both obviously and objectively a breaking change <br/>
568
+ > —Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
569
+
570
+ I understand that policy doesn't work universally ("exceptions to every rule\!"),
571
+ but it is the policy here.
572
+ As such, in many cases it is good to specify a dependency on this library using
573
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
574
+
575
+ For example:
576
+
577
+ ```ruby
578
+ spec.add_dependency("markly-merge", "~> 1.0")
579
+ ```
580
+
581
+ <details markdown="1">
582
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
583
+
584
+ SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
585
+ is a *breaking change* to an API, and for that reason the bike shedding is endless.
586
+
587
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
588
+ read this article from the creator of SemVer:
589
+
590
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
591
+
592
+ </details>
593
+
594
+ See [CHANGELOG.md][📌changelog] for a list of releases.
595
+
596
+ ## 📄 License
597
+
598
+ The gem is available as open source under the terms of
599
+ the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
600
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
601
+
602
+ ### © Copyright
603
+
604
+ <ul>
605
+ <li>
606
+ Copyright (c) 2025 Peter H. Boling, of
607
+ <a href="https://discord.gg/3qme4XHNKN">
608
+ Galtzo.com
609
+ <picture>
610
+ <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">
611
+ </picture>
612
+ </a>, and markly-merge contributors.
613
+ </li>
614
+ </ul>
615
+
616
+ ## 🤑 A request for help
617
+
618
+ Maintainers have teeth and need to pay their dentists.
619
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
620
+ I began spending most of my time building open source tools.
621
+ I'm hoping to be able to pay for my kids' health insurance this month,
622
+ so if you value the work I am doing, I need your support.
623
+ Please consider sponsoring me or the project.
624
+
625
+ To join the community or get help 👇️ Join the Discord.
626
+
627
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
628
+
629
+ To say "thanks\!" ☝️ Join the Discord or 👇️ send money.
630
+
631
+ [![Sponsor kettle-rb/markly-merge 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]
632
+
633
+ ### Please give the project a star ⭐ ♥.
634
+
635
+ Thanks for RTFM. ☺️
636
+
637
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
638
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
639
+ [⛳liberapay]: https://liberapay.com/pboling/donate
640
+ [🖇osc-all-img]: https://img.shields.io/opencollective/all/kettle-rb
641
+ [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/kettle-rb
642
+ [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/kettle-rb
643
+ [🖇osc-backers]: https://opencollective.com/kettle-rb#backer
644
+ [🖇osc-backers-i]: https://opencollective.com/kettle-rb/backers/badge.svg?style=flat
645
+ [🖇osc-sponsors]: https://opencollective.com/kettle-rb#sponsor
646
+ [🖇osc-sponsors-i]: https://opencollective.com/kettle-rb/sponsors/badge.svg?style=flat
647
+ [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/kettle-rb?style=for-the-badge
648
+ [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/kettle-rb?style=for-the-badge
649
+ [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/kettle-rb?style=for-the-badge
650
+ [🖇osc]: https://opencollective.com/kettle-rb
651
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
652
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
653
+ [🖇sponsor]: https://github.com/sponsors/pboling
654
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
655
+ [🖇polar]: https://polar.sh/pboling
656
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
657
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
658
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
659
+ [🖇patreon]: https://patreon.com/galtzo
660
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
661
+ [🖇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
662
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
663
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
664
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
665
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
666
+ [🖇floss-funding.dev]: https://floss-funding.dev
667
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
668
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
669
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
670
+ [✉️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
671
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
672
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
673
+ [⛳️gem-namespace]: https://github.com/kettle-rb/markly-merge
674
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Markly::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
675
+ [⛳️gem-name]: https://bestgems.org/gems/markly-merge
676
+ [⛳️name-img]: https://img.shields.io/badge/name-markly--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
677
+ [⛳️tag-img]: https://img.shields.io/github/tag/kettle-rb/markly-merge.svg
678
+ [⛳️tag]: http://github.com/kettle-rb/markly-merge/releases
679
+ [🚂maint-blog]: http://www.railsbling.com/tags/markly-merge
680
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
681
+ [🚂maint-contact]: http://www.railsbling.com/contact
682
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
683
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
684
+ [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
685
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
686
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
687
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
688
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
689
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
690
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
691
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
692
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
693
+ [💖🌳linktree]: https://linktr.ee/galtzo
694
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
695
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
696
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
697
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
698
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
699
+ [💖🧊berg]: https://codeberg.org/pboling
700
+ [💖🐙hub]: https://github.org/pboling
701
+ [💖🛖hut]: https://sr.ht/~galtzo/
702
+ [💖🧪lab]: https://gitlab.com/pboling
703
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
704
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
705
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
706
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
707
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-markly-merge?utm_source=rubygems-markly-merge&utm_medium=referral&utm_campaign=readme
708
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
709
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
710
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
711
+ [🚂railsbling]: http://www.railsbling.com
712
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
713
+ [📜src-gl]: https://gitlab.com/kettle-rb/markly-merge/
714
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
715
+ [📜src-cb]: https://codeberg.org/kettle-rb/markly-merge
716
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
717
+ [📜src-gh]: https://github.com/kettle-rb/markly-merge
718
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
719
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
720
+ [📜gl-wiki]: https://gitlab.com/kettle-rb/markly-merge/-/wikis/home
721
+ [📜gh-wiki]: https://github.com/kettle-rb/markly-merge/wiki
722
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
723
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
724
+ [👽dl-rank]: https://bestgems.org/gems/markly-merge
725
+ [👽dl-ranki]: https://img.shields.io/gem/rd/markly-merge.svg
726
+ [👽oss-help]: https://www.codetriage.com/kettle-rb/markly-merge
727
+ [👽oss-helpi]: https://www.codetriage.com/kettle-rb/markly-merge/badges/users.svg
728
+ [👽version]: https://bestgems.org/gems/markly-merge
729
+ [👽versioni]: https://img.shields.io/gem/v/markly-merge.svg
730
+ [🏀qlty-mnt]: https://qlty.sh/gh/kettle-rb/projects/markly-merge
731
+ [🏀qlty-mnti]: https://qlty.sh/gh/kettle-rb/projects/markly-merge/maintainability.svg
732
+ [🏀qlty-cov]: https://qlty.sh/gh/kettle-rb/projects/markly-merge/metrics/code?sort=coverageRating
733
+ [🏀qlty-covi]: https://qlty.sh/gh/kettle-rb/projects/markly-merge/coverage.svg
734
+ [🏀codecov]: https://codecov.io/gh/kettle-rb/markly-merge
735
+ [🏀codecovi]: https://codecov.io/gh/kettle-rb/markly-merge/graph/badge.svg
736
+ [🏀coveralls]: https://coveralls.io/github/kettle-rb/markly-merge?branch=main
737
+ [🏀coveralls-img]: https://coveralls.io/repos/github/kettle-rb/markly-merge/badge.svg?branch=main
738
+ [🖐codeQL]: https://github.com/kettle-rb/markly-merge/security/code-scanning
739
+ [🖐codeQL-img]: https://github.com/kettle-rb/markly-merge/actions/workflows/codeql-analysis.yml/badge.svg
740
+ [🚎2-cov-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/coverage.yml
741
+ [🚎2-cov-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/coverage.yml/badge.svg
742
+ [🚎3-hd-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/heads.yml
743
+ [🚎3-hd-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/heads.yml/badge.svg
744
+ [🚎5-st-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/style.yml
745
+ [🚎5-st-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/style.yml/badge.svg
746
+ [🚎6-s-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/supported.yml
747
+ [🚎6-s-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/supported.yml/badge.svg
748
+ [🚎9-t-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/truffle.yml
749
+ [🚎9-t-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/truffle.yml/badge.svg
750
+ [🚎11-c-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml
751
+ [🚎11-c-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/current.yml/badge.svg
752
+ [🚎12-crh-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/dep-heads.yml
753
+ [🚎12-crh-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/dep-heads.yml/badge.svg
754
+ [🚎13-🔒️-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/locked_deps.yml
755
+ [🚎13-🔒️-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/locked_deps.yml/badge.svg
756
+ [🚎14-🔓️-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/unlocked_deps.yml
757
+ [🚎14-🔓️-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/unlocked_deps.yml/badge.svg
758
+ [🚎15-🪪-wf]: https://github.com/kettle-rb/prism-merge/actions/workflows/license-eye.yml
759
+ [🚎15-🪪-wfi]: https://github.com/kettle-rb/prism-merge/actions/workflows/license-eye.yml/badge.svg
760
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
761
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
762
+ [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
763
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
764
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
765
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
766
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
767
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
768
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
769
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
770
+ [🤝gh-issues]: https://github.com/kettle-rb/markly-merge/issues
771
+ [🤝gh-pulls]: https://github.com/kettle-rb/markly-merge/pulls
772
+ [🤝gl-issues]: https://gitlab.com/kettle-rb/markly-merge/-/issues
773
+ [🤝gl-pulls]: https://gitlab.com/kettle-rb/markly-merge/-/merge_requests
774
+ [🤝cb-issues]: https://codeberg.org/kettle-rb/markly-merge/issues
775
+ [🤝cb-pulls]: https://codeberg.org/kettle-rb/markly-merge/pulls
776
+ [🤝cb-donate]: https://donate.codeberg.org/
777
+ [🤝contributing]: CONTRIBUTING.md
778
+ [🏀codecov-g]: https://codecov.io/gh/kettle-rb/markly-merge/graphs/tree.svg
779
+ [🖐contrib-rocks]: https://contrib.rocks
780
+ [🖐contributors]: https://github.com/kettle-rb/markly-merge/graphs/contributors
781
+ [🖐contributors-img]: https://contrib.rocks/image?repo=kettle-rb/markly-merge
782
+ [🚎contributors-gl]: https://gitlab.com/kettle-rb/markly-merge/-/graphs/main
783
+ [🪇conduct]: CODE_OF_CONDUCT.md
784
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
785
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
786
+ [📌semver]: https://semver.org/spec/v2.0.0.html
787
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
788
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
789
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
790
+ [📌changelog]: CHANGELOG.md
791
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
792
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
793
+ [📌gitmoji]: https://gitmoji.dev
794
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
795
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
796
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.204-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
797
+ [🔐security]: SECURITY.md
798
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
799
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
800
+ [📄license]: LICENSE.txt
801
+ [📄license-ref]: https://opensource.org/licenses/MIT
802
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
803
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
804
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
805
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
806
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
807
+ [🚎yard-current]: http://rubydoc.info/gems/markly-merge
808
+ [🚎yard-head]: https://markly-merge.galtzo.com
809
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
810
+ [💎SHA_checksums]: https://gitlab.com/kettle-rb/markly-merge/-/tree/main/checksums
811
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
812
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
813
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
814
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
815
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/