zip-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: a859c92350627feaf0b728b47e735675f19c339d50c6bf709a5c516e6fd252ec
4
- data.tar.gz: 54d03cd74d3ec83b9050bdcad173320f5a9c00dd49a657a04f736f92c3ce53bc
3
+ metadata.gz: 5a9683e713ef24a65e74b003579e4c54bf369859b740dcbd6f56ef788942e858
4
+ data.tar.gz: 13488970a1b826964bd9bc478c1e85c301e37f88c561127883f04a3bdf419662
5
5
  SHA512:
6
- metadata.gz: 96e218946eb50b84c11e3d1fff6354626fd44f50d1672e32275aa3eb18e960821566845a0a4fd6adcd1a68a7c2be9d2de6255c2933da91b266105309e3369b20
7
- data.tar.gz: 882dadf7d82282213faf7d43691eee6f4382a14eb261c5b4aeb7659db609c9532b1c86a688a5bdd2f0c034a1c1eaf37c8db98f31536f726c65f490488763e4d5
6
+ metadata.gz: 2d0d142fe3be5c9c838bbe288e5b5ffaeb3a94742bf9a66a1fc7bfc7e6739d903cf469a41786cdfc1a1f30ca7f20edda28877b14a6a78c6345a66aac6c5dc678
7
+ data.tar.gz: b114ba117f07c46042de4ff98048825e3390b6157ca5e0fa96b8106311bd3e2ee972c9a93c7cb85099d1b5b8fa13e33a350ac89bcda26da9a8ef01449e13a214
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,307 @@
1
+ <a href="https://github.com/structuredmerge"><img alt="structuredmerge Logo by GitHub" src="https://github.com/structuredmerge.png?size=192" width="14%" align="right"/></a>
2
+
3
+ # ☯️ Zip::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
+ `Zip::Merge` plans ZIP archive merges by member path and raw byte ranges. It is
25
+ a Kaitai-family binary AST integration in the StructuredMerge stack: ZIP
26
+ inventories are represented with TreeHaver ZIP report nodes, and analysis enters
27
+ through a `parser_for(:zip)` Kaitai path. It depends on `binary-merge` for the
28
+ shared binary-family substrate: preservation reports, unsafe diagnostics,
29
+ byte-range ownership, and renderer planning vocabulary.
30
+ It identifies safe preservation cases, unsafe entries, members that need
31
+ structured nested merges, and archive metadata that must be rewritten by a
32
+ renderer.
33
+
34
+ ### Key Features
35
+
36
+ - Central directory and local header inventory parsing.
37
+ - Kaitai-family binary AST reporting through TreeHaver ZIP structures.
38
+ - Shared binary-family report and diagnostic helpers from `binary-merge`.
39
+ - Member decisions for preserve, add, delete, rewrite, delegate, and reject operations.
40
+ - Nested family dispatch for structured archive members.
41
+ - Raw-preserving renderer for safe unchanged members.
42
+ - Checksum and central-directory update reporting.
43
+
44
+ ## 💡 Info you can shake a stick at
45
+
46
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
47
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
48
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf]|
49
+ | 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] |
50
+ | 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] |
51
+ | 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] |
52
+ | 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] |
53
+ | 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] |
54
+ | 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] |
55
+ | `...` 💖 | [![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] |
56
+
57
+ ### Compatibility
58
+
59
+ Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
60
+ CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
61
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
62
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
63
+ not practical for the current toolchain.
64
+
65
+ <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>
66
+
67
+ The _amazing_ test matrix is powered by the kettle-dev stack.
68
+
69
+ <details markdown="1">
70
+ <summary>How kettle-dev manages complexity in tests</summary>
71
+
72
+ | Gem | Source | Role | Total downloads |
73
+ |-----|--------|------|---------------------|
74
+ | [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) |
75
+ | [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) |
76
+ | [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) |
77
+ | [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) |
78
+ | [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) |
79
+ | [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) |
80
+ | [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) |
81
+ | [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) |
82
+
83
+ </details>
84
+
85
+ ## ✨ Installation
86
+
87
+ Install the gem and add to the application's Gemfile by executing:
88
+
89
+ ```console
90
+ bundle add zip-merge
91
+ ```
92
+
93
+ If bundler is not being used to manage dependencies, install the gem by executing:
94
+
95
+ ```console
96
+ gem install zip-merge
97
+ ```
98
+
99
+ ## ⚙️ Configuration
100
+
101
+ `Zip::Merge` works from parsed inventories rather than global settings.
102
+
103
+ ```ruby
104
+ ancestor = Zip::Merge.parse_zip_inventory(ancestor_bytes)
105
+ current = Zip::Merge.parse_zip_inventory(current_bytes)
106
+ incoming = Zip::Merge.parse_zip_inventory(incoming_bytes)
107
+ plan = Zip::Merge.plan_zip_merge(ancestor, current, incoming)
108
+ ```
109
+
110
+ Rendering requires caller-supplied member bytes for added, rewritten, or delegated members.
111
+
112
+ ## 🔧 Basic Usage
113
+
114
+ ```ruby
115
+ require "zip/merge"
116
+
117
+ zip_parser = TreeHaver.parser_for(:zip, backend_type: :kaitai)
118
+ ancestor = zip_parser.parse(File.binread("base.zip"))
119
+ current = zip_parser.parse(File.binread("ours.zip"))
120
+ incoming = zip_parser.parse(File.binread("theirs.zip"))
121
+
122
+ plan = Zip::Merge.plan_zip_merge(ancestor, current, incoming)
123
+ plan.member_decisions.each do |decision|
124
+ puts "#{decision.operation}: #{decision.normalized_path}"
125
+ end
126
+ ```
127
+
128
+ ## 🔐 Security
129
+
130
+ See [SECURITY.md][🔐security].
131
+
132
+ ## 🤝 Contributing
133
+
134
+ If you need some ideas of where to help, you could work on adding more code coverage,
135
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
136
+ or use the gem and think about how it could be better.
137
+
138
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
139
+
140
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
141
+
142
+ ### Code Coverage
143
+
144
+ <details markdown="1">
145
+ <summary>Coverage service badges</summary>
146
+
147
+ </details>
148
+
149
+ ## 📌 Versioning
150
+
151
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
152
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
153
+
154
+ For example:
155
+
156
+ ```ruby
157
+ spec.add_dependency("zip-merge", "~> 7.0")
158
+ ```
159
+
160
+ <details markdown="1">
161
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
162
+
163
+ Dropping support for a platform can be a breaking change for affected users.
164
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
165
+
166
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
167
+ read this article from the creator of SemVer:
168
+
169
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
170
+
171
+ </details>
172
+
173
+ See [CHANGELOG.md][📌changelog] for a list of releases.
174
+
175
+ ## 📄 License
176
+
177
+ The gem is available under the following licenses: [AGPL-3.0-only](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/AGPL-3.0-only.md), [PolyForm-Small-Business-1.0.0](https://github.com/structuredmerge/structuredmerge-ruby/blob/main/PolyForm-Small-Business-1.0.0.md).
178
+ See [LICENSE.md][📄license] for details.
179
+
180
+ If none of the available licenses suit your use case, please [contact us](mailto:floss@galtzo.com) to discuss a custom commercial license.
181
+
182
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
183
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
184
+ [⛳liberapay]: https://liberapay.com/pboling/donate
185
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
186
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
187
+ [🖇sponsor]: https://github.com/sponsors/pboling
188
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
189
+ [🖇kofi]: https://ko-fi.com/pboling
190
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
191
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
192
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
193
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
194
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
195
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
196
+ [🖇floss-funding.dev]: https://floss-funding.dev
197
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
198
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
199
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
200
+ [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
201
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
202
+ [✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
203
+ [✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
204
+ [✉️ruby-forum]: https://www.rubyforum.org/tag/structuredmerge
205
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
206
+ [⛳️gem-namespace]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/zip-merge
207
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Zip::Merge-3C2D2D.svg?style=square&logo=ruby&logoColor=white
208
+ [⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/zip-merge
209
+ [⛳️name-img]: https://img.shields.io/badge/name-zip--merge-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
210
+ [⛳️tag-img]: https://img.shields.io/github/tag/structuredmerge/structuredmerge-ruby.svg
211
+ [⛳️tag]: https://github.com/structuredmerge/structuredmerge-ruby/releases
212
+ [🚂maint-blog]: http://www.railsbling.com/tags/zip-merge
213
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
214
+ [🚂maint-contact]: http://www.railsbling.com/contact
215
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
216
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
217
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
218
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
219
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
220
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
221
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
222
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
223
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
224
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
225
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
226
+ [💖🌳linktree]: https://linktr.ee/galtzo
227
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
228
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
229
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
230
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
231
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
232
+ [💖🧊berg]: https://codeberg.org/pboling
233
+ [💖🐙hub]: https://github.org/pboling
234
+ [💖🛖hut]: https://sr.ht/~galtzo/
235
+ [💖🧪lab]: https://gitlab.com/pboling
236
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
237
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
238
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
239
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
240
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-zip-merge?utm_source=rubygems-zip-merge&utm_medium=referral&utm_campaign=readme
241
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
242
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
243
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
244
+ [🚂railsbling]: http://www.railsbling.com
245
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
246
+ [📜src-gl]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/gems/zip-merge
247
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
248
+ [📜src-cb]: https://codeberg.org/structuredmerge/structuredmerge-ruby/src/branch/main/gems/zip-merge
249
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=GitHub&logoColor=green
250
+ [📜src-gh]: https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/zip-merge
251
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
252
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
253
+ [📜gl-wiki]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/wikis/home
254
+ [📜gh-wiki]: https://github.com/structuredmerge/structuredmerge-ruby/wiki
255
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
256
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
257
+ [👽dl-rank]: https://clickgems.clickhouse.com/dashboard/zip-merge
258
+ [👽dl-ranki]: https://img.shields.io/gem/dt/zip-merge.svg
259
+ [👽version]: https://clickgems.clickhouse.com/dashboard/zip-merge
260
+ [👽versioni]: https://img.shields.io/gem/v/zip-merge.svg
261
+ [🚎11-c-wf]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml
262
+ [🚎11-c-wfi]: https://github.com/structuredmerge/structuredmerge-ruby/actions/workflows/current.yml/badge.svg
263
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
264
+ [🤝gh-issues]: https://github.com/structuredmerge/structuredmerge-ruby/issues
265
+ [🤝gh-pulls]: https://github.com/structuredmerge/structuredmerge-ruby/pulls
266
+ [🤝gl-issues]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/issues
267
+ [🤝gl-pulls]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/merge_requests
268
+ [🤝cb-issues]: https://codeberg.org/structuredmerge/structuredmerge-ruby/issues
269
+ [🤝cb-pulls]: https://codeberg.org/structuredmerge/structuredmerge-ruby/pulls
270
+ [🤝cb-donate]: https://donate.codeberg.org/
271
+ [🤝contributing]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CONTRIBUTING.md
272
+ [🖐contrib-rocks]: https://contrib.rocks
273
+ [🖐contributors]: https://github.com/structuredmerge/structuredmerge-ruby/graphs/contributors
274
+ [🖐contributors-img]: https://contrib.rocks/image?repo=structuredmerge/structuredmerge-ruby
275
+ [🪇conduct]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CODE_OF_CONDUCT.md
276
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
277
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
278
+ [📌semver]: https://semver.org/spec/v2.0.0.html
279
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
280
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
281
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
282
+ [📌changelog]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/CHANGELOG.md
283
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
284
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
285
+ [📌gitmoji]: https://gitmoji.dev
286
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
287
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
288
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.247-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
289
+ [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
290
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
291
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
292
+ [📄license]: LICENSE.md
293
+ [📄license-ref]: LICENSE.md
294
+ [📄license-img]: https://img.shields.io/badge/License-AGPL--3.0--only_OR_PolyForm--Small--Business--1.0.0-259D6C.svg
295
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-x
296
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Incompatible:_Category_X-%E2%9C%97-C0392B.svg?style=flat&logo=Apache
297
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
298
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
299
+ [🚎yard-current]: http://rubydoc.info/gems/zip-merge
300
+ [🚎yard-head]: https://zip-merge.galtzo.com
301
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
302
+ [💎SHA_checksums]: https://gitlab.com/structuredmerge/structuredmerge-ruby/-/tree/main/checksums
303
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
304
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
305
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
306
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
307
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zip
4
+ # StructuredMerge ZIP archive merge planning and rendering helpers.
5
+ module Merge
6
+ BACKEND_REFERENCE = TreeHaver::KAITAI_STRUCT_BACKEND
7
+
8
+ module Backend
9
+ # TreeHaver language wrapper for ZIP archives parsed through Kaitai-style
10
+ # binary AST inventory.
11
+ class Language < TreeHaver::Base::Language
12
+ def initialize(name = :zip)
13
+ super(name, backend: :kaitai, options: {})
14
+ end
15
+
16
+ def self.zip
17
+ new(:zip)
18
+ end
19
+ end
20
+
21
+ # Parser adapter that exposes the ZIP inventory as the TreeHaver
22
+ # `parser_for(:zip)` Kaitai backend surface.
23
+ class Parser < TreeHaver::Base::Parser
24
+ def parse(source)
25
+ raise 'Language not set' unless language
26
+
27
+ Zip::Merge.parse_zip_inventory(source)
28
+ end
29
+
30
+ def parse_string(_old_tree, source)
31
+ parse(source)
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.register_backend!
37
+ TreeHaver::BackendRegistry.register(BACKEND_REFERENCE)
38
+ TreeHaver.register_language(
39
+ :zip,
40
+ backend_module: Backend,
41
+ backend_type: :kaitai,
42
+ gem_name: 'zip-merge',
43
+ contract: :kaitai
44
+ )
45
+ nil
46
+ end
47
+ end
48
+ end
@@ -2,10 +2,12 @@
2
2
 
3
3
  module Zip
4
4
  module Merge
5
+ # Version namespace for this gem.
5
6
  module Version
6
- VERSION = "7.0.0"
7
+ # Current gem version.
8
+ VERSION = '7.1.1'
7
9
  end
8
-
9
- VERSION = Version::VERSION
10
+ # Current gem version exposed at the traditional constant location.
11
+ VERSION = Version::VERSION # Traditional Constant Location
10
12
  end
11
13
  end
data/lib/zip/merge.rb CHANGED
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "stringio"
4
- require "zlib"
5
- require "tree_haver"
6
- require_relative "merge/version"
3
+ require 'stringio'
4
+ require 'zlib'
5
+ require 'tree_haver'
6
+ require_relative 'merge/version'
7
7
 
8
8
  module Zip
9
9
  module Merge
10
10
  LOCAL = 0x04034b50
11
11
  CENTRAL = 0x02014b50
12
12
  EOCD = 0x06054b50
13
- DOS_EPOCH = [0, 0, 0x21, 0].pack("C*")
13
+ DOS_EPOCH = [0, 0, 0x21, 0].pack('C*')
14
14
 
15
15
  RenderError = Class.new(StandardError) do
16
16
  attr_reader :diagnostic
@@ -32,19 +32,21 @@ module Zip
32
32
  TreeHaver::ZipArchiveEntry.new(
33
33
  path: name,
34
34
  normalized_path: normalize_zip_path(name),
35
- directory: name.end_with?("/"),
35
+ directory: name.end_with?('/'),
36
36
  compression: compression_name(record[:method]),
37
37
  compressed_size: record[:compressed_size],
38
38
  uncompressed_size: record[:uncompressed_size],
39
- crc32: "%08x" % record[:crc32],
39
+ crc32: '%08x' % record[:crc32],
40
40
  local_header_range: TreeHaver::ByteRange.new(start_byte: record[:local_offset], end_byte: local[:data_start]),
41
- data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start], end_byte: local[:data_start] + record[:compressed_size]),
41
+ data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start],
42
+ end_byte: local[:data_start] + record[:compressed_size]),
42
43
  central_directory_range: record[:range]
43
44
  )
44
45
  end.sort_by { |entry| entry.local_header_range.start_byte }
45
46
 
46
47
  TreeHaver::ZipFamilyReport.new(
47
- archive: TreeHaver::ZipArchiveInfo.new(format: "zip", schema: "zip.ksy", entry_count: entries.length, central_directory_range: central[:range]),
48
+ archive: TreeHaver::ZipArchiveInfo.new(format: 'zip', schema: 'zip.ksy', entry_count: entries.length,
49
+ central_directory_range: central[:range]),
48
50
  entries: entries,
49
51
  member_decisions: [],
50
52
  unsafe_entries: unsafe_entries(entries, central[:records]),
@@ -71,59 +73,70 @@ module Zip
71
73
  incoming_entry = incoming_entries[path]
72
74
  if unsafe_by_path[path]
73
75
  unsafe = unsafe_by_path[path]
74
- report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: "reject", disposition: "unsafe", reason: unsafe.reason)
76
+ report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'reject',
77
+ disposition: 'unsafe', reason: unsafe.reason)
75
78
  report.merge_report.diagnostics << diagnostic(unsafe.category, schema_path(path), unsafe.reason)
76
79
  elsif current_entry.nil? && incoming_entry
77
- decision(report, path, "add", "requires_renderer", "member exists only in incoming archive")
80
+ decision(report, path, 'add', 'requires_renderer', 'member exists only in incoming archive')
78
81
  elsif current_entry && incoming_entry.nil?
79
- decision(report, path, "delete", "requires_renderer", "member was removed from incoming archive")
80
- elsif ancestor_entry && same_entry?(current_entry, ancestor_entry) && same_entry?(incoming_entry, ancestor_entry)
81
- report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: "preserve", disposition: "safe", reason: "member is unchanged from ancestor")
82
+ decision(report, path, 'delete', 'requires_renderer', 'member was removed from incoming archive')
83
+ elsif ancestor_entry && same_entry?(current_entry,
84
+ ancestor_entry) && same_entry?(incoming_entry, ancestor_entry)
85
+ report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'preserve',
86
+ disposition: 'safe', reason: 'member is unchanged from ancestor')
82
87
  report.merge_report.preserved_ranges.concat([current_entry.local_header_range, current_entry.data_range])
83
88
  elsif (family = nested_family(path))
84
- report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: "delegate", disposition: "requires_renderer", nested_family: family, reason: "structured member can be merged by a nested family before ZIP rendering")
85
- report.merge_report.nested_dispatches << TreeHaver::BinaryNestedDispatch.new(schema_path: "#{schema_path(path)}/data", family: family, status: "planned")
89
+ report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'delegate',
90
+ disposition: 'requires_renderer', nested_family: family, reason: 'structured member can be merged by a nested family before ZIP rendering')
91
+ report.merge_report.nested_dispatches << TreeHaver::BinaryNestedDispatch.new(
92
+ schema_path: "#{schema_path(path)}/data", family: family, status: 'planned'
93
+ )
86
94
  report.merge_report.rewritten_nodes << schema_path(path)
87
95
  report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
88
96
  else
89
- decision(report, path, "rewrite", "requires_renderer", "member bytes or metadata changed")
97
+ decision(report, path, 'rewrite', 'requires_renderer', 'member bytes or metadata changed')
90
98
  report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
91
99
  end
92
100
  report.merge_report.matched_schema_paths << schema_path(path)
93
101
  end
94
102
  unless report.merge_report.rewritten_nodes.empty? && report.merge_report.checksum_updates.empty?
95
- report.merge_report.rewritten_nodes << "/central_directory"
96
- report.merge_report.checksum_updates.concat(["/central_directory/size", "/central_directory/offset"])
103
+ report.merge_report.rewritten_nodes << '/central_directory'
104
+ report.merge_report.checksum_updates.concat(['/central_directory/size', '/central_directory/offset'])
97
105
  end
