commonmarker-merge 7.0.0 → 7.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1126115415afe06ef7afd9ce3beea218c9df6b4c222ca9d18d1b7f0dfc547eb
4
- data.tar.gz: f9d5e702c16169c8a65eef5f4ed30725b5437b2f8c2c2f17f215efdb633d9e46
3
+ metadata.gz: f9d1117622ed5018293fc3b630d42d468bd58433e2435cfdd149a8f7cbd95936
4
+ data.tar.gz: bd7d6f2d1a6c442137d974ef4518dcd6fb53856c92087989d4c73e6903e4bb0b
5
5
  SHA512:
6
- metadata.gz: 3a2bef2e58c82db4e4f6ba3ae8f282d6562e06d49260d1196d7d2989901c25b116031740b3dd162b00b8e006efc5e92a30c3c4c1037e4dc34e8bf4305a9eb003
7
- data.tar.gz: d41be46c9edb3aab281558717843bc10f82805dd2c64fc1e9522ff0eecc1945def4b4cdf115260a7814d3145e4d7dc5edf3a1946b85698422147640e5b52f701
6
+ metadata.gz: de289b637e50d77bd14ba12ce043aba18075c947a4e358b3669f7eda968764866970cafb96894225e2421f6001a3cd57480db80d298294dc5b9abc67ad1199a5
7
+ data.tar.gz: '09a851c44f6a5a345a682c18fbd9b2c704cd0a712650e8718b378a6245f678bc1b86257b24dcb39d001a32782fb3a46a2a9565c68d7aa7f9df284e13749cf0ac'
checksums.yaml.gz.sig CHANGED
Binary file
data/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ # License
2
+
3
+ This project is made available under the following licenses.
4
+ Choose the option that best fits your use case:
5
+
6
+ - [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md)
7
+ - [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md)
8
+
9
+ If none of the above licenses fit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
10
+
11
+ ## Copyright Notice
12
+
13
+ - Required Notice: Copyright (c) 2026 Peter H. Boling
data/README.md ADDED
@@ -0,0 +1,531 @@
1
+ <a href="https://github.com/structuredmerge"><img alt="structuredmerge Logo by GitHub" src="https://github.com/structuredmerge.png?size=192" width="14%" align="right"/></a>
2
+
3
+ # ☯️ Commonmarker::Merge
4
+
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]
6
+
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.
8
+
9
+ ---
10
+
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.
12
+
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]
14
+
15
+ <details markdown="1">
16
+ <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
17
+
18
+ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
19
+
20
+ </details>
21
+
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>
23
+
24
+ `commonmarker-merge` is a thin wrapper around [markdown-merge][markdown-merge] that provides:
25
+
26
+ - **Hard dependency on Commonmarker** - Ensures the Comrak (Rust) parser is installed
27
+ - **Commonmarker-specific defaults** - Freeze token: `"commonmarker-merge"`, `inner_merge_code_blocks: false`
28
+ - **CommonMarker parse options** - Pass options via the `options:` parameter
29
+
30
+ ### Features (via markdown-merge)
31
+
32
+ - **Smart element matching** - Headings, paragraphs, lists, code blocks, and other block elements
33
+ are matched by their structural signatures
34
+ - **Fuzzy table matching** - Tables are matched using a multi-factor scoring algorithm that considers
35
+ header similarity, first column (row labels), content overlap, and position
36
+ - **Freeze blocks** - Mark sections with HTML comments to preserve them during merges
37
+ - **Configurable merge strategies** - Choose whether template or destination wins for conflicts,
38
+ or use a Hash for per-node-type preferences with `node_splitter` (see [ast-merge][ast-merge] docs)
39
+ - **Conservative removal mode** - `remove_template_missing_nodes: true` removes top-level destination-only structural blocks while preserving standalone HTML comment-only fragments, link reference definitions, freeze blocks, and stable separator blank lines around preserved standalone fragments
40
+ - **Type normalization** - Canonical node types work across all markdown backends
41
+ - **Full CommonMarker support** - Works with all CommonMark and GitHub Flavored Markdown extensions
42
+
43
+ ### Removal Mode Scope
44
+
45
+ `remove_template_missing_nodes: true` in `commonmarker-merge` follows the shared `markdown-merge` full-document contract:
46
+
47
+ - removes **top-level destination-only structural blocks**
48
+ - preserves **standalone HTML comment-only fragments**, **link reference definitions**, and **freeze blocks**
49
+ - preserves **one separator blank line** when removed structural content collapses around a kept standalone HTML comment fragment
50
+ - does **not** yet define generic inline-comment promotion or recursive/nested section-removal semantics
51
+
52
+ Section-local `replace_mode` / partial-template behavior still follows its own conservative Markdown rules and should not be assumed to inherit the same recursive removal contract as full-document smart merge.
53
+
54
+ [ast-merge]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/ast-merge
55
+ [markdown-merge]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/markdown-merge
56
+
57
+ ## 💡 Info you can shake a stick at
58
+
59
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
60
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
61
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
62
+ | 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] |
63
+ | 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] |
64
+ | 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] |
65
+ | 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] |
66
+ | 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] |
67
+ | 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] |
68
+ | `...` 💖 | [![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] |
69
+
70
+ ### Compatibility
71
+
72
+ Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
73
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
74
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
75
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
76
+ not practical for the current toolchain.
77
+
78
+ <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>
79
+
80
+ The _amazing_ test matrix is powered by the kettle-dev stack.
81
+
82
+ <details markdown="1">
83
+ <summary>How kettle-dev manages complexity in tests</summary>
84
+
85
+ | Gem | Source | Role | Total downloads |
86
+ |-----|--------|------|---------------------|
87
+ | [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) |
88
+ | [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) |
89
+ | [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) |
90
+ | [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) |
91
+ | [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) |
92
+ | [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) |
93
+ | [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) |
94
+ | [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) |
95
+
96
+ </details>
97
+
98
+ ## ✨ Installation
99
+
100
+ Install the gem and add to the application's Gemfile by executing:
101
+
102
+ ```console
103
+ bundle add commonmarker-merge
104
+ ```
105
+
106
+ If bundler is not being used to manage dependencies, install the gem by executing:
107
+
108
+ ```console
109
+ gem install commonmarker-merge
110
+ ```
111
+
112
+ ## ⚙️ Configuration
113
+
114
+ ### Freeze Blocks
115
+
116
+ Freeze blocks prevent sections from being modified during merges. They are marked
117
+ with HTML comments that are invisible when the Markdown is rendered:
118
+
119
+ ```markdown
120
+ <!-- commonmarker-merge:freeze -->
121
+ ## This Section Is Protected
122
+
123
+ Any content here will be preserved exactly as-is during merges.
124
+ The merge tool will not modify, replace, or remove this content.
125
+
126
+ <!-- commonmarker-merge:unfreeze -->
127
+ ```
128
+
129
+ You can add an optional reason to document why a section is frozen:
130
+
131
+ ```markdown
132
+ <!-- commonmarker-merge:freeze Manual TOC - do not auto-generate -->
133
+ ## Table of Contents
134
+ - [Installation](#installation)
135
+ - [Usage](#usage)
136
+ <!-- commonmarker-merge:unfreeze -->
137
+ ```
138
+
139
+ ### Custom Freeze Token
140
+
141
+ Use a custom freeze token if you need to avoid conflicts with other tools:
142
+
143
+ ```ruby
144
+ merger = Commonmarker::Merge::SmartMerger.new(
145
+ template,
146
+ destination,
147
+ freeze_token: "my-project",
148
+ )
149
+ # Now looks for: <!-- my-project:freeze --> and <!-- my-project:unfreeze -->
150
+ ```
151
+
152
+ ### Merge Preferences
153
+
154
+ Control how conflicts between template and destination are resolved:
155
+
156
+ ```ruby
157
+ # Preserve destination customizations (default)
158
+ merger = Commonmarker::Merge::SmartMerger.new(
159
+ template,
160
+ destination,
161
+ preference: :destination,
162
+ )
163
+
164
+ # Apply template updates (overwrite destination)
165
+ merger = Commonmarker::Merge::SmartMerger.new(
166
+ template,
167
+ destination,
168
+ preference: :template,
169
+ )
170
+
171
+ # Add new sections from template that don't exist in destination
172
+ merger = Commonmarker::Merge::SmartMerger.new(
173
+ template,
174
+ destination,
175
+ add_template_only_nodes: true,
176
+ )
177
+ ```
178
+
179
+ ### Debug Logging
180
+
181
+ Enable debug logging to see merge decisions:
182
+
183
+ ```bash
184
+ export COMMONMARKER_MERGE_DEBUG=1
185
+ ```
186
+
187
+ ### Table Match Refiner
188
+
189
+ When tables don't match by exact signature (identical headers), the `TableMatchRefiner`
190
+ uses fuzzy matching to pair tables that have:
191
+
192
+ - Similar headers (e.g., "Value" vs "Values")
193
+ - Similar first column content (row labels)
194
+ - Similar overall structure and content
195
+
196
+ <!-- end list -->
197
+
198
+ ```ruby
199
+ # Enable table fuzzy matching with custom threshold
200
+ merger = Commonmarker::Merge::SmartMerger.new(
201
+ template,
202
+ destination,
203
+ match_refiners: [
204
+ Commonmarker::Merge::TableMatchRefiner.new(threshold: 0.6),
205
+ ],
206
+ )
207
+ ```
208
+
209
+ #### TableMatchAlgorithm Weights
210
+
211
+ The `TableMatchAlgorithm` uses a multi-factor scoring system with configurable weights:
212
+
213
+ | Factor | Default Weight | Description |
214
+ |----------------|----------------|--------------------------------------------------------------|
215
+ | `header_match` | 0.25 | Percentage of matching header cells (Levenshtein similarity) |
216
+ | `first_column` | 0.20 | Percentage of matching first column cells |
217
+ | `row_content` | 0.25 | Average match percentage for rows with matching first column |
218
+ | `total_cells` | 0.15 | Overall cell matching percentage |
219
+ | `position` | 0.15 | Position distance (closer tables score higher) |
220
+
221
+ ```ruby
222
+ # Custom weights for specific use cases
223
+ refiner = Commonmarker::Merge::TableMatchRefiner.new(
224
+ threshold: 0.5,
225
+ algorithm_options: {
226
+ weights: {
227
+ header_match: 0.4, # Prioritize header matching
228
+ first_column: 0.2,
229
+ row_content: 0.2,
230
+ total_cells: 0.1,
231
+ position: 0.1,
232
+ },
233
+ },
234
+ )
235
+ ```
236
+
237
+ ## 🔧 Basic Usage
238
+
239
+ ### Merging Two Markdown Files
240
+
241
+ ```ruby
242
+ require "commonmarker/merge"
243
+
244
+ template_content = File.read("template.md")
245
+ dest_content = File.read("destination.md")
246
+
247
+ merger = Commonmarker::Merge::SmartMerger.new(template_content, dest_content)
248
+ result = merger.merge
249
+
250
+ if result.success?
251
+ File.write("destination.md", result.content)
252
+ puts "Merged successfully!"
253
+ puts " - Nodes added: #{result.nodes_added}"
254
+ puts " - Nodes modified: #{result.nodes_modified}"
255
+ puts " - Frozen blocks preserved: #{result.frozen_count}"
256
+ else
257
+ puts "Merge had conflicts:"
258
+ result.conflicts.each do |conflict|
259
+ puts " - #{conflict[:location]}: #{conflict[:reason]}"
260
+ end
261
+ end
262
+ ```
263
+
264
+ ### Analyzing a Markdown File
265
+
266
+ ```ruby
267
+ require "commonmarker/merge"
268
+
269
+ source = File.read("README.md")
270
+ analysis = Commonmarker::Merge::FileAnalysis.new(source)
271
+
272
+ # Iterate over all block elements
273
+ analysis.statements.each do |node|
274
+ case node
275
+ when Commonmarker::Merge::FreezeNode
276
+ puts "Freeze block: lines #{node.start_line}-#{node.end_line}"
277
+ puts " Reason: #{node.reason}" if node.reason
278
+ else
279
+ sig = analysis.generate_signature(node)
280
+ puts "#{node.type}: #{sig.inspect}"
281
+ end
282
+ end
283
+
284
+ # Get just the freeze blocks
285
+ analysis.freeze_blocks.each do |freeze_node|
286
+ puts "Protected: #{freeze_node.content[0..50]}..."
287
+ end
288
+ ```
289
+
290
+ ### Custom Signature Generator
291
+
292
+ Override how elements are matched between files:
293
+
294
+ ```ruby
295
+ # Match headings only by level, ignoring content
296
+ custom_sig = ->(node) {
297
+ if node.respond_to?(:type) && node.type == :heading
298
+ [:heading, node.header_level] # Match any h1 to any h1, etc.
299
+ else
300
+ node # Fall through to default signature
301
+ end
302
+ }
303
+
304
+ merger = Commonmarker::Merge::SmartMerger.new(
305
+ template,
306
+ destination,
307
+ signature_generator: custom_sig,
308
+ )
309
+ ```
310
+
311
+ ### Fuzzy Table Matching
312
+
313
+ When merging documents with tables that have been renamed or restructured,
314
+ use the `TableMatchRefiner` to find the best matches:
315
+
316
+ ```ruby
317
+ require "commonmarker/merge"
318
+
319
+ template = <<~MD
320
+ # API Reference
321
+
322
+ | Endpoint | Method | Description |
323
+ |----------|--------|-------------|
324
+ | /users | GET | List users |
325
+ | /users | POST | Create user |
326
+ MD
327
+
328
+ destination = <<~MD
329
+ # API Reference
330
+
331
+ | API Endpoint | HTTP Method | Descriptions |
332
+ |--------------|-------------|--------------|
333
+ | /users | GET | List users |
334
+ | /posts | GET | List posts |
335
+ MD
336
+
337
+ # Default merge won't match the tables (headers differ)
338
+ # Use TableMatchRefiner to enable fuzzy matching
339
+ merger = Commonmarker::Merge::SmartMerger.new(
340
+ template,
341
+ destination,
342
+ match_refiners: [
343
+ Commonmarker::Merge::TableMatchRefiner.new(threshold: 0.5),
344
+ ],
345
+ )
346
+ result = merger.merge
347
+
348
+ # Tables are now matched despite header differences
349
+ # ("Endpoint" ~ "API Endpoint", "Method" ~ "HTTP Method", etc.)
350
+ ```
351
+
352
+ ## 🔐 Security
353
+
354
+ See [SECURITY.md][🔐security].
355
+
356
+ ## 🤝 Contributing
357
+
358
+ If you need some ideas of where to help, you could work on adding more code coverage,
359
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
360
+ or use the gem and think about how it could be better.
361
+
362
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
363
+
364
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
365
+
366
+ ### Code Coverage
367
+
368
+ <details markdown="1">
369
+ <summary>Coverage service badges</summary>
370
+
371
+ </details>
372
+
373
+ ## 📌 Versioning
374
+
375
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
376
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
377
+
378
+ For example:
379
+
380
+ ```ruby
381
+ spec.add_dependency("commonmarker-merge", "~> 7.0")
382
+ ```
383
+
384
+ <details markdown="1">
385
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
386
+
387
+ Dropping support for a platform can be a breaking change for affected users.
388
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
389
+
390
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
391
+ read this article from the creator of SemVer:
392
+
393
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
394
+
395
+ </details>
396
+
397
+ See [CHANGELOG.md][📌changelog] for a list of releases.
398
+
399
+ ## 📄 License
400
+
401
+ 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).
402
+ See [LICENSE.md][📄license] for details.
403
+
404
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
405
+
406
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
407
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
408
+ [⛳liberapay]: https://liberapay.com/pboling/donate
409
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
410
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
411
+ [🖇sponsor]: https://github.com/sponsors/pboling
412
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
413
+ [🖇kofi]: https://ko-fi.com/pboling
414
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
415
+ [🖇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
416
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
417
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
418
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
419
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
420
+ [🖇floss-funding.dev]: https://floss-funding.dev
421
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
422
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
423
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
424
+ [✉️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
425
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
426
+ [✉️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
427
+ [✉️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
428
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
429
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
430
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/commonmarker-merge
431
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Commonmarker::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
432
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
433
+ [⛳️name-img]: https://img.shields.io/badge/name-commonmarker--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
434
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
435
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
436
+ [🚂maint-blog]: http://www.railsbling.com/tags/commonmarker-merge
437
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
438
+ [🚂maint-contact]: http://www.railsbling.com/contact
439
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
440
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
441
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
442
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
443
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
444
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
445
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
446
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
447
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
448
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
449
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
450
+ [💖🌳linktree]: https://linktr.ee/galtzo
451
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
452
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
453
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
454
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
455
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
456
+ [💖🧊berg]: https://codeberg.org/pboling
457
+ [💖🐙hub]: https://github.org/pboling
458
+ [💖🛖hut]: https://sr.ht/~galtzo/
459
+ [💖🧪lab]: https://gitlab.com/pboling
460
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
461
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
462
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
463
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
464
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-commonmarker-merge?utm_source=rubygems-commonmarker-merge&utm_medium=referral&utm_campaign=readme
465
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
466
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
467
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
468
+ [🚂railsbling]: http://www.railsbling.com
469
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
470
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/commonmarker-merge
471
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
472
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/commonmarker-merge
473
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
474
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/commonmarker-merge
475
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
476
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
477
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
478
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
479
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
480
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
481
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
482
+ [👽dl-ranki]: https://img.shields.io/gem/dt/commonmarker-merge.svg
483
+ [👽version]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
484
+ [👽versioni]: https://img.shields.io/gem/v/commonmarker-merge.svg
485
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
486
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
487
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
488
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
489
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
490
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
491
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
492
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
493
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
494
+ [🤝cb-donate]: https://donate.codeberg.org/
495
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
496
+ [🖐contrib-rocks]: https://contrib.rocks
497
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
498
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
499
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
500
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
501
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
502
+ [📌semver]: https://semver.org/spec/v2.0.0.html
503
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
504
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
505
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
506
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
507
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
508
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
509
+ [📌gitmoji]: https://gitmoji.dev
510
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
511
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
512
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.180-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
513
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
514
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
515
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
516
+ [📄license]: LICENSE.md
517
+ [📄license-ref]: LICENSE.md
518
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
519
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
520
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
521
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
522
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
523
+ [🚎yard-current]: http://rubydoc.info/gems/commonmarker-merge
524
+ [🚎yard-head]: https://commonmarker-merge.galtzo.com
525
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
526
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
527
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
528
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
529
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
530
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
531
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/