Class: GemKit::Release::Changelog
- Inherits:
-
Object
- Object
- GemKit::Release::Changelog
- Defined in:
- lib/gem_kit/release/changelog.rb
Overview
A parser and linter for CHANGELOG.md, which follows Keep a Changelog.
The changelog is the one release artefact nothing else can regenerate, so
it is the one most easily forgotten. Making it machine-checkable turns
"did anyone write the changelog?" into a gate: gem_kit-release check
validates the format, and refuses to release a version that has no
section of its own.
changelog = GemKit::Release::Changelog.load
changelog.problems # => [] when the format is clean
changelog.release_problems("4.1.0")
The shape it expects:
# Changelog
## [Unreleased]
## [4.1.0] - 2026-08-20
### Added
- Something that happened.
Defined Under Namespace
Classes: Release
Constant Summary collapse
- SECTIONS =
The six change types Keep a Changelog defines. Anything else under a version is a typo or an invention, and both are worth catching.
%w[Added Changed Deprecated Removed Fixed Security].freeze
- UNRELEASED =
"Unreleased"- HEADING =
/\A##\s+\[([^\]]+)\](?:\s+-\s+(.*))?\s*\z/- SUBHEADING =
/\A###\s+(.*?)\s*\z/- BULLET =
/\A[-*]\s+\S/- DATE =
/\A\d{4}-\d{2}-\d{2}\z/
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#releases ⇒ Object
readonly
Returns the value of attribute releases.
Class Method Summary collapse
-
.load(path = File.join(Dir.pwd, "CHANGELOG.md")) ⇒ Object
Load the changelog sitting beside the gem (../../CHANGELOG.md).
Instance Method Summary collapse
- #find(version) ⇒ Object
-
#initialize(text, path: "CHANGELOG.md") ⇒ Changelog
constructor
A new instance of Changelog.
- #missing? ⇒ Boolean
-
#problems ⇒ Object
Everything wrong with the file's format, as a list of human-readable problems.
-
#release_problems(version) ⇒ Object
Everything standing between this changelog and releasing
version. - #released ⇒ Object
- #unreleased ⇒ Object
Constructor Details
#initialize(text, path: "CHANGELOG.md") ⇒ Changelog
Returns a new instance of Changelog.
58 59 60 61 62 |
# File 'lib/gem_kit/release/changelog.rb', line 58 def initialize(text, path: "CHANGELOG.md") @path = path @text = text @releases = text ? parse(text) : [] end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
55 56 57 |
# File 'lib/gem_kit/release/changelog.rb', line 55 def path @path end |
#releases ⇒ Object (readonly)
Returns the value of attribute releases.
55 56 57 |
# File 'lib/gem_kit/release/changelog.rb', line 55 def releases @releases end |
Class Method Details
.load(path = File.join(Dir.pwd, "CHANGELOG.md")) ⇒ Object
Load the changelog sitting beside the gem (../../CHANGELOG.md).
51 52 53 |
# File 'lib/gem_kit/release/changelog.rb', line 51 def self.load(path = File.join(Dir.pwd, "CHANGELOG.md")) new(File.exist?(path) ? File.read(path) : nil, path: path) end |
Instance Method Details
#find(version) ⇒ Object
70 71 72 73 |
# File 'lib/gem_kit/release/changelog.rb', line 70 def find(version) target = Gem::Version.new(version.to_s) released.find { |release| Gem::Version.new(release.version) == target rescue false } end |
#missing? ⇒ Boolean
64 |
# File 'lib/gem_kit/release/changelog.rb', line 64 def missing? = @text.nil? |
#problems ⇒ Object
Everything wrong with the file's format, as a list of human-readable problems. Empty means it lints clean.
77 78 79 80 81 |
# File 'lib/gem_kit/release/changelog.rb', line 77 def problems return ["#{path} does not exist"] if missing? [*header_problems, *heading_problems, *ordering_problems, *content_problems] end |
#release_problems(version) ⇒ Object
Everything standing between this changelog and releasing version.
Format problems count: a file nobody can parse is not documentation.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/gem_kit/release/changelog.rb', line 85 def release_problems(version) return problems unless problems.empty? release = find(version) return ["#{path} has no section for #{version} — run gem_kit-release changelog"] if release.nil? return ["#{path} section for #{version} is empty"] if release.empty? newest = released.first if newest && newest.version != release.version return ["#{path} lists #{newest.version} above #{version}; the release being cut must come first"] end [] end |
#released ⇒ Object
68 |
# File 'lib/gem_kit/release/changelog.rb', line 68 def released = releases.reject(&:unreleased?) |
#unreleased ⇒ Object
66 |
# File 'lib/gem_kit/release/changelog.rb', line 66 def unreleased = releases.find(&:unreleased?) |