98
106
  report
99
107
  end
100
108
 
101
109
  def render_with_raw_preservation(source:, plan:, member_bytes: {}, compression: 0)
102
- raise render_error("unsupported_compression", "/render/options/compression", "unsupported raw-preserving compression method") unless [0, 8].include?(compression)
110
+ unless [
111
+ 0, 8
112
+ ].include?(compression)
113
+ raise render_error('unsupported_compression', '/render/options/compression',
114
+ 'unsupported raw-preserving compression method')
115
+ end
103
116
 
104
117
  source = source.b
105
118
  source_inventory = parse_zip_inventory(source)
106
119
  central = scan_central_directory(source)
107
120
  source_entries = entries_by_path(source_inventory.entries)
108
121
  raw_ranges = raw_local_record_ranges(source, source_entries)
109
- output = +"".b
122
+ output = +''.b
110
123
  central_records = []
111
124
  entries = entries_by_path(plan.entries)
112
125
  plan.member_decisions.each do |member|
113
126
  entry = entries[member.normalized_path]
114
127
  case member.operation
115
- when "reject"
116
- raise render_error("rejected_member", schema_path(member.normalized_path), member.reason)
117
- when "delete"
128
+ when 'reject'
129
+ raise render_error('rejected_member', schema_path(member.normalized_path), member.reason)
130
+ when 'delete'
118
131
  next
