kettle-dev 1.1.18 → 1.1.20

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: c48a2809e582b5f6d73b3c531e4a8336d5fec8a261003d469c0762c6b0280cfd
4
- data.tar.gz: aca0074c5d5839eb5d275d82bc82f2c47bcee8db8828d3ad1d5d5fac69878147
3
+ metadata.gz: 898754550e179a5b5757c58d45a2dd50a61de0334051ab4947769cc4a33c6624
4
+ data.tar.gz: f2b3cb209b23f9074086cb22e52e674e2b0ba84788241b2222bdca9bf26d5699
5
5
  SHA512:
6
- metadata.gz: 495fb6a4ffe94e37b112735319371ea3122bddbbef3b06d681595b5a3769463b33008052c164af688221a4205e859b38b76076289ea71e7ecca519b6a930c012
7
- data.tar.gz: 88db9bbbd711aeb193b1c233da17630de0e6aeb70c35a68fe06f875c628bef5892640177dea71befa8528e9ed29de987b0f52201e20721aaef8016b5f6824422
6
+ metadata.gz: 73108e8b6bc65bd2b11e04b0a0cb20ac2fa0f7c5f3b83533c469ece206af419e1c1033bb61253815e1c98b84f21122176825e16abbb2121478c6154647812d33
7
+ data.tar.gz: 53cf04abaea992d6a6c4047e7be8292c4eb8d452614755aeb368b37d7d8f950bc42271556a39b873cf683950f49db3325b5a2245cb4687f07776044f4f562a78
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,40 @@
1
+ name: Apache SkyWalking Eyes
2
+
3
+ permissions:
4
+ contents: read
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - 'main'
10
+ - '*-stable'
11
+ tags:
12
+ - '!*' # Do not execute on tags
13
+ pull_request:
14
+ branches:
15
+ - '*'
16
+ # Allow manually triggering the workflow.
17
+ workflow_dispatch:
18
+
19
+ # Cancels all previous workflow runs for the same branch that have not yet completed.
20
+ concurrency:
21
+ # The concurrency group contains the workflow name and the branch name.
22
+ group: "${{ github.workflow }}-${{ github.ref }}"
23
+ cancel-in-progress: true
24
+
25
+ jobs:
26
+ license-check:
27
+ if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')"
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - name: Checkout
31
+ uses: actions/checkout@v5
32
+
33
+ - name: Check Dependencies' License
34
+ uses: apache/skywalking-eyes/dependency@main
35
+ with:
36
+ config: .licenserc.yaml
37
+ # Ruby packages declared as dependencies in gemspecs or Gemfiles are
38
+ # typically consumed as binaries; enable weak-compatibility
39
+ # so permissive and weak-copyleft combinations are treated as compatible.
40
+ flags: --weak-compatible
data/.licenserc.yaml ADDED
@@ -0,0 +1,7 @@
1
+ header:
2
+ license:
3
+ spdx-id: MIT
4
+
5
+ dependency:
6
+ files:
7
+ - Gemfile.lock
data/Appraisals.example CHANGED
@@ -30,6 +30,8 @@ end
30
30
  # Used for head (nightly) releases of ruby, truffleruby, and jruby.
31
31
  # Split into discrete appraisals if one of them needs a dependency locked discretely.
32
32
  appraise "head" do
33
+ # Why is gem "cgi" here? See: https://github.com/vcr/vcr/issues/1057
34
+ # gem "cgi", ">= 0.5"
33
35
  gem "benchmark", "~> 0.4", ">= 0.4.1"
34
36
  eval_gemfile "modular/x_std_libs.gemfile"
35
37
  end
data/CHANGELOG.md CHANGED
@@ -24,6 +24,33 @@ Please file a bug if you notice a violation of semantic versioning.
24
24
  ### Fixed
25
25
  ### Security
26
26
 
27
+ ## [1.1.20] - 2025-09-15
28
+ - TAG: [v1.1.20][1.1.20t]
29
+ - COVERAGE: 96.80% -- 3660/3781 lines in 25 files
30
+ - BRANCH COVERAGE: 81.65% -- 1504/1842 branches in 25 files
31
+ - 77.01% documented
32
+ ### Added
33
+ - Allow reformating of CHANGELOG.md without version bump
34
+ - `--include=GLOB` includes files not otherwise included in default template
35
+ - more test coverage
36
+ ### Fixed
37
+ - Add .licenserc.yaml to gem package
38
+ - Handling of GFM fenced code blocks in CHANGELOG.md
39
+ - Handling of nested list items in CHANGELOG.md
40
+ - Handling of blank lines around all headings in CHANGELOG.md
41
+
42
+ ## [1.1.19] - 2025-09-14
43
+ - TAG: [v1.1.19][1.1.19t]
44
+ - COVERAGE: 96.58% -- 3531/3656 lines in 25 files
45
+ - BRANCH COVERAGE: 81.11% -- 1443/1779 branches in 25 files
46
+ - 76.88% documented
47
+ ### Added
48
+ - documentation of vcr on Ruby 2.4
49
+ - Apache SkyWalking Eyes dependency license check
50
+ - Added to template
51
+ ### Fixed
52
+ - fix duplicate headings in CHANGELOG.md Unreleased section
53
+
27
54
  ## [1.1.18] - 2025-09-12
