#!/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+)*)