From d0300dd5851bcacbcba2c0ddacd057ada57b6cc2 Mon Sep 17 00:00:00 2001 From: Jasper van Merle Date: Fri, 8 Mar 2019 03:23:40 +0100 Subject: [PATCH] Create command to check for updates --- lib/docs/core/scraper.rb | 29 +++++++++++++++ lib/tasks/updates.thor | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 lib/tasks/updates.thor diff --git a/lib/docs/core/scraper.rb b/lib/docs/core/scraper.rb index 083b0015..89191e48 100644 --- a/lib/docs/core/scraper.rb +++ b/lib/docs/core/scraper.rb @@ -132,6 +132,35 @@ module Docs end end + def get_latest_version + raise NotImplementedError + end + + # Returns whether or not this scraper is outdated. + # + # The default implementation assumes the documentation uses a semver(-like) approach when it comes to versions. + # Patch updates are ignored because there are usually little to no documentation changes in bug-fix-only releases. + # + # Scrapers of documentations that do not use this versioning approach should override this method. + # + # Examples of the default implementation: + # 1 -> 2 = outdated + # 1.1 -> 1.2 = outdated + # 1.1.1 -> 1.1.2 = not outdated + def is_outdated(current_version, latest_version) + current_parts = current_version.split(/\./).map(&:to_i) + latest_parts = latest_version.split(/\./).map(&:to_i) + + # Only check the first two parts, the third part is for patch updates + [0, 1].each do |i| + break if i >= current_parts.length or i >= latest_parts.length + return true if latest_parts[i] > current_parts[i] + return false if latest_parts[i] < current_parts[i] + end + + false + end + private def request_one(url) diff --git a/lib/tasks/updates.thor b/lib/tasks/updates.thor new file mode 100644 index 00000000..35d52e28 --- /dev/null +++ b/lib/tasks/updates.thor @@ -0,0 +1,78 @@ +class UpdatesCLI < Thor + def self.to_s + 'Updates' + end + + def initialize(*args) + require 'docs' + require 'progress_bar' + super + end + + desc 'check [doc]...', 'Check for outdated documentations' + def check(*names) + # Convert names to a list of Scraper instances + # Versions are omitted, if v10 is outdated than v8 is aswell + docs = names.map {|name| Docs.find(name.split(/@|~/)[0], false)}.uniq + + # Check all documentations for updates when no arguments are given + docs = Docs.all if docs.empty? + + progress_bar = ::ProgressBar.new docs.length + progress_bar.write + + results = docs.map do |doc| + result = check_doc(doc) + progress_bar.increment! + result + end + + outdated = results.select {|result| result.is_a?(Hash) && result[:is_outdated]} + return if outdated.empty? + + logger.info("Outdated documentations (#{outdated.length}):") + outdated.each do |result| + logger.info("#{result[:name]}: #{result[:current_version]} -> #{result[:latest_version]}") + end + rescue Docs::DocNotFound => error + logger.error(error) + logger.info('Run "thor docs:list" to see the list of docs.') + end + + private + + def check_doc(doc) + # Scraper versions are always sorted from new to old + # Therefore, the first item's release value is the latest current scraper version + # + # For example, a scraper could scrape 3 versions: 10, 11 and 12 + # doc.versions.first would be the scraper for version 12 if the scraper is written like all the other scrapers are + instance = doc.versions.first.new + + current_version = instance.options[:release] + return nil if current_version.nil? + + latest_version = instance.get_latest_version + return nil if latest_version.nil? + + { + name: doc.name, + current_version: current_version, + latest_version: latest_version, + is_outdated: instance.is_outdated(current_version, latest_version) + } + rescue NotImplementedError + logger.warn("Can't check #{doc.name}, get_latest_version is not implemented") + rescue => error + logger.error("Error while checking #{doc.name}: #{error}") + end + + def logger + @logger ||= Logger.new($stdout).tap do |logger| + logger.formatter = proc do |severity, datetime, progname, msg| + prefix = severity != "INFO" ? "[#{severity}] " : "" + "#{prefix}#{msg}\n" + end + end + end +end