markdown-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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.md +13 -0
- data/README.md +673 -0
- data/lib/markdown/merge/backend_support.rb +200 -0
- data/lib/markdown/merge/cleanse/block_spacing.rb +248 -0
- data/lib/markdown/merge/cleanse/code_fence_spacing.rb +294 -0
- data/lib/markdown/merge/cleanse/condensed_link_refs.rb +411 -0
- data/lib/markdown/merge/cleanse/list_marker_duplication.rb +66 -0
- data/lib/markdown/merge/cleanse/templating_corruption.rb +86 -0
- data/lib/markdown/merge/cleanse.rb +44 -0
- data/lib/markdown/merge/code_block_match_refiner.rb +111 -0
- data/lib/markdown/merge/code_block_merger.rb +742 -0
- data/lib/markdown/merge/comment_tracker.rb +42 -0
- data/lib/markdown/merge/conflict_resolver.rb +199 -0
- data/lib/markdown/merge/debug_logger.rb +26 -0
- data/lib/markdown/merge/document_problems.rb +190 -0
- data/lib/markdown/merge/file_aligner.rb +496 -0
- data/lib/markdown/merge/file_analysis.rb +689 -0
- data/lib/markdown/merge/file_analysis_base.rb +766 -0
- data/lib/markdown/merge/freeze_node.rb +93 -0
- data/lib/markdown/merge/gap_line_node.rb +142 -0
- data/lib/markdown/merge/link_definition_formatter.rb +49 -0
- data/lib/markdown/merge/link_definition_node.rb +157 -0
- data/lib/markdown/merge/link_parser.rb +421 -0
- data/lib/markdown/merge/link_reference_rehydrator.rb +320 -0
- data/lib/markdown/merge/list_match_refiner.rb +98 -0
- data/lib/markdown/merge/list_merger.rb +322 -0
- data/lib/markdown/merge/markdown_structure.rb +123 -0
- data/lib/markdown/merge/merge_result.rb +483 -0
- data/lib/markdown/merge/node_type_normalizer.rb +126 -0
- data/lib/markdown/merge/output_builder.rb +248 -0
- data/lib/markdown/merge/partial_template_merger.rb +555 -0
- data/lib/markdown/merge/preservation_support.rb +291 -0
- data/lib/markdown/merge/rspec/shared_examples/source_preserving_provider.rb +338 -0
- data/lib/markdown/merge/smart_merger.rb +269 -0
- data/lib/markdown/merge/smart_merger_base.rb +1490 -0
- data/lib/markdown/merge/source_preserving_provider.rb +814 -0
- data/lib/markdown/merge/table_match_algorithm.rb +499 -0
- data/lib/markdown/merge/table_match_refiner.rb +132 -0
- data/lib/markdown/merge/version.rb +5 -3
- data/lib/markdown/merge/whitespace_normalizer.rb +243 -0
- data/lib/markdown/merge/wrapper_support.rb +194 -0
- data/lib/markdown/merge.rb +271 -87
- data/lib/markdown-merge.rb +7 -1
- data/sig/markdown/merge.rbs +62 -0
- data.tar.gz.sig +0 -0
- metadata +289 -15
- metadata.gz.sig +0 -0
data/README.md
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
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
|
+
# ☯️ Markdown::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
|
+
Markdown::Merge provides **intelligent Markdown file merging** using tree\_haver backends. It can be used standalone or through parser-specific wrappers.
|
|
25
|
+
|
|
26
|
+
**Direct usage** (with auto-detected or specified backend):
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
require "markdown/merge"
|
|
30
|
+
|
|
31
|
+
# Auto-detect available backend (commonmarker or markly)
|
|
32
|
+
merger = Markdown::Merge::SmartMerger.new(template_content, dest_content)
|
|
33
|
+
result = merger.merge
|
|
34
|
+
|
|
35
|
+
# Or specify a backend explicitly
|
|
36
|
+
merger = Markdown::Merge::SmartMerger.new(template_content, dest_content, backend: :markly)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Via parser-specific wrappers** (for hard dependencies and backend-specific defaults):
|
|
40
|
+
|
|
41
|
+
- [commonmarker-merge][commonmarker-merge] - Uses Comrak (Rust) via Commonmarker
|
|
42
|
+
- [markly-merge][markly-merge] - Uses libcmark-gfm (C) via Markly
|
|
43
|
+
|
|
44
|
+
### Key Features
|
|
45
|
+
|
|
46
|
+
- **Multiple Backends**: Supports Commonmarker and Markly through tree\_haver's unified API
|
|
47
|
+
- **Type Normalization**: Canonical node types (`:heading`, `:paragraph`, etc.) work across all backends
|
|
48
|
+
- **Extensible**: Register custom backends via `NodeTypeNormalizer.register_backend`
|
|
49
|
+
- **Structure-Aware**: Understands headings, paragraphs, lists, code blocks, tables, and other block elements
|
|
50
|
+
- **Freeze Block Support**: Respects freeze markers (default: `markdown-merge:freeze` / `markdown-merge:unfreeze`) for template merge control - customizable to match your project's conventions
|
|
51
|
+
- **Inner-Merge Code Blocks**: Optionally merge fenced code blocks using language-specific mergers (Ruby → prism-merge, YAML → psych-merge, JSON → json-merge, TOML → toml-merge)
|
|
52
|
+
- **Table Match Refiner**: Fuzzy matching algorithm for tables with similar but not identical headers
|
|
53
|
+
- **Full Provenance**: Tracks origin of every node
|
|
54
|
+
- **Customizable**:
|
|
55
|
+
- `backend` - select `:commonmarker`, `:markly`, or `:auto`
|
|
56
|
+
- `signature_generator` - callable custom signature generators
|
|
57
|
+
- `preference` - setting of `:template`, `:destination`, or a Hash for per-node-type preferences
|
|
58
|
+
- `add_template_only_nodes` - setting to retain sections that do not exist in destination
|
|
59
|
+
- `freeze_token` - customize freeze block markers (default: `"markdown-merge"`)
|
|
60
|
+
- `inner_merge_code_blocks` - enable language-aware code block merging
|
|
61
|
+
- `match_refiner` - fuzzy matching for unmatched nodes (e.g., `TableMatchRefiner`)
|
|
62
|
+
|
|
63
|
+
### Supported Node Types
|
|
64
|
+
|
|
65
|
+
Signatures computed by default for common Markdown block elements:
|
|
66
|
+
|
|
67
|
+
| Node Type | Signature Format | Matching Behavior |
|
|
68
|
+
|---------------------|-----------------------------------------|-----------------------------------------------------|
|
|
69
|
+
| Heading | `[:heading, level, text]` | Headings match by level and text content |
|
|
70
|
+
| Paragraph | `[:paragraph, content_hash]` | Paragraphs match by content hash |
|
|
71
|
+
| List | `[:list, type, item_count]` | Lists match by type (bullet/ordered) and item count |
|
|
72
|
+
| Code Block | `[:code_block, language, content_hash]` | Code blocks match by language and content |
|
|
73
|
+
| Block Quote | `[:blockquote, content_hash]` | Block quotes match by content hash |
|
|
74
|
+
| Table | `[:table, row_count, header_hash]` | Tables match by structure and header content |
|
|
75
|
+
| HTML Block | `[:html, content_hash]` | HTML blocks match by content hash |
|
|
76
|
+
| Thematic Break | `[:hrule]` | Horizontal rules always match |
|
|
77
|
+
| Footnote Definition | `[:footnote_definition, label]` | Footnotes match by label/name |
|
|
78
|
+
|
|
79
|
+
[commonmarker-merge]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/commonmarker-merge
|
|
80
|
+
[markly-merge]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/markly-merge
|
|
81
|
+
|
|
82
|
+
## 💡 Info you can shake a stick at
|
|
83
|
+
|
|
84
|
+
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
|
|
85
|
+
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
86
|
+
| Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
|
|
87
|
+
| 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] |
|
|
88
|
+
| 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] |
|
|
89
|
+
| 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] |
|
|
90
|
+
| 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] |
|
|
91
|
+
| 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] |
|
|
92
|
+
| 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] |
|
|
93
|
+
| `...` 💖 | [![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] |
|
|
94
|
+
|
|
95
|
+
### Compatibility
|
|
96
|
+
|
|
97
|
+
Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
|
|
98
|
+
CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
|
|
99
|
+
This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
|
|
100
|
+
may be higher than the gem's runtime compatibility floor when legacy Rubies are
|
|
101
|
+
not practical for the current toolchain.
|
|
102
|
+
|
|
103
|
+
<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>
|
|
104
|
+
|
|
105
|
+
The _amazing_ test matrix is powered by the kettle-dev stack.
|
|
106
|
+
|
|
107
|
+
<details markdown="1">
|
|
108
|
+
<summary>How kettle-dev manages complexity in tests</summary>
|
|
109
|
+
|
|
110
|
+
| Gem | Source | Role | Total downloads |
|
|
111
|
+
|-----|--------|------|---------------------|
|
|
112
|
+
| [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [](https://clickgems.clickhouse.com/dashboard/appraisal2) |
|
|
113
|
+
| [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
|
|
114
|
+
| [kettle-dev](https://clickgems.clickhouse.com/dashboard/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [](https://clickgems.clickhouse.com/dashboard/kettle-dev) |
|
|
115
|
+
| [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
|
|
116
|
+
| [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 | [](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
|
|
117
|
+
| [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [](https://clickgems.clickhouse.com/dashboard/kettle-test) |
|
|
118
|
+
| [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
|
|
119
|
+
| [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
## ✨ Installation
|
|
124
|
+
|
|
125
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
126
|
+
|
|
127
|
+
```console
|
|
128
|
+
bundle add markdown-merge
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
132
|
+
|
|
133
|
+
```console
|
|
134
|
+
gem install markdown-merge
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## ⚙️ Configuration
|
|
138
|
+
|
|
139
|
+
### SmartMerger Configuration
|
|
140
|
+
|
|
141
|
+
The `SmartMerger` class is the main entry point for merging Markdown files:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
require "markdown/merge"
|
|
145
|
+
|
|
146
|
+
merger = Markdown::Merge::SmartMerger.new(
|
|
147
|
+
template_content,
|
|
148
|
+
dest_content,
|
|
149
|
+
|
|
150
|
+
# Backend selection (default: :auto)
|
|
151
|
+
# :auto - auto-detect available backend (tries commonmarker first, then markly)
|
|
152
|
+
# :commonmarker - use Commonmarker (comrak Rust parser)
|
|
153
|
+
# :markly - use Markly (cmark-gfm C library)
|
|
154
|
+
backend: :auto,
|
|
155
|
+
|
|
156
|
+
# Which version to prefer when nodes match but differ
|
|
157
|
+
# :destination (default) - keep destination content (preserves customizations)
|
|
158
|
+
# :template - use template content (applies updates)
|
|
159
|
+
preference: :destination,
|
|
160
|
+
|
|
161
|
+
# Whether to add template-only nodes to the result
|
|
162
|
+
# false (default) - only include sections that exist in destination
|
|
163
|
+
# true - include all template sections
|
|
164
|
+
add_template_only_nodes: false,
|
|
165
|
+
|
|
166
|
+
# Token for freeze block markers
|
|
167
|
+
# Default: "markdown-merge"
|
|
168
|
+
# Looks for: <!-- markdown-merge:freeze --> / <!-- markdown-merge:unfreeze -->
|
|
169
|
+
freeze_token: "markdown-merge",
|
|
170
|
+
|
|
171
|
+
# Enable inner-merge for fenced code blocks
|
|
172
|
+
# false (default) - use standard conflict resolution for code blocks
|
|
173
|
+
# true - merge code block contents using language-specific mergers
|
|
174
|
+
# CodeBlockMerger instance - use custom CodeBlockMerger
|
|
175
|
+
inner_merge_code_blocks: false,
|
|
176
|
+
|
|
177
|
+
# Match refiner for fuzzy matching of unmatched nodes
|
|
178
|
+
# nil (default) - exact matching only
|
|
179
|
+
# TableMatchRefiner.new - enable fuzzy table matching
|
|
180
|
+
match_refiner: nil,
|
|
181
|
+
|
|
182
|
+
# Custom signature generator (optional)
|
|
183
|
+
# Receives a node (wrapped with canonical merge_type), returns a signature array or nil
|
|
184
|
+
# Return the node itself to fall through to default signature
|
|
185
|
+
signature_generator: nil,
|
|
186
|
+
|
|
187
|
+
# Backend-specific options (passed through to parser)
|
|
188
|
+
# For commonmarker: options: {}
|
|
189
|
+
# For markly: flags: Markly::DEFAULT, extensions: [:table]
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Text Matching Behavior
|
|
194
|
+
|
|
195
|
+
**Important**: When matching nodes by text content (such as for anchor patterns in
|
|
196
|
+
`PartialTemplateMerger`), the `.text` method returns **plain text without markdown formatting**.
|
|
197
|
+
|
|
198
|
+
This means:
|
|
199
|
+
|
|
200
|
+
- Markdown: `` ### The `*-merge` Gem Family ``
|
|
201
|
+
- `.text` returns: `"The *-merge Gem Family\n"`
|
|
202
|
+
|
|
203
|
+
The backticks around `*-merge` are stripped because they are inline formatting, not content.
|
|
204
|
+
This is true for both Commonmarker and Markly backends.
|
|
205
|
+
|
|
206
|
+
**Anchor pattern examples**:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
# ❌ WRONG - backticks are stripped, so this won't match
|
|
210
|
+
anchor: { type: :heading, text: /`\*-merge` Gem Family/ }
|
|
211
|
+
|
|
212
|
+
# ✅ CORRECT - match the plain text content
|
|
213
|
+
anchor: { type: :heading, text: /\*-merge.*Gem Family/ }
|
|
214
|
+
|
|
215
|
+
# ✅ CORRECT - use beginning anchor for exact heading match
|
|
216
|
+
anchor: { type: :heading, text: /^The \*-merge Gem Family/ }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Other markdown formatting that is stripped from `.text`**:
|
|
220
|
+
|
|
221
|
+
- Bold: `**text**` → `text`
|
|
222
|
+
- Italic: `*text*` or `_text_` → `text`
|
|
223
|
+
- Code: `` `code` `` → `code`
|
|
224
|
+
- Links: `[text](url)` → `text`
|
|
225
|
+
- Images: `` → `alt`
|
|
226
|
+
|
|
227
|
+
**Note**: Different parsers may have other idiosyncrasies. For example:
|
|
228
|
+
|
|
229
|
+
- Trailing newlines may or may not be present
|
|
230
|
+
- Whitespace normalization may differ
|
|
231
|
+
- Entity encoding may vary
|
|
232
|
+
|
|
233
|
+
Always test your patterns against actual parsed content when building merge recipes.
|
|
234
|
+
|
|
235
|
+
### Node Type Normalization
|
|
236
|
+
|
|
237
|
+
markdown-merge normalizes node types across backends so merge rules are portable:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
# These are equivalent regardless of backend
|
|
241
|
+
# Markly's :header becomes :heading
|
|
242
|
+
# Markly's :hrule becomes :thematic_break
|
|
243
|
+
# etc.
|
|
244
|
+
|
|
245
|
+
# Register a custom backend's type mappings
|
|
246
|
+
Markdown::Merge::NodeTypeNormalizer.register_backend(:my_parser, {
|
|
247
|
+
h1: :heading,
|
|
248
|
+
h2: :heading,
|
|
249
|
+
para: :paragraph,
|
|
250
|
+
# ...
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Parser-Specific Wrappers
|
|
255
|
+
|
|
256
|
+
For convenience, parser-specific wrappers provide backend-specific defaults:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# commonmarker-merge (freeze_token: "commonmarker-merge", inner_merge_code_blocks: false)
|
|
260
|
+
require "commonmarker/merge"
|
|
261
|
+
merger = Commonmarker::Merge::SmartMerger.new(template, dest, options: {})
|
|
262
|
+
|
|
263
|
+
# markly-merge (freeze_token: "markly-merge", inner_merge_code_blocks: true)
|
|
264
|
+
require "markly/merge"
|
|
265
|
+
merger = Markly::Merge::SmartMerger.new(template, dest, flags: Markly::DEFAULT, extensions: [:table])
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Freeze Blocks
|
|
269
|
+
|
|
270
|
+
Freeze blocks protect sections from being modified during merges. They are marked
|
|
271
|
+
with HTML comments that are invisible when the Markdown is rendered:
|
|
272
|
+
|
|
273
|
+
```markdown
|
|
274
|
+
<!-- markdown-merge:freeze -->
|
|
275
|
+
|
|
276
|
+
## This Section Is Protected
|
|
277
|
+
|
|
278
|
+
Any content here will be preserved exactly as-is during merges.
|
|
279
|
+
The merge tool will not modify, replace, or remove this content.
|
|
280
|
+
|
|
281
|
+
<!-- markdown-merge:unfreeze -->
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Add an optional frozen reason to document why:
|
|
285
|
+
|
|
286
|
+
```markdown
|
|
287
|
+
<!-- markdown-merge:freeze Custom table - manually maintained -->
|
|
288
|
+
| Feature | Status |
|
|
289
|
+
|---------|--------|
|
|
290
|
+
| Custom | ✅ |
|
|
291
|
+
<!-- markdown-merge:unfreeze -->
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Inner-Merge Code Blocks
|
|
295
|
+
|
|
296
|
+
When enabled, fenced code blocks are merged using language-specific `*-merge` gems:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
merger = SomeParser::Merge::SmartMerger.new(
|
|
300
|
+
template,
|
|
301
|
+
destination,
|
|
302
|
+
inner_merge_code_blocks: true,
|
|
303
|
+
)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Supported languages and their mergers:
|
|
307
|
+
|
|
308
|
+
| Language | Fence Info | Merger |
|
|
309
|
+
| --- | --- | --- |
|
|
310
|
+
| Ruby | `ruby`, `rb` | prism-merge |
|
|
311
|
+
| YAML | `yaml`, `yml` | psych-merge |
|
|
312
|
+
| JSON | `json` | json-merge |
|
|
313
|
+
| TOML | `toml` | toml-merge |
|
|
314
|
+
|
|
315
|
+
Example with a Ruby code block:
|
|
316
|
+
|
|
317
|
+
````markdown
|
|
318
|
+
```ruby
|
|
319
|
+
|
|
320
|
+
# Template
|
|
321
|
+
|
|
322
|
+
class MyClass
|
|
323
|
+
def new_method
|
|
324
|
+
puts "from template"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
````
|
|
329
|
+
|
|
330
|
+
When merged(with:
|
|
331
|
+
|
|
332
|
+
````markdown
|
|
333
|
+
```ruby
|
|
334
|
+
|
|
335
|
+
# Destination
|
|
336
|
+
|
|
337
|
+
class MyClass
|
|
338
|
+
def existing_method
|
|
339
|
+
puts "custom"
|
|
340
|
+
end
|
|
341
|
+
end)
|
|
342
|
+
```
|
|
343
|
+
````
|
|
344
|
+
|
|
345
|
+
Result (with `inner_merge_code_blocks: true`):
|
|
346
|
+
|
|
347
|
+
````markdown
|
|
348
|
+
```ruby
|
|
349
|
+
class MyClass
|
|
350
|
+
def existing_method
|
|
351
|
+
puts "custom"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def new_method
|
|
355
|
+
puts "from template"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
```
|
|
359
|
+
````
|
|
360
|
+
|
|
361
|
+
### Table Match Refiner
|
|
362
|
+
|
|
363
|
+
When tables don't match by exact signature, the `TableMatchRefiner` uses
|
|
364
|
+
fuzzy matching to pair tables with similar structure:
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
refiner = Markdown::Merge::TableMatchRefiner.new(
|
|
368
|
+
threshold: 0.5, # Minimum similarity (0.0-1.0)
|
|
369
|
+
algorithm_options: {
|
|
370
|
+
weights: {
|
|
371
|
+
header_match: 0.25, # Header cell similarity
|
|
372
|
+
first_column: 0.20, # Row label similarity
|
|
373
|
+
row_content: 0.25, # Row content overlap
|
|
374
|
+
total_cells: 0.15, # Overall cell matching
|
|
375
|
+
position: 0.15, # Position distance
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
merger = SomeParser::Merge::SmartMerger.new(
|
|
381
|
+
template,
|
|
382
|
+
destination,
|
|
383
|
+
match_refiner: refiner,
|
|
384
|
+
)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Debug Logging
|
|
388
|
+
|
|
389
|
+
Enable debug logging to see merge decisions:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
export MARKDOWN_MERGE_DEBUG=1
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## 🔧 Basic Usage
|
|
396
|
+
|
|
397
|
+
**Note:** This gem provides base classes for implementers. End users should use
|
|
398
|
+
[commonmarker-merge][commonmarker-merge] or
|
|
399
|
+
[markly-merge][markly-merge] instead.
|
|
400
|
+
|
|
401
|
+
### For End Users
|
|
402
|
+
|
|
403
|
+
Use a parser-specific implementation:
|
|
404
|
+
|
|
405
|
+
#### Option 1: Using commonmarker-merge (Comrak/Rust)
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
require "commonmarker/merge"
|
|
409
|
+
|
|
410
|
+
template = File.read("template.md")
|
|
411
|
+
destination = File.read("destination.md")
|
|
412
|
+
|
|
413
|
+
merger = Commonmarker::Merge::SmartMerger.new(template, destination)
|
|
414
|
+
result = merger.merge
|
|
415
|
+
|
|
416
|
+
File.write("merged.md", result.content)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### Option 2: Using markly-merge (libcmark-gfm/C)
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
require "markly/merge"
|
|
423
|
+
|
|
424
|
+
template = File.read("template.md")
|
|
425
|
+
destination = File.read("destination.md")
|
|
426
|
+
|
|
427
|
+
merger = Markly::Merge::SmartMerger.new(template, destination)
|
|
428
|
+
result = merger.merge
|
|
429
|
+
|
|
430
|
+
File.write("merged.md", result.to_markdown)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### For Implementers
|
|
434
|
+
|
|
435
|
+
Creating a new parser-specific implementation:
|
|
436
|
+
|
|
437
|
+
```ruby
|
|
438
|
+
require "markdown/merge"
|
|
439
|
+
|
|
440
|
+
module MyParser
|
|
441
|
+
module Merge
|
|
442
|
+
class FileAnalysis < Markdown::Merge::FileAnalysisBase
|
|
443
|
+
def parse_document(source)
|
|
444
|
+
# Parse source and return root document node
|
|
445
|
+
MyParser.parse(source)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def next_sibling(node)
|
|
449
|
+
# Return the next sibling of a node
|
|
450
|
+
node.next_sibling
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def compute_parser_signature(node)
|
|
454
|
+
# Compute signature for parser-specific nodes
|
|
455
|
+
# Or call super for default implementation
|
|
456
|
+
super
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
class SmartMerger < Markdown::Merge::SmartMergerBase
|
|
461
|
+
def create_file_analysis(content, **options)
|
|
462
|
+
FileAnalysis.new(content, **options)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def node_to_source(node, analysis)
|
|
466
|
+
case node
|
|
467
|
+
when Markdown::Merge::FreezeNode
|
|
468
|
+
node.full_text
|
|
469
|
+
else
|
|
470
|
+
# Convert node back to source text
|
|
471
|
+
node.to_markdown
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Freeze Block Protection
|
|
480
|
+
|
|
481
|
+
Both implementations support freeze blocks for protecting customized sections:
|
|
482
|
+
|
|
483
|
+
```markdown
|
|
484
|
+
|
|
485
|
+
# My Project
|
|
486
|
+
|
|
487
|
+
## Installation
|
|
488
|
+
|
|
489
|
+
<!-- markdown-merge:freeze Custom install instructions -->
|
|
490
|
+
This installation section has been customized and will be preserved
|
|
491
|
+
during template merges, regardless of what the template contains.
|
|
492
|
+
<!-- markdown-merge:unfreeze -->
|
|
493
|
+
|
|
494
|
+
## Usage
|
|
495
|
+
|
|
496
|
+
Standard usage section - can be updated from template.
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Content between freeze markers is always preserved from the destination file,
|
|
500
|
+
even when the template has different content for that section.
|
|
501
|
+
|
|
502
|
+
## 🔐 Security
|
|
503
|
+
|
|
504
|
+
See [SECURITY.md][🔐security].
|
|
505
|
+
|
|
506
|
+
## 🤝 Contributing
|
|
507
|
+
|
|
508
|
+
If you need some ideas of where to help, you could work on adding more code coverage,
|
|
509
|
+
check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
|
|
510
|
+
|
|
511
|
+
We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
|
|
512
|
+
|
|
513
|
+
See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
|
|
514
|
+
|
|
515
|
+
## 📌 Versioning
|
|
516
|
+
|
|
517
|
+
This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
|
|
518
|
+
For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
|
|
519
|
+
|
|
520
|
+
For example:
|
|
521
|
+
|
|
522
|
+
```ruby
|
|
523
|
+
spec.add_dependency("markdown-merge", "~> 7.0")
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
<details markdown="1">
|
|
527
|
+
<summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
|
|
528
|
+
|
|
529
|
+
Dropping support for a platform can be a breaking change for affected users.
|
|
530
|
+
If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
|
|
531
|
+
|
|
532
|
+
To get a better understanding of how SemVer is intended to work over a project's lifetime,
|
|
533
|
+
read this article from the creator of SemVer:
|
|
534
|
+
|
|
535
|
+
- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
|
|
536
|
+
|
|
537
|
+
</details>
|
|
538
|
+
|
|
539
|
+
See [CHANGELOG.md][📌changelog] for a list of releases.
|
|
540
|
+
|
|
541
|
+
## 📄 License
|
|
542
|
+
|
|
543
|
+
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).
|
|
544
|
+
See [LICENSE.md][📄license] for details.
|
|
545
|
+
|
|
546
|
+
If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
|
|
547
|
+
|
|
548
|
+
[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
|
|
549
|
+
[⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
|
|
550
|
+
[⛳liberapay]: https://liberapay.com/pboling/donate
|
|
551
|
+
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
|
|
552
|
+
[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
|
|
553
|
+
[🖇sponsor]: https://github.com/sponsors/pboling
|
|
554
|
+
[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
|
|
555
|
+
[🖇kofi]: https://ko-fi.com/pboling
|
|
556
|
+
[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
|
|
557
|
+
[🖇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
|
|
558
|
+
[🖇buyme]: https://www.buymeacoffee.com/pboling
|
|
559
|
+
[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
|
|
560
|
+
[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
|
|
561
|
+
[🖇paypal]: https://www.paypal.com/paypalme/peterboling
|
|
562
|
+
[🖇floss-funding.dev]: https://floss-funding.dev
|
|
563
|
+
[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
|
|
564
|
+
[✉️discord-invite]: https://discord.gg/3qme4XHNKN
|
|
565
|
+
[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
|
|
566
|
+
[✉️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
|
|
567
|
+
[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
|
|
568
|
+
[✉️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
|
|
569
|
+
[✉️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
|
|
570
|
+
[✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
|
|
571
|
+
[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
|
|
572
|
+
[⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/markdown-merge
|
|
573
|
+
[⛳️namespace-img]: https://img.shields.io/badge/namespace-Markdown::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
|
|
574
|
+
[⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/markdown-merge
|
|
575
|
+
[⛳️name-img]: https://img.shields.io/badge/name-markdown--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
|
|
576
|
+
[⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
|
|
577
|
+
[⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
|
|
578
|
+
[🚂maint-blog]: http://www.railsbling.com/tags/markdown-merge
|
|
579
|
+
[🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
|
|
580
|
+
[🚂maint-contact]: http://www.railsbling.com/contact
|
|
581
|
+
[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
|
|
582
|
+
[💖🖇linkedin]: http://www.linkedin.com/in/peterboling
|
|
583
|
+
[💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
|
|
584
|
+
[💖✌️wellfound]: https://wellfound.com/u/peter-boling
|
|
585
|
+
[💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
|
|
586
|
+
[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
|
|
587
|
+
[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
|
|
588
|
+
[💖🐘ruby-mast]: https://ruby.social/@galtzo
|
|
589
|
+
[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
|
|
590
|
+
[💖🦋bluesky]: https://bsky.app/profile/galtzo.com
|
|
591
|
+
[💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
|
|
592
|
+
[💖🌳linktree]: https://linktr.ee/galtzo
|
|
593
|
+
[💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
|
|
594
|
+
[💖💁🏼♂️devto]: https://dev.to/galtzo
|
|
595
|
+
[💖💁🏼♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
|
|
596
|
+
[💖💁🏼♂️aboutme]: https://about.me/peter.boling
|
|
597
|
+
[💖💁🏼♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
|
|
598
|
+
[💖🧊berg]: https://codeberg.org/pboling
|
|
599
|
+
[💖🐙hub]: https://github.org/pboling
|
|
600
|
+
[💖🛖hut]: https://sr.ht/~galtzo/
|
|
601
|
+
[💖🧪lab]: https://gitlab.com/pboling
|
|
602
|
+
[👨🏼🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
|
|
603
|
+
[👨🏼🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
|
|
604
|
+
[👨🏼🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
|
|
605
|
+
[👨🏼🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
|
|
606
|
+
[🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-markdown-merge?utm_source=rubygems-markdown-merge&utm_medium=referral&utm_campaign=readme
|
|
607
|
+
[🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
|
|
608
|
+
[🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
|
|
609
|
+
[💁🏼♂️peterboling]: http://www.peterboling.com
|
|
610
|
+
[🚂railsbling]: http://www.railsbling.com
|
|
611
|
+
[📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
|
|
612
|
+
[📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/markdown-merge
|
|
613
|
+
[📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
|
|
614
|
+
[📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/markdown-merge
|
|
615
|
+
[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
|
|
616
|
+
[📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/markdown-merge
|
|
617
|
+
[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
|
|
618
|
+
[📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
|
|
619
|
+
[📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
|
|
620
|
+
[📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
|
|
621
|
+
[📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
|
|
622
|
+
[📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
|
|
623
|
+
[👽dl-rank]: https://clickgems.clickhouse.com/dashboard/markdown-merge
|
|
624
|
+
[👽dl-ranki]: https://img.shields.io/gem/dt/markdown-merge.svg
|
|
625
|
+
[👽version]: https://clickgems.clickhouse.com/dashboard/markdown-merge
|
|
626
|
+
[👽versioni]: https://img.shields.io/gem/v/markdown-merge.svg
|
|
627
|
+
[🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
|
|
628
|
+
[🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
|
|
629
|
+
[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
|
|
630
|
+
[🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
|
|
631
|
+
[🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
|
|
632
|
+
[🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
|
|
633
|
+
[🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
|
|
634
|
+
[🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
|
|
635
|
+
[🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
|
|
636
|
+
[🤝cb-donate]: https://donate.codeberg.org/
|
|
637
|
+
[🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
|
|
638
|
+
[🖐contrib-rocks]: https://contrib.rocks
|
|
639
|
+
[🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
|
|
640
|
+
[🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
|
|
641
|
+
[🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
|
|
642
|
+
[🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
|
|
643
|
+
[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
|
|
644
|
+
[📌semver]: https://semver.org/spec/v2.0.0.html
|
|
645
|
+
[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
|
|
646
|
+
[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
|
|
647
|
+
[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
|
|
648
|
+
[📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
|
|
649
|
+
[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
|
|
650
|
+
[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
|
|
651
|
+
[📌gitmoji]: https://gitmoji.dev
|
|
652
|
+
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
653
|
+
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
654
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.086-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
655
|
+
[🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
|
|
656
|
+
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
657
|
+
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
658
|
+
[📄license]: LICENSE.md
|
|
659
|
+
[📄license-ref]: LICENSE.md
|
|
660
|
+
[📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
|
|
661
|
+
[📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
|
|
662
|
+
[📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
|
|
663
|
+
[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
|
|
664
|
+
[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
|
|
665
|
+
[🚎yard-current]: http://rubydoc.info/gems/markdown-merge
|
|
666
|
+
[🚎yard-head]: https://markdown-merge.galtzo.com
|
|
667
|
+
[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
|
|
668
|
+
[💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
|
|
669
|
+
[💎rlts]: https://github.com/rubocop-lts/rubocop-lts
|
|
670
|
+
[💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
671
|
+
[💎appraisal2]: https://github.com/appraisal-rb/appraisal2
|
|
672
|
+
[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
|
|
673
|
+
[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
|