119
- when "preserve"
132
+ when 'preserve'
120
133
  source_entry = source_entries.fetch(member.normalized_path)
121
134
  validate_raw_preserve_entry!(source, central, source_entry)
122
135
  range = raw_ranges.fetch(member.normalized_path)
123
136
  offset = output.bytesize
124
137
  output << source.byteslice(range.start_byte...range.end_byte)
125
138
  central_records << central_record_from_entry(source_entry, offset)
126
- when "add", "rewrite", "delegate"
139
+ when 'add', 'rewrite', 'delegate'
127
140
  content = member_bytes.fetch(member.normalized_path)
128
141
  rendered, record = rendered_local_record(entry, content.b, compression, output.bytesize)
129
142
  output << rendered
@@ -138,12 +151,14 @@ module Zip
138
151
  output << eocd_record(central_records.length, central_size, central_start)
139
152
  report = parse_zip_inventory(output)
140
153
  merge_report = plan.merge_report
141
- merge_report.preserved_ranges = plan.member_decisions.filter_map { |member| raw_ranges[member.normalized_path] if member.operation == "preserve" }
154
+ merge_report.preserved_ranges = plan.member_decisions.filter_map do |member|
155
+ raw_ranges[member.normalized_path] if member.operation == 'preserve'
156
+ end
142
157
  [output, report, merge_report]
143
158
  end
144
159
 
145
160
  def new_stored_zip(entries)
146
- output = +"".b
161
+ output = +''.b
147
162
  central = []
148
163
  entries.keys.sort.each do |name|
149
164
  rendered, record = rendered_local_record(path_entry(name, entries[name]), entries[name].b, 0, output.bytesize)
@@ -157,34 +172,38 @@ module Zip
157
172
  end
158
173
 
159
174
  def empty_report
160
- TreeHaver::BinaryMergeReport.new(format: "zip", schema: "zip.ksy", matched_schema_paths: [], preserved_ranges: [], rewritten_nodes: [], checksum_updates: [], nested_dispatches: [], diagnostics: [])
175
+ Binary::Merge.preservation_report(format: 'zip', schema: 'zip.ksy', matched_schema_paths: [],
176
+ preserved_ranges: [])
161
177
  end
162
178
 
163
179
  def scan_central_directory(source)
164
180
  eocd = source.bytesize - 22
