kettle-dev 1.1.45 → 1.1.47

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: 8069b7963c8a87d179dd475edcbb066b6a5c1fd7d322b338819dfb86379aa3c8
4
- data.tar.gz: e6a69655d6c2803aa1e27fe3c91048c2d98f2580260fce709220d09a1f5b0e02
3
+ metadata.gz: ce61aa17df248a723743179807ea6da8b1c139dbd2555459a69035b4a33338bd
4
+ data.tar.gz: 7eb08bdf8772350131a62caa2ac893de26871ab093d671ee798b5a1edb7a97b0
5
5
  SHA512:
6
- metadata.gz: cc02fca8fc6fc2625d433e6be1c1f16a6165a31af84fa6096c696de8363a887ba3b6398063e9f4996b6d2f6832de0de4e1b584f95ec74434f66d66b5a9525c1f
7
- data.tar.gz: 850ad6fc5a7606081f4da6d77aae2613a15b7b239f1a88478727fdd38aca2176afbf3386868b2668508099e487924791644d5d61870b37cfdb3315f759e45335
6
+ metadata.gz: 7936fd630aab1b64ab97ae9b52a570524b5c20ddc576bddbb10df3ee6dd05e26a736e33c34cbe3ba7cebd903c112d68440d9863f5ade11ae7c18f84cc1d08b68
7
+ data.tar.gz: cec2cbdcab872105c822f07d0539ccd08c8ad66aa1a1e7ded93a1c8d3f790a1efc48449f0810dc4128e201d32aa517fde4c5054f4a7e604c1b4e0cda630d3155
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,5 +1,5 @@
1
1
  ⚡️ A message from a fellow meat-based-AI ⚡️
2
- - [❤️] Finely-crafted open-source tools like <%= @gem_name %> (& many more) are a full-time endeavor.
2
+ - [❤️] Finely-crafted open-source tools like <%= @gem_name %> (& many more) require time and effort.
3
3
  - [❤️] Though I adore my work, it lacks financial sustainability.
4
4
  - [❤️] Please, help me continue enhancing your tools by becoming a sponsor:
5
5
  - [💲] https://liberapay.com/pboling/donate
@@ -63,3 +63,5 @@ jobs:
63
63
  run: bundle exec appraisal ${{ matrix.appraisal }} bundle
64
64
  - name: Run ${{ matrix.appraisal }} checks via ${{ matrix.exec_cmd }}
65
65
  run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
66
+ - name: Validate RBS Types
67
+ run: bundle exec appraisal ${{ matrix.appraisal }} bin/rbs validate
data/CHANGELOG.md CHANGED
@@ -30,6 +30,37 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.1.47] - 2025-11-06
34
+
35
+ - TAG: [v1.1.47][1.1.47t]
36
+ - COVERAGE: 95.68% -- 4054/4237 lines in 26 files
37
+ - BRANCH COVERAGE: 80.45% -- 1675/2082 branches in 26 files
38
+ - 79.89% documented
39
+
40
+ ### Added
41
+
42
+ - Handle custom dependencies in Gemfiles gracefully
43
+ - Intelligent templating of Appraisals
44
+
45
+ ### Fixed
46
+
47
+ - Typos in funding links
48
+
49
+ ## [1.1.46] - 2025-11-04
50
+
51
+ - TAG: [v1.1.46][1.1.46t]
52
+ - COVERAGE: 96.25% -- 3958/4112 lines in 26 files
53
+ - BRANCH COVERAGE: 80.95% -- 1636/2021 branches in 26 files
54
+ - 79.68% documented
55
+
56
+ ### Added
57
+
58
+ - Validate RBS Types within style workflow
59
+
60
+ ### Fixed
61
+
62
+ - typos in README.md
63
+
33
64
  ## [1.1.45] - 2025-10-31
34
65
 
35
66
  - TAG: [v1.1.45][1.1.45t]
@@ -1252,7 +1283,11 @@ Please file a bug if you notice a violation of semantic versioning.
1252
1283
  - Selecting will run the selected workflow via `act`
1253
1284
  - This may move to its own gem in the future.
1254
1285
 
1255
- [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.45...HEAD
1286
+ [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.47...HEAD
1287
+ [1.1.47]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.46...v1.1.47
1288
+ [1.1.47t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.47
1289
+ [1.1.46]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.45...v1.1.46
1290
+ [1.1.46t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.46
1256
1291
  [1.1.45]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.44...v1.1.45
1257
1292
  [1.1.45t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.45
1258
1293
  [1.1.44]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.43...v1.1.44
data/FUNDING.md CHANGED
@@ -6,7 +6,7 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
6
6
 
7
7
  [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal]
8
8
 
9
- [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
9
+ [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
10
10
 
11
11
  [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
12
12
  [⛳liberapay]: https://liberapay.com/pboling/donate
@@ -46,7 +46,7 @@ To join the community or get help 👇️ Join the Discord.
46
46
 
47
47
  To say "thanks for maintaining such a great tool" ☝️ Join the Discord or 👇️ send money.
48
48
 
49
- [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
49
+ [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
50
50
 
51
51
  # Another Way to Support Open Source Software
52
52
 
@@ -6,7 +6,7 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
6
6
 
7
7
  [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal]
8
8
 
9
- [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
9
+ [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
10
10
 
11
11
  [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
12
12
  [⛳liberapay]: https://liberapay.com/pboling/donate
data/README.md CHANGED
@@ -652,7 +652,7 @@ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed
652
652
 
653
653
  **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
654
654
 
655
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
655
+ [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
656
656
 
657
657
  ## 🔐 Security
658
658
 
@@ -779,7 +779,7 @@ To join the community or get help 👇️ Join the Discord.
779
779
 
780
780
  To say "thanks!" ☝️ Join the Discord or 👇️ send money.
781
781
 
782
- [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
782
+ [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
783
783
 
784
784
  ### Please give the project a star ⭐ ♥.
785
785
 
@@ -965,9 +965,9 @@ Thanks for RTFM. ☺️
965
965
  [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
966
966
  [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
967
967
  [📌gitmoji]:https://gitmoji.dev
968
- [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
968
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
969
969
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
970
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.112-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
970
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.237-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
971
971
  [🔐security]: SECURITY.md
972
972
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
973
973
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/README.md.example CHANGED
@@ -225,7 +225,7 @@ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed
225
225
 
226
226
  **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
227
227
 
228
- [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
228
+ [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
229
229
 
230
230
  ## 🔐 Security
231
231
 
@@ -352,7 +352,7 @@ To join the community or get help 👇️ Join the Discord.
352
352
 
353
353
  To say "thanks!" ☝️ Join the Discord or 👇️ send money.
354
354
 
355
- [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
355
+ [![Sponsor kettle-rb/kettle-dev on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
356
356
 
357
357
  ### Please give the project a star ⭐ ♥.
358
358
 
@@ -543,9 +543,9 @@ Thanks for RTFM. ☺️
543
543
  [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
544
544
  [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
545
545
  [📌gitmoji]:https://gitmoji.dev
546
- [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
546
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
547
547
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
548
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.112-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
548
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.237-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
549
549
  [🔐security]: SECURITY.md
550
550
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
551
551
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -202,7 +202,7 @@ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed
202
202
 
203
203
  **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
204
204
 
205
- [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
205
+ [![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 on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
206
206
 
207
207
  ## 🔐 Security
208
208
 
@@ -329,7 +329,7 @@ To join the community or get help 👇️ Join the Discord.
329
329
 
330
330
  To say "thanks!" ☝️ Join the Discord or 👇️ send money.
331
331
 
332
- [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
332
+ [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
333
333
 
334
334
  ### Please give the project a star ⭐ ♥.
335
335
 
@@ -509,7 +509,7 @@ Thanks for RTFM. ☺️
509
509
  [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
510
510
  [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
511
511
  [📌gitmoji]:https://gitmoji.dev
512
- [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
512
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
513
513
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
514
514
  [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.076-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
515
515
  [🔐security]: SECURITY.md
data/Rakefile.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # kettle-dev Rakefile v1.1.45 - 2025-10-31
3
+ # kettle-dev Rakefile v1.1.47 - 2025-11-06
4
4
  # Ruby 2.3 (Safe Navigation) or higher required
5
5
  #
6
6
  # MIT License (see License.txt)
@@ -2,6 +2,7 @@
2
2
 
3
3
  # External stdlibs
4
4
  require "find"
5
+ require "set"
5
6
 
6
7
  module Kettle
7
8
  module Dev
@@ -277,6 +278,46 @@ module Kettle
277
278
  Kettle::Dev.debug_error(e, __method__)
278
279
  # If replacement fails unexpectedly, proceed with content as-is
279
280
  end
281
+
282
+ # If updating the Appraisals file and a destination already exists,
283
+ # merge appraise blocks: augment matching blocks with missing gem/eval_gemfile lines,
284
+ # preserve destination-only blocks and comments/preamble.
285
+ begin
286
+ if dest_exists && File.basename(dest_path.to_s) == "Appraisals" && File.file?(dest_path.to_s)
287
+ existing = begin
288
+ File.read(dest_path)
289
+ rescue
290
+ ""
291
+ end
292
+ content = merge_appraisals(content, existing)
293
+ end
294
+ rescue StandardError => e
295
+ Kettle::Dev.debug_error(e, __method__)
296
+ # On any error, fall back to generated content
297
+ end
298
+
299
+ # If updating a Gemfile or modular .gemfile and the destination already exists,
300
+ # merge dependency lines from the source into the destination to preserve any
301
+ # user-defined gem entries. We append missing `gem "name"` lines; we never
302
+ # alter or remove existing gem lines in the destination.
303
+ begin
304
+ if dest_exists
305
+ dest_str = dest_path.to_s
306
+ is_gemfile_like = File.basename(dest_str) == "Gemfile" || dest_str.end_with?(".gemfile")
307
+ if is_gemfile_like && File.file?(dest_str)
308
+ begin
309
+ existing = File.read(dest_str)
310
+ content = merge_gemfile_dependencies(content, existing)
311
+ rescue StandardError => e
312
+ Kettle::Dev.debug_error(e, __method__)
313
+ # If merging fails, fall back to writing generated content
314
+ end
315
+ end
316
+ end
317
+ rescue StandardError => e
318
+ Kettle::Dev.debug_error(e, __method__)
319
+ end
320
+
280
321
  write_file(dest_path, content)
281
322
  begin
282
323
  # Ensure executable bit for git hook scripts when writing under .git-hooks
@@ -291,6 +332,192 @@ module Kettle
291
332
  puts "Wrote #{dest_path}"
292
333
  end
293
334
 
335
+ # Merge gem dependency lines from a source Gemfile-like content into an existing
336
+ # destination Gemfile-like content. Existing gem lines in the destination win;
337
+ # we only append missing gem declarations from the source at the end of the file.
338
+ # This is deliberately conservative and avoids attempting to relocate gems inside
339
+ # group/platform blocks or reconcile version constraints.
340
+ # @param src_content [String]
341
+ # @param dest_content [String]
342
+ # @return [String] merged content
343
+ def merge_gemfile_dependencies(src_content, dest_content)
344
+ begin
345
+ gem_re = /^\s*gem\s+['"]([^'"\s]+)['"].*$/
346
+ # Collect first occurrence of each gem line in source
347
+ src_gems = {}
348
+ src_content.each_line do |ln|
349
+ next if ln.strip.start_with?("#")
350
+ if (m = ln.match(gem_re))
351
+ name = m[1]
352
+ src_gems[name] ||= ln.rstrip
353
+ end
354
+ end
355
+
356
+ # Index existing gems in destination
357
+ dest_gems = {}
358
+ dest_content.each_line do |ln|
359
+ next if ln.strip.start_with?("#")
360
+ if (m = ln.match(gem_re))
361
+ dest_gems[m[1]] = true
362
+ end
363
+ end
364
+
365
+ missing = src_gems.keys.reject { |n| dest_gems.key?(n) }
366
+ return dest_content if missing.empty?
367
+
368
+ out = dest_content.dup
369
+ out << "\n" unless out.end_with?("\n") || out.empty?
370
+ out << missing.map { |n| src_gems[n] }.join("\n")
371
+ out << "\n"
372
+ out
373
+ rescue StandardError => e
374
+ Kettle::Dev.debug_error(e, __method__)
375
+ dest_content
376
+ end
377
+ end
378
+
379
+ # Merge Appraisals template into existing Appraisals file.
380
+ # Rules:
381
+ # - For each appraise "name" block in template:
382
+ # * If destination has same block, ensure all gem/ eval_gemfile lines from template
383
+ # exist in destination (append missing just before end), keep other dest lines.
384
+ # Use template's contiguous header comment lines (immediately preceding the appraise line)
385
+ # if any; otherwise retain destination's header comments.
386
+ # * If destination lacks the block, add the full template block (with its header).
387
+ # - Preserve destination-only blocks (not present in template) unchanged and after
388
+ # the merged template-ordered blocks.
389
+ # - Preamble (content before first appraise) comes from template when present, else destination.
390
+ def merge_appraisals(template_content, dest_content)
391
+ begin
392
+ parse_blocks = lambda do |text|
393
+ lines = text.lines
394
+ blocks = []
395
+ i = 0
396
+ while i < lines.length
397
+ line = lines[i]
398
+ if line =~ /^\s*appraise\s+["']([^"']+)["']\s+do\s*$/
399
+ name = $1
400
+ # collect header comment lines immediately preceding (contiguous, no blank between comment group and appraise line)
401
+ header_lines = []
402
+ j = i - 1
403
+ while j >= 0
404
+ prev = lines[j]
405
+ break if prev.strip.empty?
406
+ if prev.lstrip.start_with?("#")
407
+ header_lines.unshift(prev)
408
+ j -= 1
409
+ else
410
+ break
411
+ end
412
+ end
413
+ body_lines = []
414
+ i += 1
415
+ while i < lines.length
416
+ l2 = lines[i]
417
+ if l2 =~ /^\s*end\s*$/
418
+ end_line = l2
419
+ blocks << {
420
+ name: name,
421
+ header: header_lines.dup,
422
+ body: body_lines.dup,
423
+ end_line: end_line,
424
+ raw_order: blocks.length,
425
+ original_indices: (j ? (j + 1)..i : i),
426
+ }
427
+ break
428
+ else
429
+ body_lines << l2
430
+ end
431
+ i += 1
432
+ end
433
+ end
434
+ i += 1
435
+ end
436
+ preamble = if blocks.empty?
437
+ text
438
+ else
439
+ # preamble = lines from start up to first block start (exclusive)
440
+ first_block = blocks.first
441
+ # Take lines up to first occurrence of the appraise line (supports either quote type)
442
+ re = /^\s*appraise\s+["']#{Regexp.escape(first_block[:name])}["']\s+do\s*$/
443
+ idx = lines.index { |l| l =~ re } || 0
444
+ lines[0...idx].join
445
+ end
446
+ {blocks: blocks, preamble: preamble}
447
+ end
448
+
449
+ tmpl = parse_blocks.call(template_content)
450
+ dest = parse_blocks.call(dest_content)
451
+ tmpl_blocks = tmpl[:blocks]
452
+ dest_blocks = dest[:blocks]
453
+ dest_by_name = dest_blocks.map { |b| [b[:name], b] }.to_h
454
+
455
+ merged_blocks_strings = []
456
+ gem_or_eval_re = /^\s*(?:gem|eval_gemfile)\b/
457
+
458
+ tmpl_blocks.each do |tb|
459
+ if (db = dest_by_name[tb[:name]])
460
+ # Merge lines
461
+ existing_lines = db[:body].map(&:rstrip)
462
+ existing_set = existing_lines.to_set
463
+ # Collect template gem/eval lines
464
+ tmpl_needed = tb[:body].select { |l| gem_or_eval_re =~ l }
465
+ additions = []
466
+ tmpl_needed.each do |l|
467
+ line_key = l.rstrip
468
+ additions << l unless existing_set.include?(line_key)
469
+ end
470
+ merged_body = db[:body].dup
471
+ unless additions.empty?
472
+ # insert before end (just append; body excludes 'end')
473
+ merged_body += additions
474
+ end
475
+ header = tb[:header].any? ? tb[:header] : db[:header]
476
+ block_text = +""
477
+ block_text << "\n" unless merged_blocks_strings.empty?
478
+ header.each { |hl| block_text << hl } if header.any?
479
+ block_text << "appraise \"#{tb[:name]}\" do\n"
480
+ merged_body.each { |bl| block_text << bl }
481
+ block_text << db[:end_line]
482
+ merged_blocks_strings << block_text
483
+ dest_by_name.delete(tb[:name])
484
+ else
485
+ # New block from template
486
+ block_text = +""
487
+ block_text << "\n" unless merged_blocks_strings.empty?
488
+ tb[:header].each { |hl| block_text << hl } if tb[:header].any?
489
+ block_text << "appraise \"#{tb[:name]}\" do\n"
490
+ tb[:body].each { |bl| block_text << bl }
491
+ block_text << tb[:end_line]
492
+ merged_blocks_strings << block_text
493
+ end
494
+ end
495
+ # Append destination-only blocks preserving their original text
496
+ dest_remaining_order = dest_blocks.select { |b| dest_by_name.key?(b[:name]) }
497
+ dest_remaining_order.each do |b|
498
+ block_text = +""
499
+ block_text << "\n" unless merged_blocks_strings.empty?
500
+ b[:header].each { |hl| block_text << hl } if b[:header].any?
501
+ block_text << "appraise \"#{b[:name]}\" do\n"
502
+ b[:body].each { |bl| block_text << bl }
503
+ block_text << b[:end_line]
504
+ merged_blocks_strings << block_text
505
+ end
506
+
507
+ preamble = tmpl[:preamble].to_s.strip.empty? ? dest[:preamble] : tmpl[:preamble]
508
+ out = +""
509
+ out << preamble unless preamble.nil? || preamble.empty?
510
+ out << "\n" unless out.end_with?("\n")
511
+ out << merged_blocks_strings.join
512
+ out << "\n" unless out.end_with?("\n")
513
+ out
514
+ rescue StandardError => e
515
+ Kettle::Dev.debug_error(e, __method__)
516
+ # Fallback: prefer destination (user changes) and append template content to allow manual reconciliation
517
+ dest_content + "\n# --- TEMPLATE APPRAISALS (unmerged) ---\n" + template_content
518
+ end
519
+ end
520
+
294
521
  # Copy a directory tree, prompting before creating or overwriting.
295
522
  # @return [void]
296
523
  def copy_dir_with_prompt(src_dir, dest_dir)
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.1.45"
9
+ VERSION = "1.1.47"
10
10
 
11
11
  module_function
12
12
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.45
4
+ version: 1.1.47
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -396,10 +396,10 @@ licenses:
396
396
  - MIT
397
397
  metadata:
398
398
  homepage_uri: https://kettle-dev.galtzo.com/
399
- source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.45
400
- changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.45/CHANGELOG.md
399
+ source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.47
400
+ changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.47/CHANGELOG.md
401
401
  bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
402
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.45
402
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.47
403
403
  funding_uri: https://github.com/sponsors/pboling
404
404
  wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
405
405
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file