config-merge 7.1.13

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9d88bcf213ac283e2ead497bcae0ff73e001e4f49d75d6787fd15792c40f0c7d
4
+ data.tar.gz: fa8eab1bdf98f112e74994e9252e1019d149225e7cef42080edc7624ef7d5e51
5
+ SHA512:
6
+ metadata.gz: d0dd2e1b2d1c81139004406c7a1fa53cbdfc72816a9e8aca1e02b456eb88e0f81cdeebdfec9dff1f9b6167422a7e38477dc87ff723c15c8a804d6fb87ebdec74
7
+ data.tar.gz: 214e8bdffc1931f119f311f4c8f7517d05e434f71e6d05a2317b92dcec29b76e90a7197b470c6ec3b34d9923860010ba11d021c3e4ec7de05da15a910d636541
checksums.yaml.gz.sig ADDED
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,307 @@
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
+ # 🔧 Config::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
+ config-merge applies declared merge policies to line-oriented configuration files. It compares base, destination, and incoming versions by flag and argument, combining independent edits while reporting conflicting edits to singleton options. Policies are validated YAML and reference named normalizers; policy files do not execute Ruby code.
25
+
26
+ ## 💡 Info you can shake a stick at
27
+
28
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
29
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
30
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
31
+ | 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] |
32
+ | 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] |
33
+ | 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] |
34
+ | 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] |
35
+ | 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] |
36
+ | 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] |
37
+ | `...` 💖 | [![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] |
38
+
39
+ ### Compatibility
40
+
41
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
42
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
43
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
44
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
45
+ not practical for the current toolchain.
46
+
47
+ <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>
48
+
49
+ The _amazing_ test matrix is powered by the kettle-dev stack.
50
+
51
+ <details markdown="1">
52
+ <summary>How kettle-dev manages complexity in tests</summary>
53
+
54
+ | Gem | Source | Role | Total downloads |
55
+ |-----|--------|------|---------------------|
56
+ | [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) |
57
+ | [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) |
58
+ | [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) |
59
+ | [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) |
60
+ | [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) |
61
+ | [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) |
62
+ | [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) |
63
+ | [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) |
64
+
65
+ </details>
66
+
67
+ ## ✨ Installation
68
+
69
+ Install the gem and add to the application's Gemfile by executing:
70
+
71
+ ```console
72
+ bundle add config-merge
73
+ ```
74
+
75
+ If bundler is not being used to manage dependencies, install the gem by executing:
76
+
77
+ ```console
78
+ gem install config-merge
79
+ ```
80
+
81
+ ## ⚙️ Configuration
82
+
83
+ A policy declares how a flag is identified, whether it can repeat, and how a conflict is handled. The bundled RSpec policy treats `--require` as a singleton option owned by the destination and merges repeatable options such as `--env` as separate records.
84
+
85
+ ```yaml
86
+ name: rspec
87
+ format: flag_file
88
+ defaults:
89
+ cardinality: repeatable
90
+ on_conflict: conflict
91
+ flags:
92
+ --require:
93
+ cardinality: single
94
+ on_conflict: ours
95
+ --env:
96
+ cardinality: repeatable
97
+ on_conflict: union
98
+ ```
99
+
100
+ Undeclared flags default to unresolved conflicts; repeatable flags can explicitly opt into union. Normalizers affect comparison only; rendering retains the option values from the selected source records. YAML is loaded with safe parsing, and unknown fields or normalizer names are rejected.
101
+
102
+ ## 🔧 Basic Usage
103
+
104
+ ```ruby
105
+ require "config/merge"
106
+
107
+ policy = Config::Merge::BuiltInPolicies.fetch(:rspec)
108
+ result = Config::Merge::FlagFile::ThreeWayMerger.new(
109
+ base: File.read(".rspec.base"),
110
+ ours: File.read(".rspec"),
111
+ theirs: File.read(".rspec.incoming"),
112
+ policy: policy
113
+ ).merge
114
+
115
+ if result.success?
116
+ File.write(".rspec", result.output)
117
+ else
118
+ warn "Unresolved option conflicts: #{result.conflicts.map(&:flag).join(', ')}"
119
+ end
120
+ ```
121
+
122
+ The RSpec policy preserves a destination-specific `--require rails_helper` while accepting independent template additions. `result.output` is `nil` when any conflict remains; `result.partial_output` contains the non-conflicting records for inspection.
123
+
124
+ ProviderAdapter also delegates structural formats to existing ast-merge providers. Install the format provider separately; this example uses psych-merge:
125
+
126
+ require "config/merge"
127
+ require "psych/merge"
128
+
129
+ adapter = Config::Merge::ProviderAdapter.new(
130
+ family: :yaml, dialect: :yaml, backend: :psych, profile_id: :source_preserving
131
+ )
132
+ result = adapter.merge3(base: base_yaml, ours: destination_yaml, theirs: template_yaml)
133
+
134
+ It returns the provider result unchanged, including diagnostics and source-preservation verification.
135
+
136
+ ## 🔐 Security
137
+
138
+ See [SECURITY.md][🔐security].
139
+
140
+ ## 🤝 Contributing
141
+
142
+ If you need some ideas of where to help, you could work on adding more code coverage,
143
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
144
+
145
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
146
+
147
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
148
+
149
+ ## 📌 Versioning
150
+
151
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
152
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
153
+
154
+ For example:
155
+
156
+ ```ruby
157
+ spec.add_dependency("config-merge", "~> 7.0")
158
+ ```
159
+
160
+ <details markdown="1">
161
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
162
+
163
+ Dropping support for a platform can be a breaking change for affected users.
164
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
165
+
166
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
167
+ read this article from the creator of SemVer:
168
+
169
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
170
+
171
+ </details>
172
+
173
+ See [CHANGELOG.md][📌changelog] for a list of releases.
174
+
175
+ ## 📄 License
176
+
177
+ 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).
178
+ See [LICENSE.md][📄license] for details.
179
+
180
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
181
+
182
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
183
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
184
+ [⛳liberapay]: https://liberapay.com/pboling/donate
185
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
186
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
187
+ [🖇sponsor]: https://github.com/sponsors/pboling
188
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
189
+ [🖇kofi]: https://ko-fi.com/pboling
190
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
191
+ [🖇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
192
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
193
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
194
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
195
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
196
+ [🖇floss-funding.dev]: https://floss-funding.dev
197
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
198
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
199
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
200
+ [✉️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
201
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
202
+ [✉️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
203
+ [✉️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
204
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
205
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
206
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/config-merge
207
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Config::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
208
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/config-merge
209
+ [⛳️name-img]: https://img.shields.io/badge/name-config--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
210
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
211
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
212
+ [🚂maint-blog]: http://www.railsbling.com/tags/config-merge
213
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
214
+ [🚂maint-contact]: http://www.railsbling.com/contact
215
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
216
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
217
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
218
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
219
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
220
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
221
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
222
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
223
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
224
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
225
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
226
+ [💖🌳linktree]: https://linktr.ee/galtzo
227
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
228
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
229
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
230
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
231
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
232
+ [💖🧊berg]: https://codeberg.org/pboling
233
+ [💖🐙hub]: https://github.org/pboling
234
+ [💖🛖hut]: https://sr.ht/~galtzo/
235
+ [💖🧪lab]: https://gitlab.com/pboling
236
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
237
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
238
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
239
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
240
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-config-merge?utm_source=rubygems-config-merge&utm_medium=referral&utm_campaign=readme
241
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
242
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
243
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
244
+ [🚂railsbling]: http://www.railsbling.com
245
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
246
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/config-merge
247
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
248
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/config-merge
249
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
250
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/config-merge
251
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
252
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
253
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
254
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
255
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
256
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
257
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/config-merge
258
+ [👽dl-ranki]: https://img.shields.io/gem/dt/config-merge.svg
259
+ [👽version]: https://clickgems.clickhouse.com/dashboard/config-merge
260
+ [👽versioni]: https://img.shields.io/gem/v/config-merge.svg
261
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
262
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
263
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
264
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
265
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
266
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
267
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
268
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
269
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
270
+ [🤝cb-donate]: https://donate.codeberg.org/
271
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
272
+ [🖐contrib-rocks]: https://contrib.rocks
273
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
274
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
275
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
276
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
277
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
278
+ [📌semver]: https://semver.org/spec/v2.0.0.html
279
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
280
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
281
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
282
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
283
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
284
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
285
+ [📌gitmoji]: https://gitmoji.dev
286
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
287
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
288
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.053-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
289
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
290
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
291
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
292
+ [📄license]: LICENSE.md
293
+ [📄license-ref]: LICENSE.md
294
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
295
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
296
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
297
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
298
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
299
+ [🚎yard-current]: http://rubydoc.info/gems/config-merge
300
+ [🚎yard-head]: https://config-merge.galtzo.com
301
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
302
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
303
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
304
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
305
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
306
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
307
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "policy"
4
+
5
+ module Config
6
+ module Merge
7
+ # Bundled policy catalog for common configuration formats.
8
+ module BuiltInPolicies
9
+ POLICY_DIRECTORY = File.join(__dir__, "policies").freeze
10
+
11
+ module_function
12
+
13
+ def names
14
+ Dir.glob(File.join(POLICY_DIRECTORY, "*.yml")).map do |path|
15
+ File.basename(path, ".yml").to_sym
16
+ end.sort.freeze
17
+ end
18
+
19
+ def fetch(name)
20
+ key = name.to_s
21
+ raise ArgumentError, "Unknown built-in policy: #{name}" unless key.match?(/\A[a-z0-9_-]+\z/)
22
+
23
+ path = File.join(POLICY_DIRECTORY, "#{key}.yml")
24
+ raise ArgumentError, "Unknown built-in policy: #{name}" unless File.file?(path)
25
+
26
+ Policy.load(path)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Config
4
+ module Merge
5
+ module FlagFile
6
+ # One parsed flag and its optional argument.
7
+ Option = Struct.new(:flag, :value, :raw, :line_number, :position, :binding, keyword_init: true) do
8
+ def initialize(**attributes)
9
+ attributes[:position] ||= 0
10
+ attributes[:binding] ||= :none
11
+ super(**attributes)
12
+ freeze
13
+ end
14
+
15
+ def to_s
16
+ raw
17
+ end
18
+ end
19
+
20
+ # Parsed flag-file contents. Opaque lines are comments, blank lines, and positional arguments.
21
+ Document = Struct.new(:options, :opaque_lines, :trailing_newline, keyword_init: true) do
22
+ def initialize(options:, opaque_lines:, trailing_newline: false)
23
+ super(options: options.freeze, opaque_lines: opaque_lines.freeze, trailing_newline: trailing_newline == true)
24
+ freeze
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require_relative "option"
5
+
6
+ module Config
7
+ module Merge
8
+ module FlagFile
9
+ # Parses shell-style option records, one or more per line.
10
+ class Parser
11
+ class ParseError < ArgumentError; end
12
+
13
+ def parse(source)
14
+ options = []
15
+ opaque_lines = []
16
+ source.to_s.each_line.with_index(1) do |raw_line, line_number|
17
+ parsed_options, opaque = parse_line(raw_line.chomp, line_number)
18
+ options.concat(parsed_options)
19
+ opaque_lines.concat(opaque)
20
+ end
21
+
22
+ Document.new(options: options, opaque_lines: opaque_lines, trailing_newline: source.to_s.end_with?("\n"))
23
+ end
24
+
25
+ private
26
+
27
+ def parse_line(line, line_number)
28
+ return [[], [[line_number, 0, line].freeze]] if line.strip.empty? || line.strip.start_with?("#")
29
+
30
+ parse_options(line, line_number)
31
+ rescue ArgumentError => e
32
+ raise ParseError, "Invalid option syntax on line #{line_number}: #{e.message}"
33
+ end
34
+
35
+ def parse_options(line, line_number)
36
+ tokens = Shellwords.shellsplit(line)
37
+ options, opaque = consume_tokens(tokens, line_number)
38
+ [options, opaque]
39
+ end
40
+
41
+ def consume_tokens(tokens, line_number)
42
+ options = []
43
+ opaque = []
44
+ index = 0
45
+ while index < tokens.length
46
+ option, consumed = parse_option(tokens, index, line_number)
47
+ option ? options << option : opaque << [line_number, index, Shellwords.escape(tokens[index])].freeze
48
+ index += consumed
49
+ end
50
+ [options, opaque]
51
+ end
52
+
53
+ def parse_option(tokens, index, line_number)
54
+ token = tokens[index]
55
+ return [nil, 1] unless token.start_with?("-")
56
+
57
+ flag, value, binding = parse_flag_token(token)
58
+ value, binding, consumed = parse_argument(tokens, index, value, binding)
59
+ [build_option(flag, value, line_number, index, binding), consumed]
60
+ end
61
+
62
+ def build_option(flag, value, line_number, index, binding)
63
+ Option.new(
64
+ flag: flag,
65
+ value: value,
66
+ raw: render(flag, value, binding: binding),
67
+ line_number: line_number,
68
+ position: index,
69
+ binding: binding
70
+ )
71
+ end
72
+
73
+ def parse_flag_token(token)
74
+ flag, separator, value = token.partition("=")
75
+ [flag, separator.empty? ? nil : value, separator.empty? ? :none : :equals]
76
+ end
77
+
78
+ def parse_argument(tokens, index, value, binding)
79
+ next_token = tokens[index + 1]
80
+ return [value, binding, 1] unless value.nil? && next_token && !next_token.start_with?("-")
81
+
82
+ [next_token, :separate, 2]
83
+ end
84
+
85
+ def render(flag, value, binding:)
86
+ rendered_flag = Shellwords.escape(flag)
87
+ return rendered_flag if value.nil?
88
+
89
+ rendered_value = escape(value)
90
+ return "#{rendered_flag}=#{rendered_value}" if binding == :equals || value.start_with?("-")
91
+
92
+ "#{rendered_flag} #{rendered_value}"
93
+ end
94
+
95
+ def escape(value)
96
+ return "''" if value.empty?
97
+ return Shellwords.escape(value) if value.match?(/[\s\\"']/)
98
+
99
+ value
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Config
4
+ module Merge
5
+ module FlagFile
6
+ Conflict = Struct.new(:flag, :base, :ours, :theirs, keyword_init: true) do
7
+ def initialize(flag:, base:, ours:, theirs:)
8
+ super(flag: flag, base: base.freeze, ours: ours.freeze, theirs: theirs.freeze)
9
+ freeze
10
+ end
11
+ end
12
+
13
+ # Result of a semantic flag-file merge.
14
+ class Result
15
+ attr_reader :output, :conflicts, :partial_output
16
+
17
+ def initialize(output:, conflicts:, partial_output:)
18
+ @output = output
19
+ @conflicts = conflicts.freeze
20
+ @partial_output = partial_output
21
+ freeze
22
+ end
23
+
24
+ def clean?
25
+ conflicts.empty?
26
+ end
27
+
28
+ alias success? clean?
29
+ end
30
+ end
31
+ end
32
+ end