165
- eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1("V") != EOCD
166
- raise "missing ZIP end of central directory" if eocd.negative?
167
- size = source.byteslice(eocd + 12, 4).unpack1("V")
168
- offset = source.byteslice(eocd + 16, 4).unpack1("V")
169
- comment_length = source.byteslice(eocd + 20, 2).unpack1("v")
181
+ eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1('V') != EOCD
182
+ raise 'missing ZIP end of central directory' if eocd.negative?
183
+
184
+ size = source.byteslice(eocd + 12, 4).unpack1('V')
185
+ offset = source.byteslice(eocd + 16, 4).unpack1('V')
186
+ comment_length = source.byteslice(eocd + 20, 2).unpack1('v')
170
187
  records = {}
171
188
  cursor = offset
172
189
  while cursor < offset + size
173
- raise "unexpected central directory record" unless source.byteslice(cursor, 4).unpack1("V") == CENTRAL
174
- name_len = source.byteslice(cursor + 28, 2).unpack1("v")
175
- extra_len = source.byteslice(cursor + 30, 2).unpack1("v")
176
- comment_len = source.byteslice(cursor + 32, 2).unpack1("v")
190
+ raise 'unexpected central directory record' unless source.byteslice(cursor, 4).unpack1('V') == CENTRAL
191
+
192
+ name_len = source.byteslice(cursor + 28, 2).unpack1('v')
193
+ extra_len = source.byteslice(cursor + 30, 2).unpack1('v')
194
+ comment_len = source.byteslice(cursor + 32, 2).unpack1('v')
177
195
  name = source.byteslice(cursor + 46, name_len)
178
196
  records[name] = {
179
- range: TreeHaver::ByteRange.new(start_byte: cursor, end_byte: cursor + 46 + name_len + extra_len + comment_len),
180
- flags: source.byteslice(cursor + 8, 2).unpack1("v"),
181
- method: source.byteslice(cursor + 10, 2).unpack1("v"),
182
- crc32: source.byteslice(cursor + 16, 4).unpack1("V"),
183
- compressed_size: source.byteslice(cursor + 20, 4).unpack1("V"),
184
- uncompressed_size: source.byteslice(cursor + 24, 4).unpack1("V"),
197
+ range: TreeHaver::ByteRange.new(start_byte: cursor,
198
+ end_byte: cursor + 46 + name_len + extra_len + comment_len),
199
+ flags: source.byteslice(cursor + 8, 2).unpack1('v'),
200
+ method: source.byteslice(cursor + 10, 2).unpack1('v'),
201
+ crc32: source.byteslice(cursor + 16, 4).unpack1('V'),
202
+ compressed_size: source.byteslice(cursor + 20, 4).unpack1('V'),
203
+ uncompressed_size: source.byteslice(cursor + 24, 4).unpack1('V'),
185
204
  extra_length: extra_len,
186
205
  comment_length: comment_len,
187
- local_offset: source.byteslice(cursor + 42, 4).unpack1("V")
206
+ local_offset: source.byteslice(cursor + 42, 4).unpack1('V')
188
207
  }
189
208
  cursor = records[name][:range].end_byte
190
209
  end
@@ -194,32 +213,67 @@ module Zip
194
213
  def scan_local_headers(source, records)
195
214
  records.transform_values do |record|
196
215
  cursor = record[:local_offset]
197
- raise "unexpected ZIP local header" unless source.byteslice(cursor, 4).unpack1("V") == LOCAL
198
- name_len = source.byteslice(cursor + 26, 2).unpack1("v")
199
- extra_len = source.byteslice(cursor + 28, 2).unpack1("v")
216
+ raise 'unexpected ZIP local header' unless source.byteslice(cursor, 4).unpack1('V') == LOCAL
217
+
218
+ name_len = source.byteslice(cursor + 26, 2).unpack1('v')
219
+ extra_len = source.byteslice(cursor + 28, 2).unpack1('v')
200
220
  { data_start: cursor + 30 + name_len + extra_len, extra_length: extra_len }
201
221
  end
202
222
  end
203
223
 
204
224
  def validate_raw_preserve_entry!(source, central, entry)
205
- raise render_error("archive_comment", "/archive/comment", "raw-preserving ZIP renderer does not yet preserve archive comments") if central[:archive_comment]
225
+ if central[:archive_comment]
226
+ raise render_error('archive_comment', '/archive/comment',
227
+ 'raw-preserving ZIP renderer does not yet preserve archive comments')
228
+ end
229
+
206
230
  record = central[:records].fetch(entry.path)
207
- raise render_error("encrypted_member", schema_path(entry.normalized_path), "raw-preserving ZIP renderer rejects encrypted member #{entry.normalized_path}") unless (record[:flags] & 0x1).zero?
208
- raise render_error("unsupported_compression", schema_path(entry.normalized_path), "raw-preserving ZIP renderer rejects unsupported compression #{entry.compression.inspect}") unless [0, 8].include?(record[:method])
209
- raise render_error("central_directory_extra_field", schema_path(entry.normalized_path), "raw-preserving ZIP renderer does not yet preserve central-directory extra fields for #{entry.normalized_path}") unless record[:extra_length].zero?
210
- raise render_error("member_comment", schema_path(entry.normalized_path), "raw-preserving ZIP renderer does not yet preserve member comments for #{entry.normalized_path}") unless record[:comment_length].zero?
211
- local_extra = source.byteslice(entry.local_header_range.start_byte + 28, 2).unpack1("v")
212
- raise render_error("local_header_extra_field", schema_path(entry.normalized_path), "raw-preserving ZIP renderer does not yet preserve local extra fields for #{entry.normalized_path}") unless local_extra.zero?
231
+ unless (record[:flags] & 0x1).zero?
232
+ raise render_error('encrypted_member', schema_path(entry.normalized_path),
233
+ "raw-preserving ZIP renderer rejects encrypted member #{entry.normalized_path}")
234
+ end
235
+ unless [
236
+ 0, 8
237
+ ].include?(record[:method])
238
+ raise render_error('unsupported_compression', schema_path(entry.normalized_path),
239
+ "raw-preserving ZIP renderer rejects unsupported compression #{entry.compression.inspect}")
240
+ end
241
+ unless record[:extra_length].zero?
242
+ raise render_error('central_directory_extra_field', schema_path(entry.normalized_path),
243
+ "raw-preserving ZIP renderer does not yet preserve central-directory extra fields for #{entry.normalized_path}")
244
+ end
245
+ unless record[:comment_length].zero?
246
+ raise render_error('member_comment', schema_path(entry.normalized_path),
247
+ "raw-preserving ZIP renderer does not yet preserve member comments for #{entry.normalized_path}")
248
+ end
249
+
250
+ local_extra = source.byteslice(entry.local_header_range.start_byte + 28, 2).unpack1('v')
251
+ return if local_extra.zero?
252
+
253
+ raise render_error('local_header_extra_field', schema_path(entry.normalized_path),
254
+ "raw-preserving ZIP renderer does not yet preserve local extra fields for #{entry.normalized_path}")
213
255
  end
214
256
 
215
257
  def unsafe_entries(entries, records)
216
258
  seen = {}
217
259
  entries.flat_map do |entry|
218
260
  list = []
219
- list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path, category: "path_traversal", reason: "entry escapes the archive root") if escapes_root?(entry.path)
220
- list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path, category: "duplicate_normalized_path", reason: "normalized path collides with an existing entry") if seen[entry.normalized_path] && seen[entry.normalized_path] != entry.path
221
- list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path, category: "encrypted_member", reason: "encrypted member cannot be rendered by the default provider") unless (records[entry.path][:flags] & 0x1).zero?
222
- list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path, category: "signing_sensitive_member", reason: "signature-bearing member mutation is not enabled") if signing_sensitive?(entry.normalized_path)
261
+ if escapes_root?(entry.path)
262
+ list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
263
+ category: 'path_traversal', reason: 'entry escapes the archive root')
264
+ end
265
+ if seen[entry.normalized_path] && seen[entry.normalized_path] != entry.path
266
+ list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
267
+ category: 'duplicate_normalized_path', reason: 'normalized path collides with an existing entry')
268
+ end
269
+ unless (records[entry.path][:flags] & 0x1).zero?
270
+ list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
271
+ category: 'encrypted_member', reason: 'encrypted member cannot be rendered by the default provider')
272
+ end
273
+ if signing_sensitive?(entry.normalized_path)
274
+ list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
275
+ category: 'signing_sensitive_member', reason: 'signature-bearing member mutation is not enabled')
276
+ end
223
277
  seen[entry.normalized_path] = entry.path
224
278
  list
225
279
  end
@@ -228,57 +282,79 @@ module Zip
228
282
  def rendered_local_record(entry, content, method, offset)
229
283
  payload = method == 8 ? Zlib::Deflate.deflate(content) : content
230
284
  crc = Zlib.crc32(content)
231
- header = [LOCAL, 20, 0, method].pack("Vvvv") + DOS_EPOCH + [crc, payload.bytesize, content.bytesize, entry.path.bytesize, 0].pack("VVVvv") + entry.path
232
- [header + payload, { name: entry.path, method: method, crc32: crc, compressed_size: payload.bytesize, uncompressed_size: content.bytesize, offset: offset, flags: 0 }]
285
+ header = [LOCAL, 20, 0,
286
+ method].pack('Vvvv') + DOS_EPOCH + [crc, payload.bytesize, content.bytesize, entry.path.bytesize,
287
+ 0].pack('VVVvv') + entry.path
288
+ [header + payload,
289
+ { name: entry.path, method: method, crc32: crc, compressed_size: payload.bytesize, uncompressed_size: content.bytesize,
290
+ offset: offset, flags: 0 }]
233
291
  end
234
292
 
235
293
  def central_directory_record(record)
236
- [CENTRAL, 20, 20, record[:flags], record[:method]].pack("Vvvvv") + DOS_EPOCH + [record[:crc32], record[:compressed_size], record[:uncompressed_size], record[:name].bytesize, 0, 0, 0, 0, 0, record[:offset]].pack("VVVvvvvvVV") + record[:name]
294
+ [CENTRAL, 20, 20, record[:flags],
295
+ record[:method]].pack('Vvvvv') + DOS_EPOCH + [record[:crc32], record[:compressed_size], record[:uncompressed_size],
296
+ record[:name].bytesize, 0, 0, 0, 0, 0, record[:offset]].pack('VVVvvvvvVV') + record[:name]
237
297
  end
238
298
 
239
299
  def eocd_record(entries, size, offset)
240
- [EOCD, 0, 0, entries, entries, size, offset, 0].pack("VvvvvVVv")
300
+ [EOCD, 0, 0, entries, entries, size, offset, 0].pack('VvvvvVVv')
241
301
  end
242
302
 
243
- def raw_local_record_ranges(source, entries)
303
+ def raw_local_record_ranges(_source, entries)
244
304
  ordered = entries.values.sort_by { |entry| entry.local_header_range.start_byte }
245
305
  ordered.each_with_index.to_h do |entry, index|
246
306
  end_byte = ordered[index + 1]&.local_header_range&.start_byte || entry.central_directory_range.start_byte
247
- [entry.normalized_path, TreeHaver::ByteRange.new(start_byte: entry.local_header_range.start_byte, end_byte: end_byte)]
307
+ [entry.normalized_path,
308
+ TreeHaver::ByteRange.new(start_byte: entry.local_header_range.start_byte, end_byte: end_byte)]
248
309
  end
249
310
  end
250
311
 
251
312
  def central_record_from_entry(entry, offset)
252
- { name: entry.path, method: entry.compression == "deflate" ? 8 : 0, crc32: entry.crc32.to_i(16), compressed_size: entry.compressed_size, uncompressed_size: entry.uncompressed_size, offset: offset, flags: 0 }
313
+ { name: entry.path, method: entry.compression == 'deflate' ? 8 : 0, crc32: entry.crc32.to_i(16),
314
+ compressed_size: entry.compressed_size, uncompressed_size: entry.uncompressed_size, offset: offset, flags: 0 }
253
315
  end
254
316
 
255
317
  def path_entry(name, content)
