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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.md +13 -0
- data/README.md +297 -0
- data/lib/ruby/merge/block_binding_support.rb +22 -0
- data/lib/ruby/merge/block_directive_detector.rb +208 -0
- data/lib/ruby/merge/doc_comment_support.rb +97 -0
- data/lib/ruby/merge/gemspec_support.rb +32 -0
- data/lib/ruby/merge/magic_comment_support.rb +128 -0
- data/lib/ruby/merge/method_similarity.rb +75 -0
- data/lib/ruby/merge/nocov_node_base.rb +75 -0
- data/lib/ruby/merge/nocov_wrapper_base.rb +46 -0
- data/lib/ruby/merge/rescue_semantics.rb +209 -0
- data/lib/ruby/merge/scaffold_chunk_support.rb +74 -0
- data/lib/ruby/merge/signature_support.rb +97 -0
- data/lib/ruby/merge/version.rb +5 -3
- data/lib/ruby/merge.rb +2723 -313
- data/lib/ruby-merge.rb +7 -1
- data/sig/ruby/merge.rbs +8 -0
- data.tar.gz.sig +0 -0
- metadata +261 -14
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98b5dba95c151b6d6a08ae403679f78af429360fcd4b2a1e0c5ba023cfc5639e
|
|
4
|
+
data.tar.gz: cb85a47f7e47fbb0af358f69f8ce0503b790967f76aa04a0ad99282711cede45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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
|