28
55
  - TAG: [v1.1.18][1.1.18t]
29
56
  - COVERAGE: 96.24% -- 3477/3613 lines in 25 files
@@ -648,7 +675,11 @@ Please file a bug if you notice a violation of semantic versioning.
648
675
  - Selecting will run the selected workflow via `act`
649
676
  - This may move to its own gem in the future.
650
677
 
651
- [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.18...HEAD
678
+ [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.20...HEAD
679
+ [1.1.20]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.19...v1.1.20
680
+ [1.1.20t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.20
681
+ [1.1.19]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.18...v1.1.19
682
+ [1.1.19t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.19
652
683
  [1.1.18]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.17...v1.1.18
653
684
  [1.1.18t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.18
654
685
  [1.1.17]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.16...v1.1.17
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  # 🍲 Kettle::Dev
11
11
 
12
- [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🔑codecovi]][🔑codecov] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL]
12
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🔑codecovi]][🔑codecov] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
13
13
 
14
14
  `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
15
15
 
@@ -62,7 +62,7 @@ bin/kettle-release
62
62
  | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![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] |
63
63
  | 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] |
64
64
  | 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] [![Wiki][📜wiki-img]][📜wiki] |
65
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
65
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄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] |
66
66
  | 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] |
67
67
  | 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] |
68
68
  | `...` 💖 | [![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] |
@@ -241,10 +241,12 @@ That’s it. Once installed, kettle-dev:
241
241
  - option: force: When truthy (1, true, y, yes), treat all y/N prompts as Yes. Useful for non-interactive runs or to accept defaults quickly. Example: `bundle exec rake kettle:dev:template force=true`
242
242
  - option: allowed: When truthy (1, true, y, yes), resume task after you have reviewed `.envrc`/`.env.local` and run `direnv allow`. If either file is created or updated, the task will abort with instructions unless `allowed=true` is present. Example: `bundle exec rake kettle:dev:install allowed=true`
243
243
  - option: only: A comma-separated list of glob patterns to include in templating. Any destination file whose path+filename does not match one of the patterns is excluded. Patterns are matched relative to your project root. Examples: `only="README.md,.github/**"`, `only="docs/**,lib/**/*.rb"`.
244
+ - option: include: A comma-separated list of glob patterns that opt-in additional, non-default files. Currently, `.github/workflows/discord-notifier.yml` is not copied by default and will only be copied when `include` matches it (e.g., `include=".github/workflows/discord-notifier.yml"`).
244
245
  - `kettle:dev:template` — templates files from this gem into your project (e.g., .github workflows, .devcontainer, .qlty, modular Gemfiles, README/CONTRIBUTING stubs). You can run this independently to refresh templates without the extra install prompts.
245
246
  - option: force: When truthy (1, true, y, yes), treat all y/N prompts as Yes. Useful for non-interactive runs or to accept defaults quickly. Example: `bundle exec rake kettle:dev:template force=true`
246
247
  - option: allowed: When truthy (1, true, y, yes), resume task after you have reviewed `.envrc`/`.env.local` and run `direnv allow`. If either file is created or updated, the task will abort with instructions unless `allowed=true` is present. Example: `bundle exec rake kettle:dev:template allowed=true`
247
248
  - option: only: Same as for install; limits which destination files are written based on glob patterns relative to the project root.
249
+ - option: include: Same as for install; opts into optional files (e.g., `.github/workflows/discord-notifier.yml`).
248
250
 
249
251
  Recommended one-time setup in your project:
250
252
  - Install binstubs so kettle-dev executables are available under `./bin`:
@@ -506,6 +508,7 @@ What it does:
506
508
  - `--force` Pass `force=true` to accept prompts non-interactively.
507
509
  - `--hook_templates=VAL` Pass `hook_templates=VAL` to control git hook templating.
508
510
  - `--only=VAL` Pass `only=VAL` to restrict install scope.
511
+ - `--include=VAL` Pass `include=VAL` to include optional files by glob (see notes below).
509
512
  - `-h`, `--help` Show help.
510
513
  - Behavior:
511
514
  - Verifies a clean git working tree, presence of a Gemfile and a gemspec.
@@ -861,6 +864,8 @@ Thanks for RTFM. ☺️
861
864
  [🚎13-🔒️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/locked_deps.yml/badge.svg
862
865
  [🚎14-🔓️-wf]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml
863
866
  [🚎14-🔓️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml/badge.svg
867
+ [🚎15-🪪-wf]: https://github.com/kettle-rb/kettle-dev/actions/workflows/license-eye.yml
868
+ [🚎15-🪪-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/license-eye.yml/badge.svg
864
869
  [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
865
870
  [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
866
871
  [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
@@ -909,13 +914,15 @@ Thanks for RTFM. ☺️
909
914
  [📌gitmoji]:https://gitmoji.dev
910
915
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
911
916
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
912
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.613-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
917
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.781-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
913
918
  [🔐security]: SECURITY.md
914
919
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
915
920
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
916
921
  [📄license]: LICENSE.txt
917
922
  [📄license-ref]: https://opensource.org/licenses/MIT
918
923
  [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
924
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
925
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
919
926
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
920
927
  [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
921
928
  [🚎yard-current]: http://rubydoc.info/gems/kettle-dev
data/README.md.example CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  # 🍲 Kettle::Dev
11
11
 
12
- [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🔑codecovi]][🔑codecov] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL]
12
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🔑codecovi]][🔑codecov] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
13
13
 
14
14
  `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
15
15
 
@@ -31,10 +31,10 @@
31
31
  | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
32
32
  | Works with MRI Ruby 2 | ![Ruby 2.0 Compat][💎ruby-2.0i] ![Ruby 2.1 Compat][💎ruby-2.1i] ![Ruby 2.2 Compat][💎ruby-2.2i] <br/> [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
33
33
  | Works with MRI Ruby 1 | ![Ruby 1.8 Compat][💎ruby-1.8i] ![Ruby 1.9 Compat][💎ruby-1.9i] |
34
- | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![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] |
34
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![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] |
35
35
  | 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] |
36
36
  | 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] [![Wiki][📜wiki-img]][📜wiki] |
