ruby-merge 7.0.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d9fb197110a8e0a388d9daf71e144696f7db240ff5f59bf9d6cecf10518950b
4
- data.tar.gz: 01ea7a23152ec998ea17b1e4463a1ce7115e5b4463202c5ecd73ab1829a4a224
3
+ metadata.gz: 98b5dba95c151b6d6a08ae403679f78af429360fcd4b2a1e0c5ba023cfc5639e
4
+ data.tar.gz: cb85a47f7e47fbb0af358f69f8ce0503b790967f76aa04a0ad99282711cede45
5
5
  SHA512:
6
- metadata.gz: ab2fe88791d7ec23b4b9e1525a0726638e3afa01af7136e92e67b89eaa821f290187b11d96885caf44fb51adffdb12e9f6da01c25bc333e1f2358b8f84b756f2
7
- data.tar.gz: 301910a3b44cf21892fb23b1c25106ca1ac82f53481121bb921d7978bab9efa9ba574bc8c6ba574fdb51cf7166d8a550abde90e4b9efae76f0c22219e3af8531
6
+ metadata.gz: 830b7a910c65e024d2d160150c8efe01e0f2b1a9e789fde40b072d4bb45e0ffb60f1a6643f7d07c392661c8736af207da7a994651a35328660c478787316edcd
7
+ data.tar.gz: 48538e4ce4cb76543058e5a14fe94c8bc4bc3ed03f75d195c5e4d713d6e478d1e0ac4ffbf4307218a22f87918cc06ba647f4fc8037422d5fcb6a3aa89457f29a
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,297 @@
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
+ # ☯️ Ruby::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
+ `Ruby::Merge` is the canonical Ruby source family package. It merges Ruby code by source entities instead of line ranges, preserving destination-owned bodies while allowing template-only requires, constants, modules, classes, methods, and DSL entries to be added when configured.
25
+
26
+ ### Key Features
27
+
28
+ - Tree-sitter-backed Ruby parsing through `tree_haver`.
29
+ - Entity-level matching for declarations, constants, methods, and top-level DSL entries.
30
+ - Require ordering and optional template require insertion.
31
+ - Nested output and reviewed nested output APIs for Markdown/code-block workflows.
32
+ - Method move policy and parser diagnostic thresholds for safe fallback behavior.
33
+
34
+ ## 💡 Info you can shake a stick at
35
+
36
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
37
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
38
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
39
+ | 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] |
40
+ | 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] |
41
+ | 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] |
42
+ | 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] |
43
+ | 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] |
44
+ | 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] |
45
+ | `...` 💖 | [![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] |
46
+
47
+ ### Compatibility
48
+
49
+ Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
50
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
51
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
52
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
53
+ not practical for the current toolchain.
54
+
55
+ <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>
56
+
57
+ The _amazing_ test matrix is powered by the kettle-dev stack.
58
+
59
+ <details markdown="1">
60
+ <summary>How kettle-dev manages complexity in tests</summary>
61
+
62
+ | Gem | Source | Role | Total downloads |
63
+ |-----|--------|------|---------------------|
64
+ | [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) |
65
+ | [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) |
66
+ | [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) |
67
+ | [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) |
68
+ | [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) |
69
+ | [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) |
70
+ | [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) |
71
+ | [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) |
72
+
73
+ </details>
74
+
75
+ ## ✨ Installation
76
+
77
+ Install the gem and add to the application's Gemfile by executing:
78
+
79
+ ```console
80
+ bundle add ruby-merge
81
+ ```
82
+
83
+ If bundler is not being used to manage dependencies, install the gem by executing:
84
+
85
+ ```console
86
+ gem install ruby-merge
87
+ ```
88
+
89
+ ## ⚙️ Configuration
90
+
91
+ `Ruby::Merge` exposes module-level APIs so fixture runners and Git integrations can call it without constructing a merger object.
92
+
93
+ ```ruby
94
+ profile = Ruby::Merge.ruby_backend_feature_profile
95
+ context = Ruby::Merge.ruby_plan_context
96
+ ```
97
+
98
+ `merge_ruby` accepts `merge_template_requires:` and `method_move_policy:`. Leave `merge_template_requires: false` when local files own their require list; enable it when a template is authoritative for adding new dependencies.
99
+
100
+ ## 🔧 Basic Usage
101
+
102
+ ```ruby
103
+ require "ruby/merge"
104
+
105
+ result = Ruby::Merge.merge_ruby(
106
+ File.read("template.rb"),
107
+ File.read("app.rb"),
108
+ "ruby",
109
+ merge_template_requires: true,
110
+ )
111
+
112
+ abort result.fetch(:diagnostics).inspect unless result.fetch(:ok)
113
+ File.write("app.rb", result.fetch(:output))
114
+ ```
115
+
116
+ Most module-level merge methods return a Hash with `:ok`, `:diagnostics`, `:output`, and `:policies`. Check `:ok` before writing the result; diagnostics are structured for CI output and fixture conformance reports.
117
+
118
+ ## 🔐 Security
119
+
120
+ See [SECURITY.md][🔐security].
121
+
122
+ ## 🤝 Contributing
123
+
124
+ If you need some ideas of where to help, you could work on adding more code coverage,
125
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
126
+ or use the gem and think about how it could be better.
127
+
128
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
129
+
130
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
131
+
132
+ ### Code Coverage
133
+
134
+ <details markdown="1">
135
+ <summary>Coverage service badges</summary>
136
+
137
+ </details>
138
+
139
+ ## 📌 Versioning
140
+
141
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
142
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
143
+
144
+ For example:
145
+
146
+ ```ruby
147
+ spec.add_dependency("ruby-merge", "~> 7.0")
148
+ ```
149
+
150
+ <details markdown="1">
151
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
152
+
153
+ Dropping support for a platform can be a breaking change for affected users.
154
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
155
+
156
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
157
+ read this article from the creator of SemVer:
158
+
159
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
160
+
161
+ </details>
162
+
163
+ See [CHANGELOG.md][📌changelog] for a list of releases.
164
+
165
+ ## 📄 License
166
+
167
+ 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).
168
+ See [LICENSE.md][📄license] for details.
169
+
170
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
171
+
172
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
173
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
174
+ [⛳liberapay]: https://liberapay.com/pboling/donate
175
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
176
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
177
+ [🖇sponsor]: https://github.com/sponsors/pboling
178
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
179
+ [🖇kofi]: https://ko-fi.com/pboling
180
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
181
+ [🖇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
182
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
183
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
184
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
185
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
186
+ [🖇floss-funding.dev]: https://floss-funding.dev
187
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
188
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
189
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
190
+ [✉️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
191
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
192
+ [✉️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
193
+ [✉️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
194
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
195
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
196
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/ruby-merge
197
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Ruby::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
198
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/ruby-merge
199
+ [⛳️name-img]: https://img.shields.io/badge/name-ruby--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
200
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
201
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
202
+ [🚂maint-blog]: http://www.railsbling.com/tags/ruby-merge
203
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
204
+ [🚂maint-contact]: http://www.railsbling.com/contact
205
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
206
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
207
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
208
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
209
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
210
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
211
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
212
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
213
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
214
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
215
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
216
+ [💖🌳linktree]: https://linktr.ee/galtzo
217
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
218
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
219
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
220
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
221
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
222
+ [💖🧊berg]: https://codeberg.org/pboling
223
+ [💖🐙hub]: https://github.org/pboling
224
+ [💖🛖hut]: https://sr.ht/~galtzo/
225
+ [💖🧪lab]: https://gitlab.com/pboling
226
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
227
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
228
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
229
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
230
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-ruby-merge?utm_source=rubygems-ruby-merge&utm_medium=referral&utm_campaign=readme
231
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
232
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
233
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
234
+ [🚂railsbling]: http://www.railsbling.com
235
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
236
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/ruby-merge
237
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
238
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/ruby-merge
239
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
240
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/ruby-merge
241
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
242
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
243
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
244
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
245
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
246
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
247
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/ruby-merge
248
+ [👽dl-ranki]: https://img.shields.io/gem/dt/ruby-merge.svg
249
+ [👽version]: https://clickgems.clickhouse.com/dashboard/ruby-merge
250
+ [👽versioni]: https://img.shields.io/gem/v/ruby-merge.svg
251
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
252
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
253
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
254
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
255
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
256
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
257
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
258
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
259
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
260
+ [🤝cb-donate]: https://donate.codeberg.org/
261
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
262
+ [🖐contrib-rocks]: https://contrib.rocks
263
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
264
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
265
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
266
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
267
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
268
+ [📌semver]: https://semver.org/spec/v2.0.0.html
269
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
270
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
271
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
272
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
273
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
274
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
275
+ [📌gitmoji]: https://gitmoji.dev
276
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
277
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
278
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.989-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
279
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
280
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
281
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
282
+ [📄license]: LICENSE.md
283
+ [📄license-ref]: LICENSE.md
284
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
285
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
286
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
287
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
288
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
289
+ [🚎yard-current]: http://rubydoc.info/gems/ruby-merge
290
+ [🚎yard-head]: https://ruby-merge.galtzo.com
291
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
292
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
293
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
294
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
295
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
296
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
297
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Parser-neutral naming policy for a block-local receiver.
6
+ module BlockBindingSupport
7
+ module_function
8
+
9
+ def effective_receiver(receiver, block_var, placeholder: :__block_binding__)
10
+ block_var && receiver == block_var ? placeholder : receiver
11
+ end
12
+
13
+ def preferred_block_var(template_var, destination_var)
14
+ template_var if template_var && destination_var && template_var != destination_var
15
+ end
16
+
17
+ def merged_block_var(var, preferred_var)
18
+ preferred_var || var
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Detects Ruby comment block directive pairs in source lines.
6
+ #
7
+ # This is Ruby-specific substrate behavior shared by Ruby parser providers.
8
+ # Parser-specific gems may consume the spans and project them into their own
9
+ # node types, but directive token semantics should live here.
10
+ class BlockDirectiveDetector
11
+ Span = Struct.new(:kind, :start_line, :end_line, :open_marker, :close_marker, keyword_init: true)
12
+
13
+ NOCOV_TOKEN = ':nocov:'
14
+ SIMPLECOV_DISABLE_RE = /\A\s*#\s*simplecov\s*:\s*disable\b/i
15
+ SIMPLECOV_ENABLE_RE = /\A\s*#\s*simplecov\s*:\s*enable\b/i
16
+ DIRECTIVE_CONTENT_RE = /\A(?::nocov:|[\w-]+:(?:freeze|unfreeze))\z/i
17
+
18
+ class << self
19
+ def coverage_directive_line?(line, nocov_token: NOCOV_TOKEN)
20
+ stripped = line.to_s.chomp
21
+ stripped.match?(simplecov_disable_re) ||
22
+ stripped.match?(simplecov_enable_re) ||
23
+ stripped.match?(nocov_re(nocov_token))
24
+ end
25
+
26
+ def directive_content?(content)
27
+ DIRECTIVE_CONTENT_RE.match?(content.to_s.strip) ||
28
+ coverage_directive_line?("# #{content}")
29
+ end
30
+
31
+ def simplecov_disable_re
32
+ SIMPLECOV_DISABLE_RE
33
+ end
34
+
35
+ def simplecov_enable_re
36
+ SIMPLECOV_ENABLE_RE
37
+ end
38
+
39
+ def nocov_re(nocov_token = NOCOV_TOKEN)
40
+ /\A\s*#\s?#{Regexp.escape(nocov_token)}\s*\z/i
41
+ end
42
+ end
43
+
44
+ def initialize(lines, freeze_token: nil, nocov_token: NOCOV_TOKEN, source_label: nil)
45
+ @lines = lines
46
+ @freeze_token = freeze_token
47
+ @nocov_token = nocov_token
48
+ @source_label = source_label
49
+ end
50
+
51
+ def detect_spans
52
+ raw_spans = []
53
+ raw_spans.concat(detect_freeze_spans) if @freeze_token
54
+ raw_spans.concat(detect_nocov_spans)
55
+
56
+ validate_no_crossing(raw_spans.sort_by(&:start_line))
57
+ end
58
+
59
+ private
60
+
61
+ def detector_name
62
+ 'ruby-merge'
63
+ end
64
+
65
+ def directive_error_class
66
+ Ast::Merge::Error
67
+ end
68
+
69
+ def warn_prefix
70
+ if @source_label
71
+ "[#{detector_name}] BlockDirectiveDetector (#{@source_label}):"
72
+ else
73
+ "[#{detector_name}] BlockDirectiveDetector:"
74
+ end
75
+ end
76
+
77
+ def report_unbalanced(message)
78
+ raise directive_error_class, "#{warn_prefix} #{message}" if @source_label
79
+
80
+ warn("#{warn_prefix} #{message} - ignoring")
81
+ end
82
+
83
+ def detect_freeze_spans
84
+ freeze_pat = /\A\s*#\s?#{Regexp.escape(@freeze_token)}:freeze\b/i
85
+ unfreeze_pat = /\A\s*#\s?#{Regexp.escape(@freeze_token)}:unfreeze\b/i
86
+
87
+ spans = []
88
+ stack = []
89
+
90
+ @lines.each_with_index do |line, index|
91
+ line_num = index + 1
92
+ stripped = line.to_s.chomp
93
+ if stripped.match?(freeze_pat)
94
+ stack.push({ start_line: line_num, open_marker: stripped })
95
+ elsif stripped.match?(unfreeze_pat)
96
+ if (open = stack.pop)
97
+ spans << Span.new(
98
+ kind: :freeze,
99
+ start_line: open[:start_line],
100
+ end_line: line_num,
101
+ open_marker: open[:open_marker],
102
+ close_marker: stripped
103
+ )
104
+ else
105
+ report_unbalanced("unmatched #{@freeze_token}:unfreeze at line #{line_num}")
106
+ end
107
+ end
108
+ end
109
+
110
+ stack.each do |open|
111
+ report_unbalanced("unclosed #{@freeze_token}:freeze at line #{open[:start_line]}")
112
+ end
113
+
114
+ spans
115
+ end
116
+
117
+ def detect_nocov_spans
118
+ return [] unless @nocov_token
119
+
120
+ nocov_pat = self.class.nocov_re(@nocov_token)
121
+
122
+ spans = []
123
+ stack = []
124
+
125
+ @lines.each_with_index do |line, index|
126
+ line_num = index + 1
127
+ stripped = line.to_s.chomp
128
+ if stripped.match?(self.class.simplecov_disable_re)
129
+ stack.push({ start_line: line_num, open_marker: stripped })
130
+ elsif stripped.match?(self.class.simplecov_enable_re)
131
+ if (open = stack.pop)
132
+ spans << Span.new(
133
+ kind: :nocov,
134
+ start_line: open[:start_line],
135
+ end_line: line_num,
136
+ open_marker: open[:open_marker],
137
+ close_marker: stripped
138
+ )
139
+ else
140
+ report_unbalanced("unmatched simplecov:enable at line #{line_num}")
141
+ end
142
+ elsif stripped.match?(nocov_pat)
143
+ if stack.empty?
144
+ stack.push({ start_line: line_num, open_marker: stripped })
145
+ else
146
+ open = stack.pop
147
+ spans << Span.new(
148
+ kind: :nocov,
149
+ start_line: open[:start_line],
150
+ end_line: line_num,
151
+ open_marker: open[:open_marker],
152
+ close_marker: stripped
153
+ )
154
+ end
155
+ end
156
+ end
157
+
158
+ stack.each do |open|
159
+ report_unbalanced("unclosed coverage directive at line #{open[:start_line]}")
160
+ end
161
+
162
+ spans
163
+ end
164
+
165
+ def validate_no_crossing(spans)
166
+ valid = []
167
+ invalid_indices = Set.new
168
+
169
+ spans.each_with_index do |first, first_index|
170
+ next if invalid_indices.include?(first_index)
171
+
172
+ crossing = false
173
+ spans.each_with_index do |second, second_index|
174
+ next if first_index == second_index || invalid_indices.include?(second_index)
175
+ next unless crossing_spans?(first, second)
176
+
177
+ report_unbalanced("offset-overlapping #{first.kind} block " \
178
+ "(lines #{first.start_line}..#{first.end_line}) and #{second.kind} block " \
179
+ "(lines #{second.start_line}..#{second.end_line}) - both treated as plain comments")
180
+ invalid_indices.add(first_index)
181
+ invalid_indices.add(second_index)
182
+ crossing = true
183
+ break
184
+ end
185
+
186
+ valid << first unless crossing
187
+ end
188
+
189
+ valid
190
+ end
191
+
192
+ def crossing_spans?(first, second)
193
+ (first.start_line < second.start_line && first.end_line > second.start_line && first.end_line < second.end_line) ||
194
+ (second.start_line < first.start_line && second.end_line > first.start_line && second.end_line < first.end_line)
195
+ end
196
+
197
+ def top_level_spans_only(spans)
198
+ spans.reject do |span|
199
+ spans.any? do |other|
200
+ other != span &&
201
+ other.start_line <= span.start_line &&
202
+ other.end_line >= span.end_line
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Ruby-specific doc-comment semantics shared by Ruby parser providers.
6
+ module DocCommentSupport
7
+ TAG_PREFIX = /\A@[a-z_]+\b/
8
+ EXAMPLE_TAG = /\A@example\b(?<rest>.*)\z/
9
+ MAGIC_COMMENT_PREFIXES = %w[
10
+ coding
11
+ encoding
12
+ frozen_string_literal
13
+ shareable_constant_value
14
+ typed
15
+ warn_indent
16
+ ].freeze
17
+
18
+ module_function
19
+
20
+ def normalize_comment_content(raw)
21
+ raw.to_s.sub(/\A\s*#\s?/, '').strip
22
+ end
23
+
24
+ def comment_prefix_for(raw)
25
+ raw.to_s[/\A\s*#\s*/] || '# '
26
+ end
27
+
28
+ def doc_comment_content?(raw, magic_comment: false)
29
+ content = normalize_comment_content(raw)
30
+ return false if content.empty?
31
+ return false if BlockDirectiveDetector.directive_content?(content)
32
+ return false if magic_comment || magic_comment_content?(content)
33
+
34
+ true
35
+ end
36
+
37
+ def magic_comment_content?(content)
38
+ MAGIC_COMMENT_PREFIXES.any? { |prefix| content.to_s.start_with?("#{prefix}:") }
39
+ end
40
+
41
+ def declared_example_language(rest)
42
+ match = rest.to_s.strip.match(/\A\[(?<language>[^\]]+)\]/)
43
+ normalize_language(match && match[:language])
44
+ end
45
+
46
+ def declared_example_language_for_tag(content)
47
+ match = EXAMPLE_TAG.match(content.to_s)
48
+ return unless match
49
+
50
+ declared_example_language(match[:rest])
51
+ end
52
+
53
+ def normalize_language(language)
54
+ return if language.nil?
55
+
56
+ normalized = language.to_s.strip.downcase.tr('-', '_')
57
+ return if normalized.empty?
58
+
59
+ normalized
60
+ end
61
+
62
+ def next_tag_index(normalized_lines, start_index)
63
+ normalized_lines.each_with_index do |content, index|
64
+ next if index < start_index
65
+
66
+ return index if TAG_PREFIX.match?(content)
67
+ end
68
+ nil
69
+ end
70
+
71
+ def example_blocks(entries)
72
+ normalized = entries.map { |entry| normalize_comment_content(entry[:raw]) }
73
+ normalized.each_with_index.filter_map do |content, tag_index|
74
+ match = EXAMPLE_TAG.match(content)
75
+ next unless match
76
+
77
+ body_start_index = tag_index + 1
78
+ body_end_index = next_tag_index(normalized, body_start_index) || normalized.length
79
+ next if body_start_index >= body_end_index
80
+
81
+ body_entries = entries[body_start_index...body_end_index]
82
+ next if body_entries.nil? || body_entries.empty?
83
+
84
+ {
85
+ tag_index: tag_index,
86
+ tag_line: entries[tag_index][:line],
87
+ tag_text: normalized[tag_index],
88
+ body_start_index: body_start_index,
89
+ body_end_index: body_end_index,
90
+ body_entries: body_entries,
91
+ declared_language: declared_example_language(match[:rest])
92
+ }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end