rust-merge 7.0.0 → 7.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c38459e1d39971cade4fe28316b85c476ccdd6ef327ce876f9df133c48155a00
4
- data.tar.gz: 44034fa2d21db2abc0fe6189e29123d44ccdea2ba36f9b9840c0e6dacd31130d
3
+ metadata.gz: aef016d95324b777f2300aa31db682b1cb717ea3ade223d5f44058e0f8c42428
4
+ data.tar.gz: 2a97cb609a0a13efa790fec5a8a7bd76feff180babd2932c7751ed0d0bed3209
5
5
  SHA512:
6
- metadata.gz: 56383574edb836f7149e9b24f4c09d084c477b6cb1e8c42e1e6873dd0369fde3201ea4dddb7c8834101564f4a7bdb433e1fa8c34be74554b758c5e4f03e34cac
7
- data.tar.gz: 75b5778bc46334c29f63cecc88b735a9e436f2678cf7a2f0ce086cab01f7a0ee15258596eaff701c36f686f8dc2bdea5b2ff79303db0278a68730487e5894b12
6
+ metadata.gz: 2fc3aa2c3a852d2bd196b2a963fda2b3f3a890e4cf6a27270b4311ab0bb70c7ff7d6357cd9ba8ed6b7859bd9b290d35bc0bfd2c2b43471955b602127b5bd74fe
7
+ data.tar.gz: 7ee67c2c39b1fad125389987ab0341de1f0fdf0e95d9cf3e4ed9627cc84c9f7d37ddbd15ad6b116569f951b1b0c0b1669612c5fe513cfc2da76b3905d2827cd2
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,286 @@
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
+ # ☯️ Rust::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
+ `Rust::Merge` merges Rust source files by top-level items through the shared tree-sitter language-pack backend. It preserves destination items and appends template-only items when they do not already exist by structural path.
25
+
26
+ ### Key Features
27
+
28
+ - Rust dialect feature profile and backend capability reporting.
29
+ - Item ownership for functions, modules, structs, enums, impl blocks, traits, and use items as exposed by the language pack.
30
+ - Destination parse diagnostics with the common result shape.
31
+ - Module-level APIs for fixture runners and Git integrations.
32
+
33
+ ## 💡 Info you can shake a stick at
34
+
35
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
36
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
37
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
38
+ | 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] |
39
+ | 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] |
40
+ | 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] |
41
+ | 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] |
42
+ | 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] |
43
+ | 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] |
44
+ | `...` 💖 | [![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] |
45
+
46
+ ### Compatibility
47
+
48
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
49
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
50
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
51
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
52
+ not practical for the current toolchain.
53
+
54
+ <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>
55
+
56
+ The _amazing_ test matrix is powered by the kettle-dev stack.
57
+
58
+ <details markdown="1">
59
+ <summary>How kettle-dev manages complexity in tests</summary>
60
+
61
+ | Gem | Source | Role | Total downloads |
62
+ |-----|--------|------|---------------------|
63
+ | [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) |
64
+ | [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) |
65
+ | [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) |
66
+ | [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) |
67
+ | [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) |
68
+ | [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) |
69
+ | [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) |
70
+ | [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) |
71
+
72
+ </details>
73
+
74
+ ## ✨ Installation
75
+
76
+ Install the gem and add to the application's Gemfile by executing:
77
+
78
+ ```console
79
+ bundle add rust-merge
80
+ ```
81
+
82
+ If bundler is not being used to manage dependencies, install the gem by executing:
83
+
84
+ ```console
85
+ gem install rust-merge
86
+ ```
87
+
88
+ ## ⚙️ Configuration
89
+
90
+ ```ruby
91
+ profile = Rust::Merge.rust_backend_feature_profile
92
+ context = Rust::Merge.rust_plan_context
93
+ backends = Rust::Merge.available_rust_backends
94
+ ```
95
+
96
+ The Ruby implementation uses the shared language-pack backend and returns diagnostics for unsupported dialects or unavailable parsers.
97
+
98
+ ## 🔧 Basic Usage
99
+
100
+ ```ruby
101
+ require "rust/merge"
102
+
103
+ result = Rust::Merge.merge_rust(
104
+ File.read("template.rs"),
105
+ File.read("lib.rs"),
106
+ "rust",
107
+ )
108
+
109
+ abort result.fetch(:diagnostics).inspect unless result.fetch(:ok)
110
+ File.write("lib.rs", result.fetch(:output))
111
+ ```
112
+
113
+ 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.
114
+
115
+ ## 🔐 Security
116
+
117
+ See [SECURITY.md][🔐security].
118
+
119
+ ## 🤝 Contributing
120
+
121
+ If you need some ideas of where to help, you could work on adding more code coverage,
122
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
123
+
124
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
125
+
126
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
127
+
128
+ ## 📌 Versioning
129
+
130
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
131
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
132
+
133
+ For example:
134
+
135
+ ```ruby
136
+ spec.add_dependency("rust-merge", "~> 7.0")
137
+ ```
138
+
139
+ <details markdown="1">
140
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
141
+
142
+ Dropping support for a platform can be a breaking change for affected users.
143
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
144
+
145
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
146
+ read this article from the creator of SemVer:
147
+
148
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
149
+
150
+ </details>
151
+
152
+ See [CHANGELOG.md][📌changelog] for a list of releases.
153
+
154
+ ## 📄 License
155
+
156
+ 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).
157
+ See [LICENSE.md][📄license] for details.
158
+
159
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
160
+
161
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
162
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
163
+ [⛳liberapay]: https://liberapay.com/pboling/donate
164
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
165
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
166
+ [🖇sponsor]: https://github.com/sponsors/pboling
167
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
168
+ [🖇kofi]: https://ko-fi.com/pboling
169
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
170
+ [🖇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
171
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
172
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
173
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
174
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
175
+ [🖇floss-funding.dev]: https://floss-funding.dev
176
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
177
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
178
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
179
+ [✉️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
180
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
181
+ [✉️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
182
+ [✉️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
183
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
184
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
185
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/rust-merge
186
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Rust::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
187
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/rust-merge
188
+ [⛳️name-img]: https://img.shields.io/badge/name-rust--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
189
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
190
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
191
+ [🚂maint-blog]: http://www.railsbling.com/tags/rust-merge
192
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
193
+ [🚂maint-contact]: http://www.railsbling.com/contact
194
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
195
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
196
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
197
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
198
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
199
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
200
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
201
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
202
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
203
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
204
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
205
+ [💖🌳linktree]: https://linktr.ee/galtzo
206
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
207
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
208
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
209
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
210
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
211
+ [💖🧊berg]: https://codeberg.org/pboling
212
+ [💖🐙hub]: https://github.org/pboling
213
+ [💖🛖hut]: https://sr.ht/~galtzo/
214
+ [💖🧪lab]: https://gitlab.com/pboling
215
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
216
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
217
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
218
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
219
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-rust-merge?utm_source=rubygems-rust-merge&utm_medium=referral&utm_campaign=readme
220
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
221
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
222
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
223
+ [🚂railsbling]: http://www.railsbling.com
224
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
225
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/rust-merge
226
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
227
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/rust-merge
228
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
229
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/rust-merge
230
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
231
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
232
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
233
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
234
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
235
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
236
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/rust-merge
237
+ [👽dl-ranki]: https://img.shields.io/gem/dt/rust-merge.svg
238
+ [👽version]: https://clickgems.clickhouse.com/dashboard/rust-merge
239
+ [👽versioni]: https://img.shields.io/gem/v/rust-merge.svg
240
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
241
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
242
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
243
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
244
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
245
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
246
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
247
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
248
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
249
+ [🤝cb-donate]: https://donate.codeberg.org/
250
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
251
+ [🖐contrib-rocks]: https://contrib.rocks
252
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
253
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
254
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
255
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
256
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
257
+ [📌semver]: https://semver.org/spec/v2.0.0.html
258
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
259
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
260
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
261
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
262
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
263
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
264
+ [📌gitmoji]: https://gitmoji.dev
265
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
266
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
267
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.127-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
268
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
269
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
270
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
271
+ [📄license]: LICENSE.md
272
+ [📄license-ref]: LICENSE.md
273
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
274
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
275
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
276
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
277
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
278
+ [🚎yard-current]: http://rubydoc.info/gems/rust-merge
279
+ [🚎yard-head]: https://rust-merge.galtzo.com
280
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
281
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
282
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
283
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
284
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
285
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
286
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rust
4
+ module Merge
5
+ # Native TreeHaver analysis of safely attributable top-level Rust items.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one AST pass establishes ordered ownership and attribute/comment attachment
7
+ class FileAnalysis
8
+ attr_reader :source, :root_node, :declarations, :errors, :backend
9
+
10
+ def initialize(source)
11
+ @source = source
12
+ @backend = TREE_SITTER_BACKEND.id.to_sym
13
+ @errors = []
14
+ parse!
15
+ rescue StandardError => e
16
+ @root_node = nil
17
+ @declarations = [].freeze
18
+ @errors = [e].freeze
19
+ end
20
+
21
+ private
22
+
23
+ def parse!
24
+ @root_node = TreeHaver.parser_for(:rust, backend_type: :tree_sitter).parse(source).root_node
25
+ raise TreeHaver::NotAvailable, 'Rust parse returned no root node' unless root_node
26
+ raise TreeHaver::NotAvailable, 'Rust parse contains syntax errors' if root_node.has_error?
27
+
28
+ wrappers = []
29
+ pending = []
30
+ root_node.children.each do |node|
31
+ if attachable?(node)
32
+ if comment?(node) && trailing_comment?(wrappers.last, node)
33
+ wrappers.last.attach_trailing_comment!(node)
34
+ else
35
+ pending << node
36
+ end
37
+ next
38
+ end
39
+ if NodeWrapper::DECLARATION_TYPES.include?(node.type)
40
+ wrappers << NodeWrapper.new(node, source: source, leading_comments: attached_prefix(pending, node))
41
+ pending.clear
42
+ elsif globally_unmanaged?(node)
43
+ pending.clear
44
+ elsif node.named?
45
+ raise TreeHaver::NotAvailable, "Unmanaged top-level Rust node #{node.type.inspect}"
46
+ end
47
+ end
48
+ @declarations = wrappers.freeze
49
+ end
50
+
51
+ def attachable?(node)
52
+ comment?(node) || NodeWrapper::ATTRIBUTE_TYPES.include?(node.type)
53
+ end
54
+
55
+ def comment?(node) = NodeWrapper::COMMENT_TYPES.include?(node.type)
56
+
57
+ def globally_unmanaged?(node)
58
+ %w[inner_attribute_item shebang].include?(node.type)
59
+ end
60
+
61
+ def trailing_comment?(owner, comment)
62
+ owner && whitespace_only?(source.byteslice(owner.end_byte...comment.start_byte).to_s) &&
63
+ source.byteslice(owner.end_byte...comment.start_byte).to_s.count("\n").zero?
64
+ end
65
+
66
+ def attached_prefix(items, owner)
67
+ attached = []
68
+ cursor = owner.start_byte
69
+ items.reverse_each do |item|
70
+ gap = source.byteslice(item.end_byte...cursor).to_s
71
+ break unless whitespace_only?(gap)
72
+ break if gap.count("\n") > 1 && !structural_attribute?(item)
73
+
74
+ attached.unshift(item)
75
+ cursor = item.start_byte
76
+ end
77
+ attached.freeze
78
+ end
79
+
80
+ def structural_attribute?(item)
81
+ return true if NodeWrapper::ATTRIBUTE_TYPES.include?(item.type)
82
+
83
+ !find_descendant(item, 'outer_doc_comment_marker').nil?
84
+ end
85
+
86
+ def find_descendant(parent, type)
87
+ return parent if parent.type == type
88
+
89
+ parent.children.each do |child|
90
+ found = find_descendant(child, type)
91
+ return found if found
92
+ end
93
+ nil
94
+ end
95
+
96
+ def whitespace_only?(value)
97
+ value.each_byte.all? { |byte| [9, 10, 13, 32].include?(byte) }
98
+ end
99
+ end
100
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
101
+ end
102
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rust
4
+ module Merge
5
+ # Structural identity and source-range adapter for one top-level Rust item.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- Rust item forms share one structural identity boundary
7
+ class NodeWrapper
8
+ DECLARATION_TYPES = %w[
9
+ const_item
10
+ enum_item
11
+ expression_statement
12
+ extern_crate_declaration
13
+ function_item
14
+ impl_item
15
+ macro_definition
16
+ mod_item
17
+ static_item
18
+ struct_item
19
+ trait_item
20
+ type_item
21
+ union_item
22
+ use_declaration
23
+ ].freeze
24
+ NAMED_ITEMS = {
25
+ 'const_item' => [:const, %w[identifier]],
26
+ 'enum_item' => [:enum, %w[type_identifier]],
27
+ 'function_item' => [:function, %w[identifier]],
28
+ 'macro_definition' => [:macro, %w[identifier]],
29
+ 'mod_item' => [:module, %w[identifier]],
30
+ 'static_item' => [:static, %w[identifier]],
31
+ 'struct_item' => [:struct, %w[type_identifier]],
32
+ 'trait_item' => [:trait, %w[type_identifier]],
33
+ 'type_item' => [:type, %w[type_identifier]],
34
+ 'union_item' => [:union, %w[type_identifier]]
35
+ }.freeze
36
+ COMMENT_TYPES = %w[block_comment line_comment].freeze
37
+ ATTRIBUTE_TYPES = %w[attribute_item].freeze
38
+
39
+ attr_reader :node, :source, :leading_comments, :trailing_comments
40
+
41
+ def initialize(node, source:, leading_comments: [])
42
+ @node = node
43
+ @source = source
44
+ @leading_comments = leading_comments.freeze
45
+ @trailing_comments = []
46
+ end
47
+
48
+ def signature
49
+ value = case node.type
50
+ when 'use_declaration' then use_signature
51
+ when 'extern_crate_declaration' then extern_crate_signature
52
+ when 'impl_item' then impl_signature
53
+ when 'expression_statement' then macro_invocation_signature
54
+ else named_signature(node)
55
+ end
56
+ value.freeze
57
+ end
58
+
59
+ def membership_signature
60
+ return semantic_node(use_body) if compound_use?
61
+
62
+ signature
63
+ end
64
+
65
+ def compound? = compound_use?
66
+ def start_byte = leading_comments.first&.start_byte || node.start_byte
67
+ def end_byte = trailing_comments.last&.end_byte || node.end_byte
68
+ def start_line = line_for_byte(start_byte)
69
+ def end_line = line_for_byte(end_byte.zero? ? 0 : end_byte - 1)
70
+ def source_text = source.byteslice(start_byte...end_byte).to_s
71
+ def attach_trailing_comment!(comment) = trailing_comments << comment
72
+ def semantic_tree = semantic_node(node)
73
+
74
+ private
75
+
76
+ def named_signature(value)
77
+ kind, name_types = NAMED_ITEMS.fetch(value.type) do
78
+ raise ArgumentError, "Unsupported top-level Rust node #{value.type.inspect}"
79
+ end
80
+ name = direct_named_child(value, name_types)
81
+ raise ArgumentError, "#{value.type} has no structurally attributable name" unless name
82
+
83
+ [kind, text(name)]
84
+ end
85
+
86
+ def use_signature
87
+ body = use_body
88
+ return [:use_tree, use_tree_root(body)] if compound_use?
89
+
90
+ [:use, use_target(body)]
91
+ end
92
+
93
+ def use_body
94
+ node.children.find do |child|
95
+ child.named? && child.type != 'visibility_modifier'
96
+ end || raise(ArgumentError, 'use declaration has no structural tree')
97
+ end
98
+
99
+ def compound_use?
100
+ node.type == 'use_declaration' && !find_descendant(use_body, %w[use_list]).nil?
101
+ end
102
+
103
+ def use_tree_root(body)
104
+ return :root if body.type == 'use_list'
105
+
106
+ child = body.children.find { |candidate| candidate.named? && candidate.type != 'use_list' }
107
+ raise ArgumentError, 'compound use tree has no structural root' unless child
108
+
109
+ path_head(child)
110
+ end
111
+
112
+ def use_target(body)
113
+ case body.type
114
+ when 'use_as_clause'
115
+ target = body.children.find(&:named?)
116
+ raise ArgumentError, 'use alias has no structural target' unless target
117
+
118
+ path_head(target)
119
+ when 'scoped_identifier'
120
+ path_head(body)
121
+ when 'use_wildcard'
122
+ [:wildcard, path_head(body)]
123
+ when 'identifier', 'self', 'super', 'crate'
124
+ text(body)
125
+ else
126
+ [:tree, semantic_node(body)]
127
+ end
128
+ end
129
+
130
+ def extern_crate_signature
131
+ identifiers = node.children.select { |child| child.type == 'identifier' }
132
+ raise ArgumentError, 'extern crate declaration has no crate name' if identifiers.empty?
133
+
134
+ [:extern_crate, text(identifiers.first)]
135
+ end
136
+
137
+ def impl_signature
138
+ children = node.children
139
+ for_index = children.index { |child| child.type == 'for' }
140
+ type_nodes = children.select { |child| rust_type_node?(child) }
141
+ target = for_index ? children[(for_index + 1)..].find { |child| rust_type_node?(child) } : type_nodes.last
142
+ trait = for_index && children[...for_index].reverse.find { |child| rust_type_node?(child) }
143
+ raise ArgumentError, 'impl item has no structural target' unless target
144
+
145
+ polarity = children.any? { |child| child.type == '!' } ? :negative : :positive
146
+ [:impl, trait && path_head(trait), path_head(target), polarity, generic_arity]
147
+ end
148
+
149
+ def generic_arity
150
+ parameters = node.children.find { |child| child.type == 'type_parameters' }
151
+ return 0 unless parameters
152
+
153
+ parameters.children.count do |child|
154
+ %w[type_parameter lifetime_parameter const_parameter].include?(child.type)
155
+ end
156
+ end
157
+
158
+ def rust_type_node?(value)
159
+ return false unless value.named?
160
+
161
+ excluded = %w[type_parameters where_clause declaration_list visibility_modifier] +
162
+ COMMENT_TYPES + ATTRIBUTE_TYPES
163
+ !excluded.include?(value.type)
164
+ end
165
+
166
+ def macro_invocation_signature
167
+ invocation = node.children.find { |child| child.type == 'macro_invocation' }
168
+ raise ArgumentError, 'expression statement is not a top-level macro invocation' unless invocation
169
+
170
+ path = invocation.children.find do |child|
171
+ %w[identifier scoped_identifier].include?(child.type)
172
+ end
173
+ raise ArgumentError, 'macro invocation has no structural path' unless path
174
+
175
+ [:macro_invocation, path_head(path)]
176
+ end
177
+
178
+ def path_head(value)
179
+ case value.type
180
+ when 'generic_type', 'reference_type', 'pointer_type'
181
+ child = value.children.find(&:named?)
182
+ child ? path_head(child) : value.type
183
+ when 'scoped_identifier', 'scoped_type_identifier'
184
+ value.children.select(&:named?).map { |child| path_head(child) }
185
+ when 'use_wildcard'
186
+ child = value.children.find(&:named?)
187
+ child ? path_head(child) : :wildcard
188
+ else
189
+ text(value)
190
+ end
191
+ end
192
+
193
+ def direct_named_child(parent, types)
194
+ parent.children.find { |candidate| types.include?(candidate.type) }
195
+ end
196
+
197
+ def find_descendant(parent, types)
198
+ return parent if types.include?(parent.type)
199
+
200
+ parent.children.each do |child|
201
+ found = find_descendant(child, types)
202
+ return found if found
203
+ end
204
+ nil
205
+ end
206
+
207
+ def semantic_node(value)
208
+ children = value.children
209
+ return [value.type, text(value)] if children.empty?
210
+
211
+ [value.type, children.select(&:named?).map { |child| semantic_node(child) }]
212
+ end
213
+
214
+ def text(value) = source.byteslice(value.start_byte...value.end_byte).to_s
215
+ def line_for_byte(byte) = source.byteslice(0...byte).to_s.count("\n") + 1
216
+ end
217
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
218
+ end
219
+ end