commonmarker-merge 7.0.0 → 7.1.3

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: 8c7607184951bc6d2cf8de396a1ec165bd8ecd7eb112991559c2bd8f54242ae8
4
+ data.tar.gz: 20b02bcd4b76fb65bae69740aa1321a99700f441f95581710b0c037b35b2e43a
5
5
  SHA512:
6
- metadata.gz: 3a2bef2e58c82db4e4f6ba3ae8f282d6562e06d49260d1196d7d2989901c25b116031740b3dd162b00b8e006efc5e92a30c3c4c1037e4dc34e8bf4305a9eb003
7
- data.tar.gz: d41be46c9edb3aab281558717843bc10f82805dd2c64fc1e9522ff0eecc1945def4b4cdf115260a7814d3145e4d7dc5edf3a1946b85698422147640e5b52f701
6
+ metadata.gz: 44f25a3cbe62fc23ca41813387e7f63209832ba7f180aa83bf6faadcbc74750280f537b34b803a6f07c2c82d1e174e93848d8d6386cd27710c2781ad1be66e05
7
+ data.tar.gz: ac4be6c07c6b60c52df4c60cd2e0beff78743c5d060b2e2e7b3c03a5788c3a3dc26a35f3eb447fe425916f0108319f8fb4b3550d2aa91f80b4ab0d98c6e4a613
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,523 @@
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+, 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
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
360
+
361
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
362
+
363
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
364
+
365
+ ## 📌 Versioning
366
+
367
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
368
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
369
+
370
+ For example:
371
+
372
+ ```ruby
373
+ spec.add_dependency("commonmarker-merge", "~> 7.0")
374
+ ```
375
+
376
+ <details markdown="1">
377
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
378
+
379
+ Dropping support for a platform can be a breaking change for affected users.
380
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
381
+
382
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
383
+ read this article from the creator of SemVer:
384
+
385
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
386
+
387
+ </details>
388
+
389
+ See [CHANGELOG.md][📌changelog] for a list of releases.
390
+
391
+ ## 📄 License
392
+
393
+ 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).
394
+ See [LICENSE.md][📄license] for details.
395
+
396
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
397
+
398
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
399
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
400
+ [⛳liberapay]: https://liberapay.com/pboling/donate
401
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
402
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
403
+ [🖇sponsor]: https://github.com/sponsors/pboling
404
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
405
+ [🖇kofi]: https://ko-fi.com/pboling
406
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
407
+ [🖇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
408
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
409
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
410
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
411
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
412
+ [🖇floss-funding.dev]: https://floss-funding.dev
413
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
414
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
415
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
416
+ [✉️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
417
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
418
+ [✉️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
419
+ [✉️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
420
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
421
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
422
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/commonmarker-merge
423
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Commonmarker::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
424
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
425
+ [⛳️name-img]: https://img.shields.io/badge/name-commonmarker--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
426
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
427
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
428
+ [🚂maint-blog]: http://www.railsbling.com/tags/commonmarker-merge
429
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
430
+ [🚂maint-contact]: http://www.railsbling.com/contact
431
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
432
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
433
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
434
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
435
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
436
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
437
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
438
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
439
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
440
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
441
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
442
+ [💖🌳linktree]: https://linktr.ee/galtzo
443
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
444
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
445
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
446
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
447
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
448
+ [💖🧊berg]: https://codeberg.org/pboling
449
+ [💖🐙hub]: https://github.org/pboling
450
+ [💖🛖hut]: https://sr.ht/~galtzo/
451
+ [💖🧪lab]: https://gitlab.com/pboling
452
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
453
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
454
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
455
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
456
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-commonmarker-merge?utm_source=rubygems-commonmarker-merge&utm_medium=referral&utm_campaign=readme
457
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
458
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
459
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
460
+ [🚂railsbling]: http://www.railsbling.com
461
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
462
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/commonmarker-merge
463
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
464
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/commonmarker-merge
465
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
466
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/commonmarker-merge
467
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
468
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
469
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
470
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
471
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
472
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
473
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
474
+ [👽dl-ranki]: https://img.shields.io/gem/dt/commonmarker-merge.svg
475
+ [👽version]: https://clickgems.clickhouse.com/dashboard/commonmarker-merge
476
+ [👽versioni]: https://img.shields.io/gem/v/commonmarker-merge.svg
477
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
478
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
479
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
480
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
481
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
482
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
483
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
484
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
485
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
486
+ [🤝cb-donate]: https://donate.codeberg.org/
487
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
488
+ [🖐contrib-rocks]: https://contrib.rocks
489
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
490
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
491
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
492
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
493
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
494
+ [📌semver]: https://semver.org/spec/v2.0.0.html
495
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
496
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
497
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
498
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
499
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
500
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
501
+ [📌gitmoji]: https://gitmoji.dev
502
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
503
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
504
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.180-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
505
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
506
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
507
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
508
+ [📄license]: LICENSE.md
509
+ [📄license-ref]: LICENSE.md
510
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
511
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
512
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
513
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
514
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
515
+ [🚎yard-current]: http://rubydoc.info/gems/commonmarker-merge
516
+ [🚎yard-head]: https://commonmarker-merge.galtzo.com
517
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
518
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
519
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
520
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
521
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
522
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
523
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/