256
- TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name), directory: name.end_with?("/"), compression: "stored", compressed_size: content.bytesize, uncompressed_size: content.bytesize, crc32: "%08x" % Zlib.crc32(content), local_header_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), data_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), central_directory_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0))
318
+ TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name),
319
+ directory: name.end_with?('/'), compression: 'stored', compressed_size: content.bytesize, uncompressed_size: content.bytesize, crc32: '%08x' % Zlib.crc32(content), local_header_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), data_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), central_directory_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0))
257
320
  end
258
321
 
259
322
  def decision(report, path, operation, disposition, reason)
260
- report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation, disposition: disposition, reason: reason)
323
+ report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation,
324
+ disposition: disposition, reason: reason)
261
325
  report.merge_report.rewritten_nodes << schema_path(path)
262
326
  end
263
327
 
264
328
  def normalize_zip_path(path)
265
- path.tr("\\", "/").split("/").each_with_object([]) { |part, stack| part == ".." ? stack.pop : stack << part unless part == "." }.join("/")
329
+ path.tr('\\', '/').split('/').each_with_object([]) do |part, stack|
330
+ unless part == '.'
331
+ part == '..' ? stack.pop : stack << part
332
+ end
333
+ end.join('/')
266
334
  end
267
335
 
268
336
  def compression_name(method)
269
337
  case method
270
- when 0 then "stored"
271
- when 8 then "deflate"
338
+ when 0 then 'stored'
339
+ when 8 then 'deflate'
272
340
  else "method-#{method}"
273
341
  end
274
342
  end
275
343
 
276
344
  def escapes_root?(path)
277
- path.start_with?("/") || path.tr("\\", "/").split("/").then { |parts| depth = 0; parts.any? { |part| part == ".." ? (depth -= 1) : (depth += 1 unless part == "."); depth.negative? } }
345
+ path.start_with?('/') || path.tr('\\', '/').split('/').then do |parts|
346
+ depth = 0
347
+ parts.any? do |part|
348
+ part == '..' ? (depth -= 1) : (depth += 1 unless part == '.')
349
+ depth.negative?
350
+ end
351
+ end
278
352
  end
279
353
 
280
354
  def signing_sensitive?(path)
281
- path.upcase.start_with?("META-INF/") && [".RSA", ".DSA", ".EC", ".SF"].any? { |suffix| path.upcase.end_with?(suffix) }
355
+ path.upcase.start_with?('META-INF/') && ['.RSA', '.DSA', '.EC', '.SF'].any? do |suffix|
356
+ path.upcase.end_with?(suffix)
357
+ end
282
358
  end
283
359
 
284
360
  def same_entry?(left, right)
@@ -290,10 +366,11 @@ module Zip
290
366
  end
291
367
 
292
368
  def nested_family(path)
293
- return "markdown" if path.match?(/\.m(?:d|arkdown)\z/i)
294
- return "json" if path.end_with?(".json")
295
- return "yaml" if path.match?(/\.ya?ml\z/i)
296
- return "xml" if path.end_with?(".xml")
369
+ return 'markdown' if path.match?(/\.m(?:d|arkdown)\z/i)
370
+ return 'json' if path.end_with?('.json')
371
+ return 'yaml' if path.match?(/\.ya?ml\z/i)
372
+
373
+ 'xml' if path.end_with?('.xml')
297
374
  end
298
375
 
299
376
  def schema_path(path)
@@ -301,7 +378,7 @@ module Zip
301
378
  end
302
379
 
303
380
  def diagnostic(category, schema_path, message)
304
- TreeHaver::BinaryDiagnostic.new(severity: "error", category: category, message: message, schema_path: schema_path)
381
+ Binary::Merge.unsafe_diagnostic(schema_path: schema_path, byte_range: nil, message: message, category: category)
305
382
  end
306
383
 
307
384
  def render_error(category, schema_path, message)
@@ -309,3 +386,8 @@ module Zip
309
386
  end
310
387
  end
311
388
  end
389
+
390
+ require 'binary/merge'
391
+ require_relative 'merge/backend'
392
+
393
+ Zip::Merge.register_backend!
data/lib/zip-merge.rb CHANGED
@@ -1,3 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "zip/merge"
3
+ require_relative 'zip/merge'
4
+ require 'version_gem'
5
+ require_relative 'zip/merge/version'
6
+
7
+ Zip::Merge::Version.class_eval do
8
+ extend VersionGem::Basic
9
+ end
data/sig/zip/merge.rbs ADDED
@@ -0,0 +1,8 @@
1
+ module Zip
2
+ module Merge
3
+ module Version
4
+ VERSION: String
5
+ end
6
+ VERSION: String
7
+ end
8
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip-merge
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain:
10
10
  - |
11
11
  -----BEGIN CERTIFICATE-----
@@ -37,46 +37,297 @@ cert_chain:
37
37
  -----END CERTIFICATE-----
38
38
  date: 1980-01-02 00:00:00.000000000 Z
39
39
  dependencies:
40
+ - !ruby/object:Gem::Dependency
41
+ name: binary-merge
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 7.1.1
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 7.1.1
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: tree_haver
42
56
  requirement: !ruby/object:Gem::Requirement
43
57
  requirements:
44
58
  - - '='
45
59
  - !ruby/object:Gem::Version
46
- version: 7.0.0
60
+ version: 7.1.1
47
61
  type: :runtime
48
62
  prerelease: false
49
63
  version_requirements: !ruby/object:Gem::Requirement
50
64
  requirements:
51
65
  - - '='
52
66
  - !ruby/object:Gem::Version
