Parser for Keep a Changelog format with querying and write-back
gem install philiprehberger-changelog_parserParser for Keep a Changelog format with querying and write-back
Add to your Gemfile:
gem "philiprehberger-changelog_parser"
Or install directly:
gem install philiprehberger-changelog_parser
require "philiprehberger/changelog_parser"
changelog = Philiprehberger::ChangelogParser.parse('CHANGELOG.md')
changelog.versions # => ['Unreleased', '0.2.0', '0.1.0']
changelog.latest # => VersionEntry for 0.2.0
entry = changelog.version('0.2.0')
entry.date # => '2026-03-20'
entry.categories['Added'] # => ['New feature A', 'New feature B']
changelog.empty? # => false (true for templates and freshly-initialized changelogs)
changelog.add('Unreleased', 'Added', 'New search feature')
changelog.add('Unreleased', 'Fixed', 'Resolved login bug')
changelog.release('0.3.0', date: '2026-03-22')
# Moves Unreleased entries to the new version
changelog.write('CHANGELOG.md')
# Or get the markdown string
markdown = changelog.to_markdown
json = changelog.to_json
# => '{"title":"Changelog","versions":[{"version":"Unreleased","date":null,"categories":{}},...]}'
require "philiprehberger/changelog_parser"
changelog = Philiprehberger::ChangelogParser.parse("CHANGELOG.md")
# Get all changes between two versions
changes = changelog.diff("0.1.0", "0.3.0")
changes["Added"] # => ["Feature B", "Feature C"]
# Get all changes since a version
recent = changelog.since("0.1.0")
recent["Fixed"] # => ["Bug fix B"]
results = changelog.search("authentication")
results.each do |match|
puts "#{match[:version]} [#{match[:category]}] #{match[:entry]}"
end
# Also accepts regex
changelog.search(/\bbug\b/i)
warnings = changelog.validate
# => ["empty version: 0.2.0", "date out of order: 2026-03-01 before 2026-03-15"]
added = changelog.filter(category: 'Added')
added.each do |match|
puts "#{match[:version]} (#{match[:date]}): #{match[:entry]}"
end
changelog.remove('Unreleased', 'Added', 'Obsolete feature')
json = changelog.to_json
restored = Philiprehberger::ChangelogParser.from_json(json)
restored.versions # => same as original
changelog = Philiprehberger::ChangelogParser.parse(<<~MD)
# Changelog
## [Unreleased]
## [0.1.0] - 2026-03-15
### Added
- Initial release
MD
ChangelogParser| Method | Description |
|---|---|
.parse(path_or_string) | Parse a changelog from a file path or string |
.from_json(json_string) | Deserialize a changelog from a JSON string |
Changelog| Method | Description |
|---|---|
#versions | Return all version strings |
#version(v) | Find a specific version entry |
#categories | Sorted unique category names across all entries |
#entry_count | Total count of line items across all versions |
#empty? | True when the changelog has no line-item entries |
#unreleased | Return the Unreleased entry |
#latest | Return the latest released version |
#add(version, category, entry) | Add an entry to a version |
#remove(version, category, entry) | Remove an entry from a version |
#release(version, date:) | Create a release from Unreleased |
#write(path) | Write changelog to a file |
#diff(from, to) | Returns merged entries between two versions |
#since(version) | Returns merged entries newer than a version |
#filter(category:) | Return all entries from a specific category across versions |
#search(query) | Search entries by keyword or regex |
#validate | Check for common issues (duplicates, date order, empty versions) |
#to_json | Serialize as JSON string |
#to_markdown | Render as markdown string |
VersionEntry| Method | Description |
|---|---|
#version | The version string |
#date | The release date |
#categories | Hash of category to entries |
#empty? | True if version has no entries |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: