Finish get_latest_version for 81 scrapers and add uploading functionality

pull/986/head
Jasper van Merle 6 years ago
parent 2d1e8aa00c
commit 3dc17a9b29

@ -40,6 +40,7 @@ group :docs do
gem 'unix_utils', require: false
gem 'tty-pager', require: false
gem 'net-sftp', '>= 2.1.3.rc2', require: false
gem 'terminal-table', require: false
end
group :test do

@ -101,6 +101,8 @@ GEM
unicode-display_width (~> 1.4.0)
unicode_utils (~> 1.4.0)
strings-ansi (0.1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thin (1.7.2)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
@ -153,6 +155,7 @@ DEPENDENCIES
sinatra-contrib
sprockets
sprockets-helpers
terminal-table
thin
thor
tty-pager

@ -132,7 +132,7 @@ module Docs
end
end
def get_latest_version(&block)
def get_latest_version(options, &block)
raise NotImplementedError
end
@ -147,15 +147,15 @@ module Docs
# 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)
def is_outdated(scraper_version, latest_version)
scraper_parts = scraper_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]
break if i >= scraper_parts.length or i >= latest_parts.length
return true if latest_parts[i] > scraper_parts[i]
return false if latest_parts[i] < scraper_parts[i]
end
false
@ -231,38 +231,62 @@ module Docs
{}
end
#
# Utility methods for get_latest_version
#
def fetch(url, &block)
Request.run(url) do |response|
def fetch(url, options, &block)
headers = {}
if options.key?(:github_token) and url.start_with?('https://api.github.com/')
headers['Authorization'] = "token #{options[:github_token]}"
end
options[:logger].debug("Fetching #{url}")
Request.run(url, { headers: headers }) do |response|
if response.success?
block.call response.body
else
options[:logger].error("Couldn't fetch #{url} (response code #{response.code})")
block.call nil
end
end
end
def fetch_doc(url, &block)
fetch(url) do |body|
parser = Parser.new(body)
block.call parser.html
def fetch_doc(url, options, &block)
fetch(url, options) do |body|
block.call Nokogiri::HTML.parse body, nil, 'UTF-8'
end
end
def fetch_json(url, &block)
fetch(url) do |body|
def fetch_json(url, options, &block)
fetch(url, options) do |body|
json = JSON.parse(body)
block.call json
end
end
def get_npm_version(package, &block)
fetch_json("https://registry.npmjs.com/#{package}") do |json|
def get_npm_version(package, options, &block)
fetch_json("https://registry.npmjs.com/#{package}", options) do |json|
block.call json['dist-tags']['latest']
end
end
def get_latest_github_release(owner, repo, options, &block)
fetch_json("https://api.github.com/repos/#{owner}/#{repo}/releases/latest", options, &block)
end
def get_github_tags(owner, repo, options, &block)
fetch_json("https://api.github.com/repos/#{owner}/#{repo}/tags", options, &block)
end
def get_github_file_contents(owner, repo, path, options, &block)
fetch_json("https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}", options) do |json|
block.call(Base64.decode64(json['content']))
end
end
module FixInternalUrlsBehavior
def self.included(base)
base.extend ClassMethods

@ -155,8 +155,8 @@ module Docs
end
end
def get_latest_version(&block)
get_npm_version('@angular/core', &block)
def get_latest_version(options, &block)
get_npm_version('@angular/core', options, &block)
end
private

@ -70,8 +70,8 @@ module Docs
self.base_url = "https://code.angularjs.org/#{release}/docs/partials/"
end
def get_latest_version(&block)
get_npm_version('angular', &block)
def get_latest_version(options, &block)
get_npm_version('angular', options, &block)
end
end
end

@ -88,8 +88,8 @@ module Docs
list_of_all_modules.html)
end
def get_latest_version(&block)
fetch_doc('https://docs.ansible.com/ansible/latest/index.html') do |doc|
def get_latest_version(options, &block)
fetch_doc('https://docs.ansible.com/ansible/latest/index.html', options) do |doc|
block.call doc.at_css('.DocSiteProduct-CurrentVersion').content.strip
end
end

@ -34,8 +34,8 @@ module Docs
Licensed under the Apache License, Version 2.0.
HTML
def get_latest_version(&block)
fetch_doc('http://httpd.apache.org/docs/') do |doc|
def get_latest_version(options, &block)
fetch_doc('http://httpd.apache.org/docs/', options) do |doc|
block.call doc.at_css('#apcontents > ul a')['href'][0...-1]
end
end

@ -43,8 +43,8 @@ module Docs
self.base_url = "https://pig.apache.org/docs/r#{release}/"
end
def get_latest_version(&block)
fetch_doc('https://pig.apache.org/') do |doc|
def get_latest_version(options, &block)
fetch_doc('https://pig.apache.org/', options) do |doc|
item = doc.at_css('div[id="menu_1.2"] > .menuitem:last-child')
block.call item.content.strip.sub(/Release /, '')
end

@ -18,8 +18,8 @@ module Docs
Licensed under the MIT License.
HTML
def get_latest_version(&block)
fetch_doc('https://caolan.github.io/async/') do |doc|
def get_latest_version(options, &block)
fetch_doc('https://caolan.github.io/async/', options) do |doc|
version = doc.at_css('#version-dropdown > a').content.strip[1..-1]
block.call version
end

@ -23,8 +23,8 @@ module Docs
'<div></div>'
end
def get_latest_version(&block)
fetch_doc('https://babeljs.io/docs/en/') do |doc|
def get_latest_version(options, &block)
fetch_doc('https://babeljs.io/docs/en/', options) do |doc|
block.call doc.at_css('a[href="/versions"] > h3').content
end
end

@ -21,8 +21,8 @@ module Docs
Licensed under the MIT License.
HTML
def get_latest_version(&block)
fetch_doc('https://backbonejs.org/') do |doc|
def get_latest_version(options, &block)
fetch_doc('https://backbonejs.org/', options) do |doc|
version = doc.at_css('.version').content
block.call version[1...-1]
end

@ -18,8 +18,8 @@ module Docs
Licensed under the GNU Free Documentation License.
HTML
def get_latest_version(&block)
fetch('https://www.gnu.org/software/bash/manual/html_node/index.html') do |body|
def get_latest_version(options, &block)
fetch('https://www.gnu.org/software/bash/manual/html_node/index.html', options) do |body|
version = body.scan(/, Version ([0-9.]+)/)[0][0]
block.call version[0...-1]
end

@ -19,8 +19,8 @@ module Docs
Licensed under the MIT License.
HTML
def get_latest_version(&block)
get_npm_version('bluebird', &block)
def get_latest_version(options, &block)
get_npm_version('bluebird', options, &block)
end
end
end

@ -34,5 +34,11 @@ module Docs
options[:only] = %w(getting-started/ css/ components/ javascript/)
end
def get_latest_version(options, &block)
fetch_doc('https://getbootstrap.com/', options) do |doc|
block.call doc.at_css('#bd-versions').content.strip[1..-1]
end
end
end
end

@ -27,5 +27,12 @@ module Docs
self.release = '0.11.7'
self.base_url = "https://bottlepy.org/docs/#{self.version}/"
end
def get_latest_version(options, &block)
fetch_doc('https://bottlepy.org/docs/stable/', options) do |doc|
label = doc.at_css('.sphinxsidebarwrapper > ul > li > b')
block.call label.content.sub(/Bottle /, '')
end
end
end
end

@ -19,5 +19,9 @@ module Docs
&copy; 2018 Bower contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('bower', options, &block)
end
end
end

@ -71,6 +71,12 @@ module Docs
self.base_url = 'https://api.cakephp.org/2.7/'
end
def get_latest_version(options, &block)
fetch_doc('https://api.cakephp.org/3.7/', options) do |doc|
block.call doc.at_css('.version-picker .dropdown-toggle').content.strip
end
end
private
def parse(response)

@ -23,5 +23,9 @@ module Docs
&copy; 2016 Chai.js Assertion Library<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('chai', options, &block)
end
end
end

@ -47,5 +47,12 @@ module Docs
options[:only_patterns] = [/\A#{client_path}\//, /\A#{server_path}\//]
end
def get_latest_version(options, &block)
fetch_doc('https://docs-archive.chef.io/', options) do |doc|
cell = doc.at_css('.main-archives > tr:nth-child(2) > td:nth-child(2)')
block.call cell.content.sub(/Chef Client /, '')
end
end
end
end

@ -27,5 +27,11 @@ module Docs
self.release = '1.7'
self.base_url = 'https://clojure.github.io/clojure/branch-clojure-1.7.0/'
end
def get_latest_version(options, &block)
fetch_doc('http://clojure.github.io/clojure/index.html', options) do |doc|
block.call doc.at_css('#header-version').content[1..-1]
end
end
end
end

@ -59,5 +59,12 @@ module Docs
self.release = '3.5.2'
self.base_url = 'https://cmake.org/cmake/help/v3.5/'
end
def get_latest_version(options, &block)
fetch_doc('https://cmake.org/documentation/', options) do |doc|
link = doc.at_css('.entry-content ul > li > strong > a > big')
block.call link.content.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -18,5 +18,11 @@ module Docs
&copy; 2011 Michael Bodnarchuk and contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
fetch_doc('https://codeception.com/changelog', options) do |doc|
block.call doc.at_css('#page > h4').content
end
end
end
end

@ -21,5 +21,9 @@ module Docs
&copy; 2015 DavertMik &lt;davert@codegyre.com&gt; (http://codegyre.com)<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('codeceptjs', options, &block)
end
end
end

@ -38,5 +38,12 @@ module Docs
version '3' do
self.release = '3.1.8'
end
def get_latest_version(options, &block)
fetch_doc('https://codeigniter.com/user_guide/changelog.html', options) do |doc|
header = doc.at_css('#change-log h2')
block.call header.content.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -30,5 +30,9 @@ module Docs
options[:container] = '.container'
end
def get_latest_version(options, &block)
get_npm_version('coffeescript', options, &block)
end
end
end

@ -42,5 +42,14 @@ module Docs
self.release = '6.5.0'
self.base_url = 'https://cordova.apache.org/docs/en/6.x/'
end
def get_latest_version(options, &block)
fetch_doc('https://cordova.apache.org/docs/en/latest/', options) do |doc|
label = doc.at_css('#versionDropdown').content.strip
version = label.scan(/([0-9.]+)/)[0][0]
version = version[0...-1] if version.end_with?('.')
block.call version
end
end
end
end

@ -34,5 +34,11 @@ module Docs
HTML
end
}
def get_latest_version(options, &block)
fetch('https://crystal-lang.org/api', options) do |body|
block.call body.scan(/Crystal Docs ([0-9.]+)/)[0][0]
end
end
end
end

@ -26,5 +26,11 @@ module Docs
def initial_urls
%w(https://dlang.org/phobos/index.html https://dlang.org/spec/intro.html)
end
def get_latest_version(options, &block)
fetch_doc('https://dlang.org/changelog/', options) do |doc|
block.call doc.at_css('#content > ul > li:nth-child(2) > a')['id']
end
end
end
end

@ -58,5 +58,9 @@ module Docs
options[:root_title] = 'D3.js'
options[:only_patterns] = [/\.md\z/]
end
def get_latest_version(options, &block)
get_npm_version('d3', options, &block)
end
end
end

@ -31,5 +31,12 @@ module Docs
self.release = '1.24.3'
self.base_url = "https://api.dartlang.org/stable/#{release}/"
end
def get_latest_version(options, &block)
fetch_doc('https://api.dartlang.org/', options) do |doc|
label = doc.at_css('footer > span').content.strip
block.call label.sub(/Dart /, '')
end
end
end
end

@ -63,5 +63,11 @@ module Docs
self.release = '1.8.18'
self.base_url = 'https://docs.djangoproject.com/en/1.8/'
end
def get_latest_version(options, &block)
fetch_doc('https://docs.djangoproject.com/', options) do |doc|
block.call doc.at_css('#doc-versions > li.current > span > strong').content
end
end
end
end

@ -137,5 +137,12 @@ module Docs
options[:container] = '#docs'
options[:only_patterns] << /\Aswarm\//
end
def get_latest_version(options, &block)
fetch_doc('https://docs.docker.com/', options) do |doc|
label = doc.at_css('.nav-container button.dropdown-toggle').content.strip
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -36,6 +36,12 @@ module Docs
urls.map { |url| "<a href='#{url}'>#{url}</a>" }.join
end
def get_latest_version(options, &block)
fetch_doc('https://dojotoolkit.org/api/', options) do |doc|
block.call doc.at_css('#versionSelector > option[selected]').content
end
end
private
def get_url_list(json, set = Set.new)

@ -98,5 +98,14 @@ module Docs
/\A[\w\-\.]+\.php\/7\.x\z/
]
end
def get_latest_version(options, &block)
fetch_doc('http://cgit.drupalcode.org/drupal', options) do |doc|
version = doc.at_css('td.form > form > select > option[selected]').content
version = version.scan(/([0-9.]+)/)[0][0]
version = version[0...-1] if version.end_with?('.')
block.call version
end
end
end
end

@ -22,5 +22,11 @@ module Docs
&copy; 2013&ndash;2018 GitHub Inc.<br>
Licensed under the MIT license.
HTML
def get_latest_version(options, &block)
fetch_doc('https://electronjs.org/docs', options) do |doc|
block.call doc.at_css('.docs-version').content
end
end
end
end

@ -97,5 +97,11 @@ module Docs
'https://elixir-lang.org/getting-started/'
]
end
def get_latest_version(options, &block)
fetch_doc('https://hexdocs.pm/elixir/api-reference.html', options) do |doc|
block.call doc.at_css('h2.sidebar-projectVersion').content.strip[1..-1]
end
end
end
end

@ -56,5 +56,11 @@ module Docs
https://emberjs.com/api/ember-data/2.14/classes/DS
)
end
def get_latest_version(options, &block)
fetch_doc('https://emberjs.com/api/ember/release', options) do |doc|
block.call doc.at_css('.sidebar > .select-container .ember-power-select-selected-item').content.strip
end
end
end
end

@ -55,5 +55,11 @@ module Docs
version '18' do
self.release = '18.3'
end
def get_latest_version(options, &block)
fetch_doc('https://www.erlang.org/downloads', options) do |doc|
block.call doc.at_css('.col-lg-3 > ul > li').content.strip
end
end
end
end

@ -20,5 +20,9 @@ module Docs
&copy; JS Foundation and other contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('eslint', options, &block)
end
end
end

@ -28,5 +28,9 @@ module Docs
&copy; 2017 StrongLoop, IBM, and other expressjs.com contributors.<br>
Licensed under the Creative Commons Attribution-ShareAlike License v3.0.
HTML
def get_latest_version(options, &block)
get_npm_version('express', options, &block)
end
end
end

@ -33,5 +33,11 @@ module Docs
self.release = '1.2.0'
self.base_url = "https://falcon.readthedocs.io/en/#{self.release}/"
end
def get_latest_version(options, &block)
fetch_doc('https://falcon.readthedocs.io/en/stable/changes/index.html', options) do |doc|
block.call doc.at_css('#changelogs ul > li > a').content
end
end
end
end

@ -46,5 +46,11 @@ module Docs
self.release = '2.2.0'
self.base_url = "https://fishshell.com/docs/#{version}/"
end
def get_latest_version(options, &block)
fetch_doc('http://fishshell.com/docs/current/index.html', options) do |doc|
block.call doc.at_css('#toc-index').content.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -18,5 +18,9 @@ module Docs
&copy; 2013&ndash;present Facebook Inc.<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('flow-bin', options, &block)
end
end
end

@ -19,5 +19,11 @@ module Docs
&copy; 2005&ndash;2018 Linus Torvalds and others<br>
Licensed under the GNU General Public License version 2.
HTML
def get_latest_version(options, &block)
fetch_doc('https://git-scm.com/', options) do |doc|
block.call doc.at_css('.version').content.strip
end
end
end
end

@ -99,5 +99,12 @@ module Docs
options[:replace_paths] = CPP_PATHS
end
def get_latest_version(options, &block)
fetch_doc('https://gcc.gnu.org/onlinedocs/', options) do |doc|
label = doc.at_css('ul > li > ul > li > a').content.strip
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -25,5 +25,12 @@ module Docs
self.release = '4.9.3'
self.base_url = "https://gcc.gnu.org/onlinedocs/gcc-#{release}/gfortran/"
end
def get_latest_version(options, &block)
fetch_doc('https://gcc.gnu.org/onlinedocs/', options) do |doc|
label = doc.at_css('ul > li > ul > li > a').content.strip
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -24,6 +24,15 @@ module Docs
Licensed under the Creative Commons Attribution License 3.0.
HTML
def get_latest_version(options, &block)
fetch_doc('https://golang.org/pkg/', options) do |doc|
footer = doc.at_css('#footer').content
version = footer.scan(/go([0-9.]+)/)[0][0]
version = version[0...-1] if version.end_with?('.')
block.call version
end
end
private
def parse(response) # Hook here because Nokogori removes whitespace from textareas

@ -37,5 +37,11 @@ module Docs
self.release = '2.1'
self.base_url = "http://docs.godotengine.org/en/#{self.version}/"
end
def get_latest_version(options, &block)
fetch_doc('https://docs.godotengine.org/', options) do |doc|
block.call doc.at_css('.version').content.strip
end
end
end
end

@ -17,5 +17,11 @@ module Docs
&copy; 2011&ndash;2016 The Graphite Project<br>
Licensed under the Apache License, Version 2.0.
HTML
def get_latest_version(options, &block)
fetch_doc('https://graphite.readthedocs.io/en/latest/releases.html', options) do |doc|
block.call doc.at_css('#release-notes li > a').content
end
end
end
end

@ -26,5 +26,9 @@ module Docs
&copy; GruntJS Team<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('grunt-cli', options, &block)
end
end
end

@ -19,5 +19,9 @@ module Docs
&copy; 2011&ndash;2017 by Yehuda Katz<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('handlebars', options, &block)
end
end
end

@ -68,5 +68,12 @@ module Docs
options[:only_patterns] = [/\Alibraries\//]
end
def get_latest_version(options, &block)
fetch_doc('https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/', options) do |doc|
label = doc.at_css('.related > ul > li:last-child').content
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -66,5 +66,12 @@ module Docs
version 'Python' do
self.base_url = 'https://api.haxe.org/python/'
end
def get_latest_version(options, &block)
fetch_doc('https://api.haxe.org/', options) do |doc|
label = doc.at_css('.container.main-content h1 > small').content
block.call label.sub(/version /, '')
end
end
end
end

@ -19,5 +19,11 @@ module Docs
&copy; 2009&ndash;present Homebrew contributors<br>
Licensed under the BSD 2-Clause License.
HTML
def get_latest_version(options, &block)
get_latest_github_release('Homebrew', 'brew', options) do |release|
block.call release['name']
end
end
end
end

@ -54,5 +54,9 @@ module Docs
JS
capybara.html
end
def get_latest_version(options, &block)
get_npm_version('immutable', options, &block)
end
end
end

@ -46,5 +46,12 @@ module Docs
&copy; 2015 InfluxData, Inc.<br>
Licensed under the MIT license.
HTML
def get_latest_version(options, &block)
fetch_doc('https://docs.influxdata.com/influxdb/', options) do |doc|
label = doc.at_css('.navbar--current-product').content.strip
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -17,5 +17,11 @@ module Docs
&copy; 2008&ndash;2017 Pivotal Labs<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_latest_github_release('jasmine', 'jasmine', options) do |release|
block.call release['name']
end
end
end
end

@ -28,5 +28,11 @@ module Docs
&copy; 2008&ndash;2018 Tom Preston-Werner and Jekyll contributors<br>
Licensed under the MIT license.
HTML
def get_latest_version(options, &block)
fetch_doc('https://jekyllrb.com/docs/', options) do |doc|
block.call doc.at_css('.meta a').content[1..-1]
end
end
end
end

@ -17,5 +17,11 @@ module Docs
&copy; 2014&ndash;present Facebook Inc.<br>
Licensed under the BSD License.
HTML
def get_latest_version(options, &block)
fetch_doc('https://jestjs.io/docs/en/getting-started', options) do |doc|
block.call doc.at_css('header > a > h3').content
end
end
end
end

@ -22,5 +22,9 @@ module Docs
/Selectors\/odd/i,
/index/i
]
def get_latest_version(options, &block)
get_npm_version('jquery', options, &block)
end
end
end

@ -16,5 +16,12 @@ module Docs
options[:fix_urls] = ->(url) do
url.sub! 'http://api.jquerymobile.com/', 'https://api.jquerymobile.com/'
end
def get_latest_version(options, &block)
fetch_doc('https://jquerymobile.com/', options) do |doc|
label = doc.at_css('.download-box > .download-option:last-child > span').content
block.call label.sub(/Version /, '')
end
end
end
end

@ -15,5 +15,9 @@ module Docs
options[:fix_urls] = ->(url) do
url.sub! 'http://api.jqueryui.com/', 'https://api.jqueryui.com/'
end
def get_latest_version(options, &block)
get_npm_version('jquery-ui', options, &block)
end
end
end

@ -21,5 +21,11 @@ module Docs
&copy; 2011&ndash;2017 the contributors to the JSDoc 3 documentation project<br>
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
HTML
def get_latest_version(options, &block)
get_latest_github_release('jsdoc3', 'jsdoc', options) do |release|
block.call release['tag_name']
end
end
end
end

@ -49,5 +49,11 @@ module Docs
html_filters.push 'julia/entries_sphinx', 'julia/clean_html_sphinx', 'sphinx/clean_html'
end
def get_latest_version(options, &block)
get_latest_github_release('JuliaLang', 'julia', options) do |release|
block.call release['tag_name'][1..-1]
end
end
end
end

@ -33,5 +33,11 @@ module Docs
&copy; Steven Sanderson, the Knockout.js team, and other contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_latest_github_release('knockout', 'knockout', options) do |release|
block.call release['tag_name'][1..-1]
end
end
end
end

@ -34,5 +34,9 @@ module Docs
&copy; 2018 Koa contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('koa', options, &block)
end
end
end

@ -28,5 +28,11 @@ module Docs
&copy; 2010&ndash;2018 JetBrains s.r.o.<br>
Licensed under the Apache License, Version 2.0.
HTML
def get_latest_version(options, &block)
get_latest_github_release('JetBrains', 'kotlin', options) do |release|
block.call release['tag_name'][1..-1]
end
end
end
end

@ -133,5 +133,11 @@ module Docs
url
end
end
def get_latest_version(options, &block)
get_latest_github_release('laravel', 'laravel', options) do |release|
block.call release['tag_name'][1..-1]
end
end
end
end

@ -39,5 +39,11 @@ module Docs
self.base_url = "https://leafletjs.com/reference-#{release}.html"
end
def get_latest_version(options, &block)
fetch_doc('https://leafletjs.com/index.html', options) do |doc|
link = doc.css('ul > li > a').to_a.select {|node| node.content == 'Docs'}.first
block.call link['href'].scan(/reference-([0-9.]+)\.html/)[0][0]
end
end
end
end

@ -21,5 +21,12 @@ module Docs
&copy; 2009&ndash;2016 The Core Less Team<br>
Licensed under the Creative Commons Attribution License 3.0.
HTML
def get_latest_version(options, &block)
fetch_doc('http://lesscss.org/features/', options) do |doc|
label = doc.at_css('.footer-links > li').content
block.call label.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -19,5 +19,11 @@ module Docs
&copy; 2005, 2006 Tobias Luetke<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_github_tags('Shopify', 'liquid', options) do |tags|
block.call tags[0]['name'][1..-1]
end
end
end
end

@ -32,5 +32,11 @@ module Docs
self.release = '2.4.2'
self.base_url = "https://lodash.com/docs/#{release}"
end
def get_latest_version(options, &block)
fetch_doc('https://lodash.com/docs/', options) do |doc|
block.call doc.at_css('#version > option[selected]').content
end
end
end
end

@ -39,5 +39,11 @@ module Docs
&copy; 2006&ndash;2016 L&Ouml;VE Development Team<br>
Licensed under the GNU Free Documentation License, Version 1.3.
HTML
def get_latest_version(options, &block)
fetch_doc('https://love2d.org/wiki/Version_History', options) do |doc|
block.call doc.at_css('#mw-content-text table a').content
end
end
end
end

@ -26,5 +26,11 @@ module Docs
self.release = '5.1.5'
self.base_url = 'https://www.lua.org/manual/5.1/'
end
def get_latest_version(options, &block)
fetch_doc('https://www.lua.org/manual/', options) do |doc|
block.call doc.at_css('p.menubar > a').content
end
end
end
end

@ -38,5 +38,9 @@ module Docs
html_filters.push 'marionette/entries_v2'
end
def get_latest_version(options, &block)
get_npm_version('backbone.marionette', options, &block)
end
end
end

@ -64,5 +64,11 @@ module Docs
"https://matplotlib.org/#{release}/mpl_toolkits/axes_grid/api/"
]
end
def get_latest_version(options, &block)
get_latest_github_release('matplotlib', 'matplotlib', options) do |release|
block.call release['tag_name'][1..-1]
end
end
end
end

@ -45,5 +45,11 @@ module Docs
self.base_urls = ['https://guide.meteor.com/v1.3/', "https://docs.meteor.com/v#{self.release}/"]
options[:fix_urls] = nil
end
def get_latest_version(options, &block)
fetch_doc('https://docs.meteor.com/#/full/', options) do |doc|
block.call doc.at_css('select.version-select > option').content
end
end
end
end

@ -18,5 +18,9 @@ module Docs
&copy; 2011&ndash;2018 JS Foundation and contributors<br>
Licensed under the Creative Commons Attribution 4.0 International License.
HTML
def get_latest_version(options, &block)
get_npm_version('mocha', options, &block)
end
end
end

@ -15,5 +15,9 @@ module Docs
&copy; 2009&ndash;2017 The Modernizr team<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_npm_version('modernizr', options, &block)
end
end
end

@ -22,5 +22,11 @@ module Docs
&copy; JS Foundation and other contributors<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
fetch_doc('http://momentjs.com/', options) do |doc|
block.call doc.at_css('.hero-title > h1 > span').content
end
end
end
end

@ -26,5 +26,12 @@ module Docs
&copy; 2010 LearnBoost<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
fetch_doc('https://mongoosejs.com/docs/', options) do |doc|
label = doc.at_css('.pure-menu-link').content.strip
block.call label.sub(/Version /, '')
end
end
end
end

@ -21,5 +21,11 @@ module Docs
&copy; Ryan Davis, seattle.rb<br>
Licensed under the MIT License.
HTML
def get_latest_version(options, &block)
get_github_file_contents('seattlerb', 'minitest', 'History.rdoc', options) do |contents|
block.call contents.scan(/([0-9.]+)/)[0][0]
end
end
end
end

@ -93,5 +93,11 @@ module Docs
version '4.1' do
self.release = '4.1.16'
end
def get_latest_version(options, &block)
get_latest_github_release('rails', 'rails', options) do |release|
block.call release['name']
end
end
end
end

@ -84,5 +84,17 @@ module Docs
version '2.2' do
self.release = '2.2.10'
end
def get_latest_version(options, &block)
get_github_tags('ruby', 'ruby', options) do |tags|
tags.each do |tag|
version = tag['name'].gsub(/_/, '.')[1..-1]
if !/^([0-9.]+)$/.match(version).nil? && version.count('.') == 2
block.call version
break
end
end
end
end
end
end

@ -1,4 +1,12 @@
class UpdatesCLI < Thor
# The GitHub user that is allowed to upload reports
# TODO: Update this before creating a PR
UPLOAD_USER = 'jmerle'
# The repository to create an issue in when uploading the results
# TODO: Update this before creating a PR
UPLOAD_REPO = 'jmerle/devdocs'
def self.to_s
'Updates'
end
@ -6,10 +14,14 @@ class UpdatesCLI < Thor
def initialize(*args)
require 'docs'
require 'progress_bar'
require 'terminal-table'
require 'date'
super
end
desc 'check [--verbose] [doc]...', 'Check for outdated documentations'
desc 'check [--github-token] [--upload] [--verbose] [doc]...', 'Check for outdated documentations'
option :github_token, :type => :string
option :upload, :type => :boolean
option :verbose, :type => :boolean
def check(*names)
# Convert names to a list of Scraper instances
@ -19,23 +31,26 @@ class UpdatesCLI < Thor
# 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
opts = {
logger: logger
}
results = docs.map do |doc|
result = check_doc(doc)
progress_bar.increment!
result
if options.key?(:github_token)
opts[:github_token] = options[:github_token]
end
valid_results = results.select {|result| result.is_a?(Hash)}
with_progress_bar do |bar|
bar.max = docs.length
bar.write
end
up_to_date_results = valid_results.select {|result| !result[:is_outdated]}
outdated_results = valid_results.select {|result| result[:is_outdated]}
results = docs.map do |doc|
result = check_doc(doc, opts)
with_progress_bar(&:increment!)
result
end
log_results('Up-to-date', up_to_date_results) if options[:verbose] and !up_to_date_results.empty?
logger.info("") if options[:verbose] and !up_to_date_results.empty? and !outdated_results.empty?
log_results('Outdated', outdated_results) unless outdated_results.empty?
process_results(results)
rescue Docs::DocNotFound => error
logger.error(error)
logger.info('Run "thor docs:list" to see the list of docs.')
@ -43,53 +58,260 @@ class UpdatesCLI < Thor
private
def check_doc(doc)
def check_doc(doc, options)
# Newer scraper versions always come before older scraper versions
# Therefore, the first item's release value is the latest current scraper version
# Therefore, the first item's release value is the latest scraper version
#
# For example, a scraper could scrape 3 versions: 10, 11 and 12
# doc.versions.first would be the scraper for version 12
instance = doc.versions.first.new
return nil unless instance.class.method_defined?(:options)
current_version = instance.options[:release]
return nil if current_version.nil?
scraper_version = instance.class.method_defined?(:options) ? instance.options[:release] : nil
return error_result(doc, '`options[:release]` does not exist') if scraper_version.nil?
logger.debug("Checking #{doc.name}")
instance.get_latest_version do |latest_version|
instance.get_latest_version(options) do |latest_version|
return {
name: doc.name,
current_version: current_version,
scraper_version: scraper_version,
latest_version: latest_version,
is_outdated: instance.is_outdated(current_version, latest_version)
is_outdated: instance.is_outdated(scraper_version, latest_version)
}
end
return nil
rescue NotImplementedError
logger.warn("Couldn't check #{doc.name}, get_latest_version is not implemented")
error_result(doc, '`get_latest_version` is not implemented')
rescue
logger.error("Error while checking #{doc.name}")
raise
end
def log_results(label, results)
logger.info("#{label} documentations (#{results.length}):")
def error_result(doc, reason)
{
name: doc.name,
error: reason
}
end
def process_results(results)
successful_results = results.select {|result| result.key?(:is_outdated)}
failed_results = results.select {|result| result.key?(:error)}
up_to_date_results = successful_results.select {|result| !result[:is_outdated]}
outdated_results = successful_results.select {|result| result[:is_outdated]}
log_results(outdated_results, up_to_date_results, failed_results)
upload_results(outdated_results, up_to_date_results, failed_results) if options[:upload]
end
#
# Result logging methods
#
def log_results(outdated_results, up_to_date_results, failed_results)
log_failed_results(failed_results) unless failed_results.empty?
log_successful_results('Up-to-date', up_to_date_results) unless up_to_date_results.empty?
log_successful_results('Outdated', outdated_results) unless outdated_results.empty?
end
def log_successful_results(label, results)
title = "#{label} documentations (#{results.length})"
headings = ['Documentation', 'Scraper version', 'Latest version']
rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}
table = Terminal::Table.new :title => title, :headings => headings, :rows => rows
puts table
end
def log_failed_results(results)
title = "Documentations that could not be checked (#{results.length})"
headings = %w(Documentation Reason)
rows = results.map {|result| [result[:name], result[:error]]}
table = Terminal::Table.new :title => title, :headings => headings, :rows => rows
puts table
end
#
# Upload methods
#
def upload_results(outdated_results, up_to_date_results, failed_results)
# We can't create issues without a GitHub token
unless options.key?(:github_token)
logger.error('Please specify a GitHub token with the public_repo permission for devdocs-bot with the --github-token parameter')
return
end
logger.info('Uploading the results to a new GitHub issue')
logger.info('Checking if the GitHub token belongs to the correct user')
github_get('/user') do |user|
# Only allow the DevDocs bot to upload reports
if user['login'] == UPLOAD_USER
issue = results_to_issue(outdated_results, up_to_date_results, failed_results)
logger.info('Creating a new GitHub issue')
github_post("/repos/#{UPLOAD_REPO}/issues", issue) do |created_issue|
search_params = {
q: "Documentation versions report in:title author:#{UPLOAD_USER} is:issue repo:#{UPLOAD_REPO}",
sort: 'created',
order: 'desc'
}
logger.info('Checking if the previous issue is still open')
github_get('/search/issues', search_params) do |matching_issues|
previous_issue = matching_issues['items'].find {|item| item['number'] != created_issue['number']}
if previous_issue.nil?
logger.info('No previous issue found')
log_upload_success(created_issue)
else
comment = "This report was superseded by ##{created_issue['number']}."
logger.info('Commenting on the previous issue')
github_post("/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}/comments", {body: comment}) do |_|
if previous_issue['closed_at'].nil?
logger.info('Closing the previous issue')
github_patch("/repos/#{UPLOAD_REPO}/issues/#{previous_issue['number']}", {state: 'closed'}) do |_|
log_upload_success(created_issue)
end
else
logger.info('The previous issue has already been closed')
log_upload_success(created_issue)
end
end
end
end
end
else
logger.error("Only #{UPLOAD_USER} is supposed to upload the results to a new issue. The specified github token is not for #{UPLOAD_USER}.")
end
end
end
def results_to_issue(outdated_results, up_to_date_results, failed_results)
results = [
successful_results_to_markdown('Outdated', outdated_results),
successful_results_to_markdown('Up-to-date', up_to_date_results),
failed_results_to_markdown(failed_results)
]
results_str = results.select {|result| !result.nil?}.join("\n\n")
title = "Documentation versions report for #{Date.today.strftime('%B')} 2019"
body = <<-MARKDOWN
## What is this?
This is an automatically created issue which contains information about the version status of the documentations available on DevDocs. The results of this report can be used by maintainers when updating outdated documentations.
Maintainers can close this issue when all documentations are up-to-date. This issue is automatically closed when the next report is created.
## Results
The #{outdated_results.length + up_to_date_results.length + failed_results.length} documentations are divided as follows:
- #{outdated_results.length} that #{outdated_results.length == 1 ? 'is' : 'are'} outdated
- #{up_to_date_results.length} that #{up_to_date_results.length == 1 ? 'is' : 'are'} up-to-date (patch updates are ignored)
- #{failed_results.length} that could not be checked
MARKDOWN
{
title: title,
body: body.strip + "\n\n" + results_str
}
end
def successful_results_to_markdown(label, results)
return nil if results.empty?
title = "#{label} documentations (#{results.length})"
headings = ['Documentation', 'Scraper version', 'Latest version']
rows = results.map {|result| [result[:name], result[:scraper_version], result[:latest_version]]}
results_to_markdown(title, headings, rows)
end
def failed_results_to_markdown(results)
return nil if results.empty?
title = "Documentations that could not be checked (#{results.length})"
headings = %w(Documentation Reason)
rows = results.map {|result| [result[:name], result[:error]]}
results_to_markdown(title, headings, rows)
end
def results_to_markdown(title, headings, rows)
"<details>\n<summary>#{title}</summary>\n\n#{create_markdown_table(headings, rows)}\n</details>"
end
def create_markdown_table(headings, rows)
header = headings.join(' | ')
separator = '-|' * headings.length
body = rows.map {|row| row.join(' | ')}
header + "\n" + separator[0...-1] + "\n" + body.join("\n")
end
def log_upload_success(created_issue)
logger.info("Successfully uploaded the results to #{created_issue['html_url']}")
end
#
# HTTP utilities
#
def github_get(endpoint, params = {}, &block)
github_request(endpoint, {method: :get, params: params}, &block)
end
def github_post(endpoint, params, &block)
github_request(endpoint, {method: :post, body: params.to_json}, &block)
end
def github_patch(endpoint, params, &block)
github_request(endpoint, {method: :patch, body: params.to_json}, &block)
end
results.each do |result|
logger.info("#{result[:name]}: #{result[:current_version]} -> #{result[:latest_version]}")
def github_request(endpoint, opts, &block)
url = "https://api.github.com#{endpoint}"
# GitHub token authentication
opts[:headers] = {
Authorization: "token #{options[:github_token]}"
}
# GitHub requires the Content-Type to be application/json when a body is passed
if opts.key?(:body)
opts[:headers]['Content-Type'] = 'application/json'
end
logger.debug("Making a #{opts[:method]} request to #{url}")
Docs::Request.run(url, opts) do |response|
# response.success? is false if the response code is 201
# GitHub returns 201 Created after an issue is created
if response.success? || response.code == 201
block.call JSON.parse(response.body)
else
logger.error("Couldn't make a #{opts[:method]} request to #{url} (response code #{response.code})")
block.call nil
end
end
end
# A utility method which ensures no progress bar is shown when stdout is not a tty
def with_progress_bar(&block)
return unless $stdout.tty?
@progress_bar ||= ::ProgressBar.new
block.call @progress_bar
end
def logger
@logger ||= Logger.new($stdout).tap do |logger|
logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
logger.formatter = proc do |severity, datetime, progname, msg|
prefix = severity != "INFO" ? "[#{severity}] " : ""
"#{prefix}#{msg}\n"
end
logger.formatter = proc {|severity, datetime, progname, msg| "[#{severity}] #{msg}\n"}
end
end
end

Loading…
Cancel
Save