53
- version: 7.0.0
54
- description: Portable ZIP inventory, planning, nested dispatch, and raw-preservation
55
- rendering helpers for Structured Merge.
67
+ version: 7.1.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: version_gem
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.1'
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.1.15
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '1.1'
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 1.1.15
88
+ - !ruby/object:Gem::Dependency
89
+ name: kettle-dev
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '2.5'
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 2.5.17
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '2.5'
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.5.17
108
+ - !ruby/object:Gem::Dependency
109
+ name: bundler-audit
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: 0.9.3
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: 0.9.3
122
+ - !ruby/object:Gem::Dependency
123
+ name: rake
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '13.0'
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '13.0'
136
+ - !ruby/object:Gem::Dependency
137
+ name: require_bench
138
+ requirement: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '1.0'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 1.0.4
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.0'
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 1.0.4
156
+ - !ruby/object:Gem::Dependency
157
+ name: anonymous_loader
158
+ requirement: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: '0.1'
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 0.1.3
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '0.1'
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: 0.1.3
176
+ - !ruby/object:Gem::Dependency
177
+ name: appraisal2
178
+ requirement: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - "~>"
181
+ - !ruby/object:Gem::Version
182
+ version: '3.2'
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: 3.2.0
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - "~>"
191
+ - !ruby/object:Gem::Version
192
+ version: '3.2'
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: 3.2.0
196
+ - !ruby/object:Gem::Dependency
197
+ name: kettle-test
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - "~>"
201
+ - !ruby/object:Gem::Version
202
+ version: '2.0'
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: 2.0.19
206
+ type: :development
207
+ prerelease: false
208
+ version_requirements: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - "~>"
211
+ - !ruby/object:Gem::Version
212
+ version: '2.0'
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 2.0.19
216
+ - !ruby/object:Gem::Dependency
217
+ name: turbo_tests2
218
+ requirement: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '3.2'
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: 3.2.4
226
+ type: :development
227
+ prerelease: false
228
+ version_requirements: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - "~>"
231
+ - !ruby/object:Gem::Version
232
+ version: '3.2'
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: 3.2.4
236
+ - !ruby/object:Gem::Dependency
237
+ name: ruby-progressbar
238
+ requirement: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: '1.13'
243
+ type: :development
244
+ prerelease: false
245
+ version_requirements: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - "~>"
248
+ - !ruby/object:Gem::Version
249
+ version: '1.13'
250
+ - !ruby/object:Gem::Dependency
251
+ name: stone_checksums
252
+ requirement: !ruby/object:Gem::Requirement
253
+ requirements:
254
+ - - "~>"
255
+ - !ruby/object:Gem::Version
256
+ version: '1.0'
257
+ - - ">="
258
+ - !ruby/object:Gem::Version
259
+ version: 1.0.8
260
+ type: :development
261
+ prerelease: false
262
+ version_requirements: !ruby/object:Gem::Requirement
263
+ requirements:
264
+ - - "~>"
265
+ - !ruby/object:Gem::Version
266
+ version: '1.0'
267
+ - - ">="
268
+ - !ruby/object:Gem::Version
269
+ version: 1.0.8
270
+ - !ruby/object:Gem::Dependency
271
+ name: gitmoji-regex
272
+ requirement: !ruby/object:Gem::Requirement
273
+ requirements:
274
+ - - "~>"
275
+ - !ruby/object:Gem::Version
276
+ version: '2.0'
277
+ - - ">="
278
+ - !ruby/object:Gem::Version
279
+ version: 2.0.11
280
+ type: :development
281
+ prerelease: false
282
+ version_requirements: !ruby/object:Gem::Requirement
283
+ requirements:
284
+ - - "~>"
285
+ - !ruby/object:Gem::Version
286
+ version: '2.0'
287
+ - - ">="
288
+ - !ruby/object:Gem::Version
289
+ version: 2.0.11
290
+ description: "☯️ Portable ZIP inventory, planning, nested dispatch, and raw-preservation
291
+ rendering helpers for Structured Merge."
56
292
  email:
57
- - info@structuredmerge.org
293
+ - floss@galtzo.com
58
294
  executables: []
59
295
  extensions: []
60
296
  extra_rdoc_files: []
61
297
  files:
298
+ - LICENSE.md
299
+ - README.md
62
300
  - lib/zip-merge.rb
63
301
  - lib/zip/merge.rb
302
+ - lib/zip/merge/backend.rb
64
303
  - lib/zip/merge/version.rb
304
+ - sig/zip/merge.rbs
65
305
  homepage: https://github.com/structuredmerge/structuredmerge-ruby
66
306
  licenses:
67
307
  - AGPL-3.0-only
68
308
  - PolyForm-Small-Business-1.0.0
69
309
  metadata:
70
310
  homepage_uri: https://structuredmerge.org
71
- source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.0.0
72
- changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.0.0/CHANGELOG.md
311
+ source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.1
312
+ changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.1/CHANGELOG.md
73
313
  bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
74
- documentation_uri: https://www.rubydoc.info/gems/zip-merge/7.0.0
314
+ documentation_uri: https://www.rubydoc.info/gems/zip-merge/7.1.1
75
315
  funding_uri: https://github.com/sponsors/pboling
76
316
  wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
317
+ news_uri: https://www.railsbling.com/tags/zip-merge
77
318
  discord_uri: https://discord.gg/3qme4XHNKN
319
+ mailing_list_uri: https://www.rubyforum.org/tag/structuredmerge
78
320
  rubygems_mfa_required: 'true'
79
- rdoc_options: []
321
+ rdoc_options:
322
+ - "--title"
323
+ - zip-merge - ☯️ Structured Merge ZIP merge planning and rendering helpers for Ruby
324
+ - "--main"
325
+ - README.md
326
+ - "--exclude"
327
+ - "^sig/"
328
+ - "--line-numbers"
329
+ - "--inline-source"
330
+ - "--quiet"
80
331
  require_paths:
81
332
  - lib
82
333
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -90,7 +341,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
341
  - !ruby/object:Gem::Version
91
342
  version: '0'
92
343
  requirements: []
93
- rubygems_version: 4.0.10
344
+ rubygems_version: 4.0.17
94
345
  specification_version: 4
95
- summary: Structured Merge ZIP merge planning and rendering helpers for Ruby
346
+ summary: "☯️ Structured Merge ZIP merge planning and rendering helpers for Ruby"
96
347
  test_files: []
metadata.gz.sig CHANGED
Binary file