Skip to content

Instantly share code, notes, and snippets.

@3Hren
Created April 15, 2020 11:57
Show Gist options
  • Save 3Hren/a996e872db94fc9f674377ccec694f11 to your computer and use it in GitHub Desktop.
Save 3Hren/a996e872db94fc9f674377ccec694f11 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'git'
require 'logger'
require 'optparse'
require 'semantic'
require 'semantic/core_ext'
require 'tempfile'
require 'tty-prompt'
DEFAULT_AUTHOR_NAME = 'Evgeny Safronov'.freeze
DEFAULT_AUTHOR_MAIL = 'division494@gmail.com'.freeze
WORKING_DIR = '/Users/esafronov/code/core'.freeze
LOG = Logger.new(STDOUT)
TYPES = {
feat: 'Added',
change: 'Changed',
fix: 'Fixed',
perf: 'Performance'
}.freeze
# ChangelogManager.
class ChangelogManager
def initialize(working_dir)
@working_dir = working_dir
@git = Git.open(working_dir)
@versions = []
@list_symbol = '*'
end
# Find the last version in git tags.
# The format must adhere semantic versioning.
def last_version
read_git_version_tags if @versions.empty?
@versions.last
end
def read_git_version_tags
tags = @git.tags.map do |v|
LOG.debug "Found version tag #{v.name}"
matches = /^v(?<version>\d+.\d+.\d+)$/.match(v.name)
matches[:version] unless matches.nil?
end
@versions = tags.compact.sort_by do |v|
Semantic::Version.new(v)
end
end
def last_version_tag
"v#{last_version}"
end
def changelog_path
File.join(@working_dir, 'debian/changelog')
end
def makefile_path
File.join(@working_dir, 'Makefile')
end
def appveyor_path
File.join(@working_dir, 'appveyor.yml')
end
def read_changelog
File.read(changelog_path)
end
def read_makefile
File.read(makefile_path)
end
def read_appveyor
File.read(appveyor_path)
end
def parse_debian_changelog
rx = /^(?<name>.*) \((?<version>.*)\) (?<distrib>.*); urgency=(?<urgency>.*)$/
rx.match(read_changelog.lines.first)
end
def last_debian_version
meta = parse_debian_changelog
meta[:version]
end
def makefile_version
rx = /VERSION = v(?<version>\d+\.\d+\.\d+)/
meta = rx.match(read_makefile)
meta[:version]
end
def update_makefile_version(version)
rx = /VERSION = v(?<version>\d+\.\d+\.\d+)/
content = read_makefile
content.gsub! rx, "VERSION = v#{version}"
File.write makefile_path, content
end
def update_appveyor_version(version)
rx = /version: (?<version>\d+\.\d+\.\d+)\.{build}/
content = read_appveyor
content.gsub! rx, "version: #{version}.{build}"
File.write appveyor_path, content
end
def do_all_stuff
changelog = {}
version = last_version_tag
# Fetching all commits between the last version and HEAD.
@git.log(1024).between(version, 'HEAD').each do |v|
lines = v.message.lines
brief = lines.first.strip
matches = /^(?<type>.+):(?<message>.*)$/.match brief
if matches.nil?
LOG.warn"malformed brief: #{brief}"
next
end
type = TYPES[matches[:type].to_sym]
next if type.nil?
# Remove type tag.
lines[0] = matches[:message]
changelog[type] = [] if changelog[type].nil?
changelog[type].push(lines)
end
lines = []
TYPES.values.each do |type|
next if changelog[type].nil?
changelog[type].each do |entry|
brief = entry.first.strip
detailed = entry[1..-1]
lines.push("#{@list_symbol} #{type}: #{brief}.")
found_exclude = false
found_internals = false
detailed.each do |v|
v = v.strip.gsub('\\n', '')
next if v.size.zero?
found_internals = true if v.include? 'Internals:'
next if found_internals
found_exclude = true if v.include? 'Changelog: exclude'
next if found_exclude
reformat_wrapped(v).each do |s|
lines.push(" #{s}")
end
end
# End line with dot if missed.
lines[-1] << '.' unless lines[-1].end_with? '.'
end
end
lines = lines.map do |line|
' ' + line
end
result = lines.join("\n")
result.tr!('`', '"')
result
end
def reformat_wrapped(text, width = 78)
lines = []
line = ''
text.split(/\s+/).each do |word|
if line.size + word.size >= width
lines << line
line = word
elsif line.empty?
line = word
else
line << ' ' << word
end
end
lines << line if line
lines
end
end
def main
options = {}
OptionParser.new do |opts|
opts.banner = 'Usage: chaman.rb [options]'
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
options[:verbose] = v
end
opts.on('-s', '--simulate', 'Perform no action') do |v|
options[:simulate] = v
end
opts.on_tail('-h', '--help', 'Show this message and exit') do
puts opts
exit
end
opts.on_tail('--version', 'Show version and exit') do
puts VERSION.join('.')
exit
end
end.parse!
LOG.info"Working directory `#{WORKING_DIR}`"
manager = ChangelogManager.new WORKING_DIR
LOG.info "Last known version in git: #{manager.last_version}"
LOG.info "Last known version in debian: #{manager.last_debian_version}"
LOG.info "Last known version in Makefile: #{manager.makefile_version}"
prompt = TTY::Prompt.new
version = Semantic::Version.new manager.last_version
version = prompt.select('What version to bump?') do |menu|
menu.choice version.increment!(:major).to_s
menu.choice version.increment!(:minor).to_s
menu.choice version.increment!(:patch).to_s
end
puts version
content = manager.do_all_stuff
deb_full_name = ENV['DEBFULLNAME'] || DEFAULT_AUTHOR_NAME
deb_email = ENV['DEBEMAIL'] || DEFAULT_AUTHOR_MAIL
time = Time.new
deb_formatted_date = time.strftime('%a, %d %b %Y %H:%M:%S %z')
changelog = manager.read_changelog
unless options[:simulate]
file = Tempfile.new('changelog')
meta = manager.parse_debian_changelog
file.write "#{meta[:name]} (#{version}) #{meta[:distrib]}; urgency=#{meta[:urgency]}"
file.write "\n\n"
file.write content
file.write "\n\n"
file.write " -- #{deb_full_name} <#{deb_email}> #{deb_formatted_date}"
file.write "\n\n"
file.write changelog
file.close
FileUtils.copy_file file.path, manager.changelog_path
manager.update_makefile_version version
manager.update_appveyor_version version
end
LOG.info 'Done'
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment