#!/usr/bin/env ruby
# frozen_string_literal: true
require 'yaml'
require 'uri'
require 'net/http'
require 'openssl'
require 'rubygems/version'
require 'fspath'
# Fetch and compare latest tool/library versions
class Livecheck
# Commands
class Cli
VERSION_REGEXP = /^([A-Z]+)_VER *:= *(.*)$/.freeze
def initialize(args)
@update = args.delete('--update')
abort '--update is the only supported option' unless args.empty?
end
def run
dump_changes
return unless @update
return unless livechecks.any?(&:changed?)
update_changelog
update_makefile
end
private
def dump_changes
livechecks.each do |lc|
next unless lc.changed?
puts "#{lc.name_n_latest_version} (current: #{lc.current_version})"
end
end
def update_changelog
changelog = FSPath('CHANGELOG.markdown')
lines = changelog.readlines
lines.insert(4, "\n") if lines[4] =~ /^## /
lines.insert(4, changelog_entry)
write(changelog, lines.join(''))
end
def update_makefile
content = makefile.read.gsub(VERSION_REGEXP) do
livecheck = livechecks_by_name[Regexp.last_match[1].downcase]
"#{livecheck.name.upcase}_VER := #{livecheck.latest_version}"
end
write(makefile, content)
end
def makefile
FSPath('Makefile')
end
def livechecks
@livechecks ||= makefile.read.scan(VERSION_REGEXP).map do |name, version|
Livecheck.new(name.downcase, version)
end
end
def livechecks_by_name
@livechecks_by_name ||= Hash[livechecks.map{ |lc| [lc.name, lc] }]
end
def changelog_entry
github_user = `git config github.user`.strip
changed = livechecks.select(&:changed?)
%W[
*
#{to_sentence(changed.map(&:name_n_latest_version))}
[@#{github_user}](https://github.com/#{github_user})
].join(' ') + "\n"
end
def to_sentence(array)
case array.length
when 0 then ''
when 1 then array[0].to_s
else "#{array[0...-1].join(', ')} and #{array[-1]}"
end
end
def write(path, data)
path.temp_file(path.dirname) do |io|
io.write data
mode = path.exist? ? path.stat.mode : (0o777 & ~File.umask)
io.path.rename(path)
path.chmod(mode)
end
warn "Wrote #{path}"
end
end
# Compare versions including libjpeg ones (9b <=> 9)
class Version
include Comparable
attr_reader :string, :parsed
alias_method :to_s, :string
def initialize(string)
@string = string
@parsed = begin
Gem::Version.new(string)
rescue ArgumentError
nil
end
end
def <=>(other)
if parsed && other.parsed
parsed <=> other.parsed
else
string <=> other.string
end
end
end
CONFIG = YAML.load(DATA).freeze
attr_reader :name, :current_version
def initialize(name, current_version)
@name = name
@current_version = Version.new(current_version)
@fetcher = Thread.new{ fetch_versions.last }
end
def latest_version
@fetcher.value
end
def changed?
latest_version != current_version
end
def name_n_latest_version
"#{name} #{latest_version}"
end
private
def config
CONFIG[name] || fail(ArgumentError, "Livecheck for #{name} not defined")
end
def get(url)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.request_get(uri.request_uri).body
end
def fetch_versions
body = get(config['url'])
version_regex = Regexp.new(config['regexp'])
versions = body.scan(version_regex).map{ |match| Version.new(*match) }.sort
fail "No versions found for #{name} in body:\n#{body}" if versions.empty?
versions
end
end
Livecheck::Cli.new(ARGV).run
__END__
advancecomp:
url: https://github.com/amadvance/advancecomp/releases.atom
regexp: tag:github.com,2008:Repository/\d+/\D+?(\d+(?:\.\d+)*)
gifsicle:
url: https://www.lcdf.org/gifsicle/
regexp: gifsicle-(\d+(?:\.\d+)*)\.tar\.gz
jhead:
url: https://www.sentex.ca/~mwandel/jhead/
regexp: jhead-(\d+(?:\.\d+)*)
jpegoptim:
url: https://github.com/tjko/jpegoptim/releases.atom
regexp: tag:github.com,2008:Repository/\d+/\D+?(\d+(?:\.\d+)*)
jpegarchive:
url: https://github.com/danielgtaylor/jpeg-archive/releases.atom
regexp: tag:github.com,2008:Repository/\d+/\D+?(\d+(?:\.\d+)*)
libjpeg:
url: https://www.ijg.org/files/
regexp: jpegsrc.v(.*?).tar.gz
libmozjpeg:
url: https://github.com/mozilla/mozjpeg/releases.atom
regexp: tag:github.com,2008:Repository/\d+/\D+?(\d+(?:\.\d+)*)
libpng:
url: https://sourceforge.net/projects/libpng/rss?path=/libpng16
regexp: /libpng\d*/(\d+(?:\.\d+)*)/
libz:
url: https://sourceforge.net/projects/libpng/rss?path=/zlib
regexp: /zlib/(\d+(?:\.\d+)*)/
optipng:
url: https://sourceforge.net/projects/optipng/rss
regexp: /OptiPNG/optipng-(\d+(?:\.\d+)*)/
pngcrush:
url: https://sourceforge.net/projects/pmt/rss?path=/pngcrush
regexp: /pngcrush/(\d+(?:\.\d+)*)/
pngquant:
url: https://pngquant.org/releases.html
regexp: (\d+(?:\.\d+)*)