37
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
37
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄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] |
38
38
  | 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] |
39
39
  | 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] |
40
40
  | `...` 💖 | [![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] |
@@ -456,6 +456,8 @@ Thanks for RTFM. ☺️
456
456
  [🚎13-🔒️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/locked_deps.yml/badge.svg
457
457
  [🚎14-🔓️-wf]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml
458
458
  [🚎14-🔓️-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/unlocked_deps.yml/badge.svg
459
+ [🚎15-🪪-wf]: https://github.com/kettle-rb/kettle-dev/actions/workflows/license-eye.yml
460
+ [🚎15-🪪-wfi]: https://github.com/kettle-rb/kettle-dev/actions/workflows/license-eye.yml/badge.svg
459
461
  [💎ruby-1.8i]: https://img.shields.io/badge/Ruby-1.8_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
460
462
  [💎ruby-1.9i]: https://img.shields.io/badge/Ruby-1.9_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
461
463
  [💎ruby-2.0i]: https://img.shields.io/badge/Ruby-2.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
@@ -509,13 +511,15 @@ Thanks for RTFM. ☺️
509
511
  [📌gitmoji]:https://gitmoji.dev
510
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
511
513
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
512
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.613-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
514
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.781-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
513
515
  [🔐security]: SECURITY.md
514
516
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
515
517
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
516
518
  [📄license]: LICENSE.txt
517
519
  [📄license-ref]: https://opensource.org/licenses/MIT
518
520
  [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
521
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
522
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
519
523
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
520
524
  [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
521
525
  [🚎yard-current]: http://rubydoc.info/gems/kettle-dev
data/Rakefile.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # kettle-dev Rakefile v1.1.18 - 2025-09-12
3
+ # kettle-dev Rakefile v1.1.20 - 2025-09-15
4
4
  # Ruby 2.3 (Safe Navigation) or higher required
5
5
  #
6
6
  # MIT License (see License.txt)
data/exe/kettle-dev-setup CHANGED
@@ -44,6 +44,7 @@ if ARGV.include?("-h") || ARGV.include?("--help")
44
44
  --force
45
45
  --hook_templates=VAL
46
46
  --only=VAL
47
+ --include=VAL
47
48
 
48
49
  Environment:
49
50
  DEBUG=true # print backtraces on errors
@@ -149,9 +149,7 @@ Gem::Specification.new do |spec|
149
149
  spec.add_development_dependency("gitmoji-regex", "~> 1.0", ">= 1.0.3") # ruby >= 2.3.0
150
150
 
151
151
  # HTTP recording for deterministic specs
152
- # It seems that somehow just having a newer version of appraisal installed breaks
153
- # Ruby 2.3 and 2.4 even if their bundle specifies an older version,
154
- # and as a result it can only be a dependency in the appraisals.
152
+ # Ruby 2.3 / 2.4 can fail with:
155
153
  # | An error occurred while loading spec_helper.
156
154
  # | Failure/Error: require "vcr"
157
155
  # |
@@ -161,6 +159,11 @@ Gem::Specification.new do |spec|
161
159
  # | # ./spec/config/vcr.rb:3:in `<top (required)>'
162
160
  # | # ./spec/spec_helper.rb:8:in `require_relative'
163
161
  # | # ./spec/spec_helper.rb:8:in `<top (required)>'
164
- # spec.add_development_dependency("vcr", ">= 4") # 6.0 claims to support ruby >= 2.3, but fails on ruby 2.4
165
- # spec.add_development_dependency("webmock", ">= 3") # Last version to support ruby >= 2.3
162
+ # So that's why we need backports.
163
+ # spec.add_development_dependency("backports", "~> 3.25", ">= 3.25.1") # ruby >= 0
164
+ # In Ruby 3.5 (HEAD) the CGI library has been pared down, so we also need to depend on gem "cgi" for ruby@head
165
+ # This is done in the "head" appraisal.
166
+ # See: https://github.com/vcr/vcr/issues/1057
167
+ # spec.add_development_dependency("vcr", ">= 4") # 6.0 claims to support ruby >= 2.3, but fails on ruby 2.4
168
+ # spec.add_development_dependency("webmock", ">= 3") # Last version to support ruby >= 2.3
166
169
  end
@@ -24,9 +24,22 @@ module Kettle
24
24
 
25
25
  changelog = File.read(@changelog_path)
26
26
 
27
- # If the detected version already exists in the changelog, abort to avoid duplicates
27
+ # If the detected version already exists in the changelog, offer reformat-only mode
28
28
  if changelog =~ /^## \[#{Regexp.escape(version)}\]/
29
- abort("CHANGELOG.md already has a section for version #{version}. Bump version.rb or remove the duplicate.")
29
+ warn("CHANGELOG.md already has a section for version #{version}.")
30
+ warn("It appears the version has not been bumped. You can reformat CHANGELOG.md without adding a new release section.")
31
+ print("Proceed with reformat only? [y/N]: ")
32
+ ans = Kettle::Dev::InputAdapter.gets&.strip&.downcase
33
+ if ans == "y" || ans == "yes"
34
+ updated = normalize_heading_spacing(changelog)
35
+ updated = ensure_footer_spacing(updated)
36
+ updated = updated.rstrip + "\n"
37
+ File.write(@changelog_path, updated)
38
+ puts "CHANGELOG.md reformatted. No new version section added."
39
+ return
40
+ else
41
+ abort("Aborting: version not bumped. Re-run after bumping version or answer 'y' to reformat-only.")
42
+ end
30
43
  end
31
44
 
32
45
  unreleased_block, before, after = extract_unreleased(changelog)
@@ -86,6 +99,9 @@ module Kettle
86
99
 
87
100
  updated = update_link_refs(updated, owner, repo, prev_version, version)
88
101
 
102
+ # Normalize spacing around headings to aid Markdown renderers
103
+ updated = normalize_heading_spacing(updated)
104
+
89
105
  # Ensure exactly one trailing newline at EOF
90
106
  updated = updated.rstrip + "\n"
91
107
 
@@ -160,6 +176,10 @@ module Kettle
160
176
  # Determine if chunk has any content (non-blank)
161
177
  content_present = chunk.any? { |l| l.strip != "" }
162
178
  if content_present
179
+ # Trim leading blank lines so there is no blank line after the header
180
+ while chunk.any? && chunk.first.strip == ""
181
+ chunk.shift
182
+ end
163
183
  # Trim trailing blank lines
164
184
  while chunk.any? && chunk.last.strip == ""
165
185
  chunk.pop
@@ -336,10 +356,71 @@ module Kettle
336
356
  new_ref_block << tags[v] if tags[v]
337
357
  end
338
358
  # Replace the old block
339
- rebuilt = lines[0...first_ref] + new_ref_block + ["\n"]
359
+ head = lines[0...first_ref]
360
+ # Ensure exactly one blank line separating body content from the reference block
361
+ if head.any? && head.last.to_s.strip != ""
362
+ head << "\n"
363
+ end
364
+ rebuilt = head + new_ref_block + ["\n"]
340
365
  rebuilt.join
341
366
  end
342
367
 
368
+ # Ensure every Markdown atx-style heading line (e.g., "# ", "## ") has exactly one blank line
369
+ # before and after it, skipping content inside fenced code blocks.
370
+ def normalize_heading_spacing(text)
371
+ lines = text.split("\n", -1)
372
+ out = []
373
+ in_fence = false
374
+ fence_re = /^\s*```/
375
+ heading_re = /^\s*#+\s+.+/
376
+ lines.each_with_index do |ln, idx|
377
+ if ln =~ fence_re
378
+ in_fence = !in_fence
379
+ out << ln
380
+ next
381
+ end
382
+ if !in_fence && ln =~ heading_re
383
+ # Ensure previous line is blank (unless start of file or already blank)
384
+ prev_blank = out.empty? ? false : out.last.to_s.strip == ""
385
+ out << "" unless out.empty? || prev_blank
386
+ out << ln
387
+ # Peek at next line in source to decide if we need to inject a blank now.
388
+ nxt = lines[idx + 1]
389
+ out << "" unless nxt.to_s.strip == ""
390
+ else
391
+ out << ln
392
+ end
393
+ end
394
+ # Collapse multiple consecutive blank lines down to a single between regions that our logic might have doubled
395
+ collapsed = []
396
+ lines_enum = out
397
+ lines_enum.each do |l|
398
+ if l.strip == "" && collapsed.last.to_s.strip == ""
399
+ next
400
+ end
401
+ collapsed << l
402
+ end
403
+ collapsed.join("\n")
404
+ end
405
+
406
+ def ensure_footer_spacing(text)
407
+ lines = text.split("\n", -1)
408
+ # Find the Unreleased link-ref which denotes start of footer refs
409
+ idx = lines.index { |l| l.start_with?(UNRELEASED_SECTION_HEADING) }
410
+ return text unless idx
411
+ head = lines[0...idx]
412
+ tail = lines[idx..-1]
413
+ # Ensure exactly one blank line between body and refs
414
+ if head.any? && head.last.to_s.strip != ""
415
+ head << ""
416
+ elsif head.any? && head.last.to_s.strip == "" && head[-2].to_s.strip == ""
417
+ # Collapse multiple blanks before footer to a single
418
+ head.pop while head.any? && head.last.to_s.strip == ""
419
+ head << ""
420
+ end
421
+ (head + tail).join("\n")
422
+ end
423
+
343
424
  def detect_initial_compare_base(lines)
344
425
  # Fallback when prev_version is unknown: try to find the first compare base used historically
345
426
  # e.g., for 1.0.0 it may be a commit SHA instead of a tag
@@ -106,16 +106,22 @@ module Kettle
106
106
  if funding_org_env && funding_org_env.to_s.strip.casecmp("false").zero?
107
107
  funding_org = nil
108
108
  else
109
- funding_org = ENV["OPENCOLLECTIVE_HANDLE"].to_s.strip if funding_org.empty?
110
- if funding_org.to_s.empty?
111
- oc_path = File.join(root.to_s, ".opencollective.yml")
112
- if File.file?(oc_path)
113
- txt = File.read(oc_path)
114
- if (m = txt.match(/\borg:\s*([\w\-]+)/i))
115
- funding_org = m[1].to_s
116
- end
109
+ # Prefer .opencollective.yml when present so that specs forcing the file path are stable
110
+ oc_path = File.join(root.to_s, ".opencollective.yml")
111
+ if File.file?(oc_path)
112
+ txt = File.read(oc_path)
113
+ funding_org = if (m = txt.match(/\borg:\s*([\w\-]+)/i))
114
+ m[1].to_s
115
+ else
116
+ ""
117
117
  end
118
118
  end
119
+
120
+ # Fallback to ENV when file not present or did not contain an org
121
+ if funding_org.to_s.strip.empty?
122
+ funding_org = ENV["OPENCOLLECTIVE_HANDLE"].to_s.strip if funding_org.empty?
123
+ end
124
+
119
125
  # Be lenient: if funding_org cannot be determined, do not raise — leave it nil and warn.
120
126
  if funding_org.to_s.empty?
121
127
  Kernel.warn("kettle-dev: Could not determine funding org.\n - Options:\n * Set ENV['FUNDING_ORG'] to your funding handle (e.g., 'opencollective-handle').\n * Or set ENV['OPENCOLLECTIVE_HANDLE'].\n * Or add .opencollective.yml with: org: <handle>\n * Or bypass by setting ENV['FUNDING_ORG']=false for gems without funding.")
@@ -114,6 +114,7 @@ module Kettle
114
114
  end
115
115
  opts.on("--hook_templates=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "hook_templates=#{v}" }
116
116
  opts.on("--only=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "only=#{v}" }
117
+ opts.on("--include=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "include=#{v}" }
117
118
  opts.on("-h", "--help", "Show help") do
118
119
  puts opts
119
120
  Kettle::Dev::ExitAdapter.exit(0)
@@ -10,6 +10,41 @@ module Kettle
10
10
 
11
11
  module_function
12
12
 
13
+ # Ensure every Markdown atx-style heading line has exactly one blank line
14
+ # before and after, skipping content inside fenced code blocks.
15
+ def normalize_heading_spacing(text)
16
+ lines = text.split("\n", -1)
17
+ out = []
18
+ in_fence = false
19
+ fence_re = /^\s*```/
20
+ heading_re = /^\s*#+\s+.+/
21
+ lines.each_with_index do |ln, idx|
22
+ if ln =~ fence_re
23
+ in_fence = !in_fence
24
+ out << ln
25
+ next
26
+ end
27
+ if !in_fence && ln =~ heading_re
28
+ prev_blank = out.empty? ? false : out.last.to_s.strip == ""
29
+ out << "" unless out.empty? || prev_blank
30
+ out << ln
31
+ nxt = lines[idx + 1]
32
+ out << "" unless nxt.to_s.strip == ""
33
+ else
34
+ out << ln
35
+ end
36
+ end
37
+ # Collapse accidental multiple blanks
38
+ collapsed = []
39
+ out.each do |l|
40
+ if l.strip == "" && collapsed.last.to_s.strip == ""
41
+ next
42
+ end
43
+ collapsed << l
44
+ end
45
+ collapsed.join("\n")
46
+ end
47
+
13
48
  # Abort wrapper that avoids terminating the entire process during specs
14
49
  def task_abort(msg)
15
50
  raise Kettle::Dev::Error, msg
@@ -57,11 +92,48 @@ module Kettle
57
92
  selected[key] ||= path
58
93
  end
59
94
  end
95
+ # Parse optional include patterns (comma-separated globs relative to project root)
96
+ include_raw = ENV["include"].to_s
97
+ include_patterns = include_raw.split(",").map { |s| s.strip }.reject(&:empty?)
98
+ matches_include = lambda do |abs_dest|
99
+ return false if include_patterns.empty?
100
+ begin
101
+ rel_dest = abs_dest.to_s
102
+ proj = project_root.to_s
103
+ if rel_dest.start_with?(proj + "/")
104
+ rel_dest = rel_dest[(proj.length + 1)..-1]
105
+ elsif rel_dest == proj
106
+ rel_dest = ""
107
+ end
108
+ include_patterns.any? do |pat|
109
+ if pat.end_with?("/**")
110
+ base = pat[0..-4]
111
+ rel_dest == base || rel_dest.start_with?(base + "/")
112
+ else
113
+ File.fnmatch?(pat, rel_dest, File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH)
114
+ end
115
+ end
116
+ rescue StandardError => e
117
+ Kettle::Dev.debug_error(e, __method__)
118
+ false
119
+ end
120
+ end
121
+
60
122
  selected.values.each do |orig_src|
61
123
  src = helpers.prefer_example(orig_src)
62
124
  # Destination path should never include the .example suffix.
63
125
  rel = orig_src.sub(/^#{Regexp.escape(gem_checkout_root)}\/?/, "").sub(/\.example\z/, "")
64
126
  dest = File.join(project_root, rel)
127
+
128
+ # Optional file: .github/workflows/discord-notifier.yml should NOT be copied by default.
129
+ # Only copy when --include matches it.
130
+ if rel == ".github/workflows/discord-notifier.yml"
131
+ unless matches_include.call(dest)
132
+ # Explicitly skip without prompting
133
+ next
134
+ end
135
+ end
136
+
65
137
  if File.basename(rel) == "FUNDING.yml"
66
138
  helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
67
139
  c = content.dup
@@ -314,6 +386,7 @@ module Kettle
314
386
  .gitlab-ci.yml
315
387
  .junie/guidelines-rbs.md
316
388
  .junie/guidelines.md
389
+ .licenserc.yaml
317
390
  .opencollective.yml
318
391
  .rspec
319
392
  .rubocop.yml
@@ -548,31 +621,152 @@ module Kettle
548
621
  )
549
622
  if File.basename(rel) == "CHANGELOG.md"
550
623
  begin
551
- # Special handling: only template header through Unreleased section (inclusive), preserve the rest
624
+ # Special handling for CHANGELOG.md
625
+ # 1) Take template header through Unreleased section (inclusive)
552
626
  src_lines = c.split("\n", -1)
553
- # Find index of Unreleased heading (case-insensitive), typical format: ## [Unreleased]
554
- unreleased_idx = src_lines.index { |ln| ln =~ /^##\s*\[\s*Unreleased\s*\]/i }
555
- if unreleased_idx
556
- # Determine end of Unreleased section: next heading of same or higher level (## or #)
557
- stop_idx = src_lines.length - 1
558
- j = unreleased_idx + 1
627
+ tpl_unrel_idx = src_lines.index { |ln| ln =~ /^##\s*\[\s*Unreleased\s*\]/i }
628
+ if tpl_unrel_idx
629
+ # Find end of Unreleased in template (next ## or # heading)
630
+ tpl_end_idx = src_lines.length - 1
631
+ j = tpl_unrel_idx + 1
559
632
  while j < src_lines.length
560
633
  if src_lines[j] =~ /^##\s+\[/ || src_lines[j] =~ /^#\s+/ || src_lines[j] =~ /^##\s+[^\[]/
561
- stop_idx = j - 1
634
+ tpl_end_idx = j - 1
562
635
  break
563
636
  end
564
637
  j += 1
565
638
  end
566
- header_through_unreleased = src_lines[0..stop_idx]
567
- tail = File.file?(dest) ? File.read(dest).split("\n", -1)[(File.read(dest).split("\n", -1).index { |ln| ln =~ /^##\s*\[\s*Unreleased\s*\]/i } || -1) + 1..-1] : nil
568
- tail ||= []
569
- # Additionally, ensure we preserve existing markdown link refs typically at file bottom (e.g., [Unreleased]: ...)
570
- # Prefer tail from existing file entirely to avoid clobbering release notes and refs.
571
- merged = (header_through_unreleased + (tail || [])).join("\n")
572
- c = merged
573
- else
574
- # If Unreleased not found, fallback to original c (only collapse header spacing below)
639
+ tpl_header_pre = src_lines[0...tpl_unrel_idx] # lines before Unreleased heading
640
+ tpl_unrel_heading = src_lines[tpl_unrel_idx]
641
+ src_lines[(tpl_unrel_idx + 1)..tpl_end_idx] || []
642
+
643
+ # 2) Extract destination Unreleased content, preserving list items under any standard headings
644
+ dest_content = File.file?(dest) ? File.read(dest) : ""
645
+ dest_lines = dest_content.split("\n", -1)
646
+ dest_unrel_idx = dest_lines.index { |ln| ln =~ /^##\s*\[\s*Unreleased\s*\]/i }
647
+ dest_end_idx = if dest_unrel_idx
648
+ k = dest_unrel_idx + 1
649
+ e = dest_lines.length - 1
650
+ while k < dest_lines.length
651
+ if dest_lines[k] =~ /^##\s+\[/ || dest_lines[k] =~ /^#\s+/ || dest_lines[k] =~ /^##\s+[^\[]/
652
+ e = k - 1
653
+ break
654
+ end
655
+ k += 1
656
+ end
657
+ e
658
+ end
659
+ dest_unrel_body = dest_unrel_idx ? (dest_lines[(dest_unrel_idx + 1)..dest_end_idx] || []) : []
660
+
661
+ # Helper: parse body into map of heading=>items (only '- ' markdown items)
662
+ std_heads = [
663
+ "### Added",
664
+ "### Changed",
665
+ "### Deprecated",
666
+ "### Removed",
667
+ "### Fixed",
668
+ "### Security",
669
+ ]
670
+
671
+ parse_items = lambda do |body_lines|
672
+ result = {}
673
+ cur = nil
674
+ i = 0
675
+ while i < body_lines.length
676
+ ln = body_lines[i]
677
+ if ln.start_with?("### ")
678
+ cur = ln.strip
679
+ result[cur] ||= []
680
+ i += 1
681
+ next
682
+ end
683
+
684
+ # Detect a list item bullet (allow optional indentation)
685
+ if (m = ln.match(/^(\s*)[-*]\s/))
686
+ result[cur] ||= []
687
+ base_indent = m[1].length
688
+ # Start a new item: include the bullet line
689
+ result[cur] << ln.rstrip
690
+ i += 1
691
+
692
+ # Include subsequent lines that belong to this list item:
693
+ # - blank lines
694
+ # - lines with indentation greater than the bullet's indentation
695
+ # - any lines inside fenced code blocks (```), regardless of indentation until fence closes
696
+ in_fence = false
697
+ fence_re = /^\s*```/
698
+ while i < body_lines.length
699
+ l2 = body_lines[i]
700
+ # Stop if next sibling/top-level bullet of same or smaller indent and not inside a fence
701
+ if !in_fence && l2 =~ /^(\s*)[-*]\s/
702
+ ind = Regexp.last_match(1).length
703
+ break if ind <= base_indent
704
+ end
705
+ # Break if a new section heading appears and we're not in a fence
706
+ break if !in_fence && l2.start_with?("### ")
707
+
708
+ if l2 =~ fence_re
709
+ in_fence = !in_fence
710
+ result[cur] << l2.rstrip
711
+ i += 1
712
+ next
713
+ end
714
+
715
+ # Include blanks and lines indented more than base indent, or anything while in fence
716
+ if in_fence || l2.strip.empty? || (l2[/^\s*/].length > base_indent)
717
+ result[cur] << l2.rstrip
718
+ i += 1
719
+ next
720
+ end
721
+
722
+ # Otherwise, this line does not belong to the current list item
723
+ break
724
+ end
725
+
726
+ next
727
+ end
728
+
729
+ # Non-bullet, non-heading line: just advance
730
+ i += 1
731
+ end
732
+ result
733
+ end
734
+
735
+ dest_items = parse_items.call(dest_unrel_body)
736
+
737
+ # 3) Build a single canonical Unreleased section: heading + the six standard subheads in order
738
+ new_unrel_block = []
739
+ new_unrel_block << tpl_unrel_heading
740
+ std_heads.each do |h|
741
+ new_unrel_block << h
742
+ if dest_items[h] && !dest_items[h].empty?
743
+ new_unrel_block.concat(dest_items[h])
744
+ end
745
+ end
746
+
747
+ # 4) Compose final content: template preface + new unreleased + rest of destination (after its unreleased)
748
+ tail_after_unrel = []
749
+ if dest_unrel_idx
750
+ tail_after_unrel = dest_lines[(dest_end_idx + 1)..-1] || []
751
+ end
752
+
753
+ # Ensure exactly one blank line between the Unreleased chunk and the next version chunk
754
+ # - Strip trailing blanks from the newly built Unreleased block
755
+ while new_unrel_block.any? && new_unrel_block.last.to_s.strip == ""
756
+ new_unrel_block.pop
757
+ end
758
+ # - Strip leading blanks from the tail
759
+ while tail_after_unrel.any? && tail_after_unrel.first.to_s.strip == ""
760
+ tail_after_unrel.shift
761
+ end
762
+ merged_lines = tpl_header_pre + new_unrel_block
763
+ # Insert a single separator blank line if there is any tail content
764
+ merged_lines << "" if tail_after_unrel.any?
765
+ merged_lines.concat(tail_after_unrel)
766
+
767
+ c = merged_lines.join("\n")
575
768
  end
769
+
576
770
  # Collapse repeated whitespace in release headers only
577
771
  lines = c.split("\n", -1)
578
772
  lines.map! do |ln|
@@ -585,12 +779,14 @@ module Kettle
585
779
  c = lines.join("\n")
586
780
  rescue StandardError => e
587
781
  Kettle::Dev.debug_error(e, __method__)
588
- # On any error, keep previous behavior (collapse whitespace only)
782
+ # Fallback: whitespace normalization
589
783
  lines = c.split("\n", -1)
590
784
  lines.map! { |ln| (ln =~ /^##\s+\[.*\]/) ? ln.gsub(/[ \t]+/, " ") : ln }
591
785
  c = lines.join("\n")
592
786
  end
593
787
  end
788
+ # Normalize spacing around Markdown headings for broad renderer compatibility
789
+ c = normalize_heading_spacing(c)
594
790
  c
595
791
  end
596
792
  else
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.1.18"
9
+ VERSION = "1.1.20"
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.18
4
+ version: 1.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -235,12 +235,13 @@ files:
235
235
  - ".github/workflows/current.yml"
236
236
  - ".github/workflows/dep-heads.yml"
237
237
  - ".github/workflows/dependency-review.yml"
238
- - ".github/workflows/discord-notifier.yml"
238
+ - ".github/workflows/discord-notifier.yml.example"
239
239
  - ".github/workflows/heads.yml"
240
240
  - ".github/workflows/heads.yml.example"
241
241
  - ".github/workflows/jruby.yml"
242
242
  - ".github/workflows/jruby.yml.example"
243
243
  - ".github/workflows/legacy.yml"
244
+ - ".github/workflows/license-eye.yml"
244
245
  - ".github/workflows/locked_deps.yml"
245
246
  - ".github/workflows/opencollective.yml"
246
247
  - ".github/workflows/style.yml"
@@ -252,6 +253,7 @@ files:
252
253
  - ".gitlab-ci.yml.example"
253
254
  - ".junie/guidelines-rbs.md"
254
255
  - ".junie/guidelines.md"
256
+ - ".licenserc.yaml"
255
257
  - ".opencollective.yml"
256
258
  - ".opencollective.yml.example"
257
259
  - ".qlty/qlty.toml"
@@ -386,10 +388,10 @@ licenses:
386
388
  - MIT
387
389
  metadata:
388
390
  homepage_uri: https://kettle-dev.galtzo.com/
389
- source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.18
390
- changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.18/CHANGELOG.md
391
+ source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.20
392
+ changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.20/CHANGELOG.md
391
393
  bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
392
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.18
394
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.20
393
395
  funding_uri: https://github.com/sponsors/pboling
394
396
  wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
395
397
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file