gem_updater 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/gem_update +12 -8
- data/lib/gem_updater.rb +18 -15
- data/lib/gem_updater/gem_file.rb +21 -8
- data/lib/gem_updater/ruby_gems_fetcher.rb +36 -27
- data/lib/gem_updater/source_page_parser.rb +62 -39
- data/lib/gem_updater_template.erb +3 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66ba0cea1b0c09848cc7e9662257d9f9e366b487
|
4
|
+
data.tar.gz: 20527e80b3fc8da321ef1539a59d4e693aac2885
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0740383ec2b47aa3d68adeb7f96e49e7bb7210e8c08ae74d02efc9557e3fc0a17e18fa7de9be0bfae404db30a091f0ba5dc09c7fe53d8c1b4c69dd8fb8beb559
|
7
|
+
data.tar.gz: bb02299d459070caadb163aa9fe2f9624eacd5bd86aca23510844cff14ea5a19d50c52bdce416104194e45d13f51eed3c591808f5b82c95604d800bc6490440d
|
data/bin/gem_update
CHANGED
@@ -2,34 +2,38 @@
|
|
2
2
|
require 'optparse'
|
3
3
|
|
4
4
|
# Exit cleanly from an early interrupt
|
5
|
-
Signal.trap(
|
5
|
+
Signal.trap('INT') { exit 1 }
|
6
6
|
|
7
|
-
|
7
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
8
8
|
require 'gem_updater'
|
9
9
|
|
10
10
|
Bundler.ui = Bundler::UI::Shell.new
|
11
11
|
|
12
12
|
options = {}
|
13
13
|
OptionParser.new do |opts|
|
14
|
-
opts.on(
|
15
|
-
options[
|
14
|
+
opts.on('-c', '--commit', 'Auto commit') do |v|
|
15
|
+
options[:commit] = v
|
16
16
|
end
|
17
17
|
end.parse!
|
18
18
|
|
19
19
|
gems = GemUpdater::Updater.new
|
20
|
-
gems.update!(
|
20
|
+
gems.update!(ARGV)
|
21
21
|
|
22
22
|
if gems.gemfile.changes.any?
|
23
|
-
if options[
|
23
|
+
if options[:commit]
|
24
24
|
require 'tempfile'
|
25
|
-
file = Tempfile.new(
|
25
|
+
file = Tempfile.new('gem_updater')
|
26
26
|
file.write "UPDATE gems\n\n"
|
27
27
|
file.write gems.format_diff.join
|
28
28
|
file.close
|
29
29
|
|
30
30
|
gemfile = Bundler.default_gemfile.to_s
|
31
31
|
|
32
|
-
system %(
|
32
|
+
system %(
|
33
|
+
git add #{gemfile} #{gemfile}.lock \
|
34
|
+
&& git commit -t #{file.path} --allow-empty-message
|
35
|
+
)
|
36
|
+
|
33
37
|
file.unlink
|
34
38
|
else
|
35
39
|
Bundler.ui.confirm "\nHere are your changes:"
|
data/lib/gem_updater.rb
CHANGED
@@ -4,8 +4,8 @@ require 'gem_updater/source_page_parser'
|
|
4
4
|
|
5
5
|
module GemUpdater
|
6
6
|
|
7
|
-
# Updater's main responsability is to fill changes
|
8
|
-
# of `Gemfile`, and then format them.
|
7
|
+
# Updater's main responsability is to fill changes
|
8
|
+
# happened before and after update of `Gemfile`, and then format them.
|
9
9
|
class Updater
|
10
10
|
attr_accessor :gemfile
|
11
11
|
|
@@ -19,8 +19,8 @@ module GemUpdater
|
|
19
19
|
# 2. find changelogs for updated gems
|
20
20
|
#
|
21
21
|
# @param gems [Array] list of gems to update
|
22
|
-
def update!(
|
23
|
-
gemfile.update!(
|
22
|
+
def update!(gems)
|
23
|
+
gemfile.update!(gems)
|
24
24
|
gemfile.compute_changes
|
25
25
|
|
26
26
|
fill_changelogs
|
@@ -35,7 +35,7 @@ module GemUpdater
|
|
35
35
|
# on the gems that were updated.
|
36
36
|
def format_diff
|
37
37
|
gemfile.changes.map do |gem, details|
|
38
|
-
ERB.new(
|
38
|
+
ERB.new(template, nil, '<>').result(binding)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -47,29 +47,32 @@ module GemUpdater
|
|
47
47
|
|
48
48
|
gemfile.changes.each do |gem_name, details|
|
49
49
|
threads << Thread.new do
|
50
|
-
if source_uri = find_source(
|
51
|
-
source_page = GemUpdater::SourcePageParser.new(
|
50
|
+
if source_uri = find_source(gem_name, details[:source])
|
51
|
+
source_page = GemUpdater::SourcePageParser.new(
|
52
|
+
url: source_uri, version: details[:versions][:new]
|
53
|
+
)
|
52
54
|
|
53
|
-
|
55
|
+
if source_page.changelog
|
56
|
+
gemfile.changes[gem_name][:changelog] = source_page.changelog
|
57
|
+
end
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
58
|
-
threads.each(
|
62
|
+
threads.each(&:join)
|
59
63
|
end
|
60
64
|
|
61
|
-
|
62
65
|
# Find where is hosted the source of a gem
|
63
66
|
#
|
64
67
|
# @param gem [String] the name of the gem
|
65
68
|
# @param source [Bundler::Source] gem's source
|
66
69
|
# @return [String] url where gem is hosted
|
67
|
-
def find_source(
|
70
|
+
def find_source(gem, source)
|
68
71
|
case source
|
69
72
|
when Bundler::Source::Rubygems
|
70
|
-
GemUpdater::RubyGemsFetcher.new(
|
73
|
+
GemUpdater::RubyGemsFetcher.new(gem, source).source_uri
|
71
74
|
when Bundler::Source::Git
|
72
|
-
source.uri.gsub(
|
75
|
+
source.uri.gsub(/^git/, 'http').chomp('.git')
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
@@ -79,9 +82,9 @@ module GemUpdater
|
|
79
82
|
# @return [ERB] the template
|
80
83
|
def template
|
81
84
|
@template ||= begin
|
82
|
-
File.read(
|
85
|
+
File.read("#{Dir.home}/.gem_updater_template.erb")
|
83
86
|
rescue Errno::ENOENT
|
84
|
-
File.read(
|
87
|
+
File.read(File.expand_path('../../lib/gem_updater_template.erb', __FILE__))
|
85
88
|
end
|
86
89
|
end
|
87
90
|
end
|
data/lib/gem_updater/gem_file.rb
CHANGED
@@ -12,9 +12,9 @@ module GemUpdater
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Run `bundle update` to update gems.
|
15
|
-
def update!(
|
16
|
-
Bundler.ui.warn
|
17
|
-
Bundler::CLI.start(
|
15
|
+
def update!(gems)
|
16
|
+
Bundler.ui.warn 'Updating gems...'
|
17
|
+
Bundler::CLI.start(['update'] + gems)
|
18
18
|
end
|
19
19
|
|
20
20
|
# Compute the diffs between two `Gemfile.lock`.
|
@@ -24,10 +24,10 @@ module GemUpdater
|
|
24
24
|
get_spec_sets
|
25
25
|
|
26
26
|
old_spec_set.each do |old_gem|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
updated_gem = new_spec_set.find { |new_gem| new_gem.name == old_gem.name }
|
28
|
+
|
29
|
+
if updated_gem && old_gem.version != updated_gem.version
|
30
|
+
fill_changes(old_gem, updated_gem)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -52,7 +52,20 @@ module GemUpdater
|
|
52
52
|
# will return the same result.
|
53
53
|
# Use a hacky way to tell bundle we want to parse the new `Gemfile.lock`
|
54
54
|
def reinitialize_spec_set!
|
55
|
-
Bundler.remove_instance_variable(
|
55
|
+
Bundler.remove_instance_variable('@locked_gems')
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add changes to between two versions of a gem
|
59
|
+
#
|
60
|
+
# @param old_gem [Bundler::LazySpecification]
|
61
|
+
# @param new_gem [Bundler::LazySpecification]
|
62
|
+
def fill_changes(old_gem, updated_gem)
|
63
|
+
changes[old_gem.name] = {
|
64
|
+
versions: {
|
65
|
+
old: old_gem.version.to_s, new: updated_gem.version.to_s
|
66
|
+
},
|
67
|
+
source: updated_gem.source
|
68
|
+
}
|
56
69
|
end
|
57
70
|
end
|
58
71
|
end
|
@@ -6,11 +6,14 @@ module GemUpdater
|
|
6
6
|
|
7
7
|
# RubyGemsFetcher is a wrapper around rubygems API.
|
8
8
|
class RubyGemsFetcher
|
9
|
+
HTTP_TOO_MANY_REQUESTS = '429'.freeze
|
10
|
+
GEM_HOMEPAGES = %w[source_code_uri homepage_uri].freeze
|
11
|
+
|
9
12
|
attr_reader :gem_name, :source
|
10
13
|
|
11
14
|
# @param gem_name [String] name of the gem
|
12
15
|
# @param source [Bundler::Source] source of gem
|
13
|
-
def initialize(
|
16
|
+
def initialize(gem_name, source)
|
14
17
|
@gem_name = gem_name
|
15
18
|
@source = source
|
16
19
|
end
|
@@ -30,29 +33,30 @@ module GemUpdater
|
|
30
33
|
#
|
31
34
|
# @return [String|nil] uri of source code
|
32
35
|
def uri_from_rubygems
|
33
|
-
return unless source.remotes.map(
|
34
|
-
tries = 0
|
35
|
-
|
36
|
-
response = begin
|
37
|
-
JSON.parse( open( "https://rubygems.org/api/v1/gems/#{gem_name}.json" ).read )
|
38
|
-
rescue OpenURI::HTTPError => e
|
39
|
-
# We may trigger too many requests, in which case give rubygems a break
|
40
|
-
if e.io.status.include?( '429' )
|
41
|
-
if ( tries += 1 ) < 2
|
42
|
-
sleep 1 and retry
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
36
|
+
return unless source.remotes.map(&:host).include?('rubygems.org')
|
46
37
|
|
47
|
-
if response
|
38
|
+
if response = query_rubygems
|
48
39
|
response[
|
49
|
-
|
50
|
-
response[ key ] && !response[ key ].empty?
|
51
|
-
end
|
40
|
+
GEM_HOMEPAGES.find { |key| response[key] && !response[key].empty? }
|
52
41
|
]
|
53
42
|
end
|
54
43
|
end
|
55
44
|
|
45
|
+
# Make the real query to rubygems
|
46
|
+
# It may fail in case we trigger too many requests
|
47
|
+
#
|
48
|
+
# @param tries [Integer|nil] (optional) how many times we tried
|
49
|
+
def query_rubygems(tries = 0)
|
50
|
+
JSON.parse(open("https://rubygems.org/api/v1/gems/#{gem_name}.json").read)
|
51
|
+
rescue OpenURI::HTTPError => e
|
52
|
+
# We may trigger too many requests, in which case give rubygems a break
|
53
|
+
if e.io.status.include?(HTTP_TOO_MANY_REQUESTS)
|
54
|
+
if (tries += 1) < 2
|
55
|
+
sleep 1 and retry
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
56
60
|
# Look if gem can be found in another remote
|
57
61
|
#
|
58
62
|
# @return [String|nil] uri of source code
|
@@ -78,17 +82,22 @@ module GemUpdater
|
|
78
82
|
#
|
79
83
|
# @return [String|nil] uri of source code
|
80
84
|
def uri_from_railsassets
|
81
|
-
|
82
|
-
response
|
83
|
-
rescue JSON::ParserError
|
84
|
-
# if gem is not found, rails-assets returns a 200
|
85
|
-
# with html (instead of json) containing a 500...
|
86
|
-
rescue OpenURI::HTTPError
|
85
|
+
if response = query_railsassets
|
86
|
+
response['url'].gsub(/^git/, 'http')
|
87
87
|
end
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
# Make the real query to railsassets
|
91
|
+
def query_railsassets
|
92
|
+
JSON.parse(
|
93
|
+
open(
|
94
|
+
"https://rails-assets.org/packages/#{gem_name.gsub(/rails-assets-/, '')}"
|
95
|
+
).read
|
96
|
+
)
|
97
|
+
rescue JSON::ParserError
|
98
|
+
# if gem is not found, rails-assets returns a 200
|
99
|
+
# with html (instead of json) containing a 500...
|
100
|
+
rescue OpenURI::HTTPError
|
92
101
|
end
|
93
102
|
end
|
94
103
|
end
|
@@ -6,11 +6,16 @@ module GemUpdater
|
|
6
6
|
# SourcePageParser is responsible for parsing a source page where
|
7
7
|
# the gem code is hosted.
|
8
8
|
class SourcePageParser
|
9
|
+
HOSTS = { github: /github.com/, bitbucket: /bitbucket.org/ }.freeze
|
10
|
+
MARKUP_FILES = %w[.md .rdoc .textile].freeze
|
11
|
+
CHANGELOG_NAMES = %w[changelog ChangeLog history changes news].freeze
|
12
|
+
|
13
|
+
attr_reader :uri, :version
|
9
14
|
|
10
15
|
# @param url [String] url of page
|
11
16
|
# @param version [String] version of gem
|
12
|
-
def initialize(
|
13
|
-
@uri = correct_uri(
|
17
|
+
def initialize(url: nil, version: nil)
|
18
|
+
@uri = correct_uri(url)
|
14
19
|
@version = version
|
15
20
|
end
|
16
21
|
|
@@ -19,16 +24,18 @@ module GemUpdater
|
|
19
24
|
# @return [String, nil] URL of changelog
|
20
25
|
def changelog
|
21
26
|
@changelog ||= begin
|
22
|
-
|
23
|
-
|
27
|
+
if uri
|
28
|
+
Bundler.ui.warn "Looking for a changelog in #{uri}"
|
29
|
+
doc = Nokogiri::HTML(open(uri))
|
24
30
|
|
25
|
-
|
31
|
+
find_changelog(doc)
|
32
|
+
end
|
26
33
|
|
27
34
|
rescue OpenURI::HTTPError # Uri points to nothing
|
28
|
-
Bundler.ui.error "Cannot find #{
|
35
|
+
Bundler.ui.error "Cannot find #{uri}"
|
29
36
|
false
|
30
37
|
rescue Errno::ETIMEDOUT # timeout
|
31
|
-
Bundler.ui.error "#{
|
38
|
+
Bundler.ui.error "#{uri} is down"
|
32
39
|
false
|
33
40
|
end
|
34
41
|
end
|
@@ -40,28 +47,43 @@ module GemUpdater
|
|
40
47
|
#
|
41
48
|
# @param url [String] the url to parse
|
42
49
|
# @return [URI] valid URI
|
43
|
-
def correct_uri(
|
44
|
-
|
50
|
+
def correct_uri(url)
|
51
|
+
return unless String === url && !url.empty?
|
52
|
+
|
53
|
+
uri = URI(url)
|
54
|
+
|
45
55
|
if uri.scheme == 'http'
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
uri = URI "https://github.com#{uri.path}"
|
50
|
-
when uri.host.match( 'bitbucket.org' )
|
51
|
-
uri = URI "https://#{uri.host}#{uri.path}"
|
52
|
-
end
|
56
|
+
known_https(uri)
|
57
|
+
else
|
58
|
+
uri
|
53
59
|
end
|
60
|
+
end
|
54
61
|
|
55
|
-
|
62
|
+
# Some uris are not https, but we know they should be,
|
63
|
+
# in which case we have an https redirection
|
64
|
+
# which is not properly handled by open-uri
|
65
|
+
#
|
66
|
+
# @param uri [URI::HTTP]
|
67
|
+
# @return [URI::HTTPS|URI::HTTP]
|
68
|
+
def known_https(uri)
|
69
|
+
case
|
70
|
+
when uri.host =~ HOSTS[:github]
|
71
|
+
# remove possible subdomain like 'wiki.github.com'
|
72
|
+
URI "https://github.com#{uri.path}"
|
73
|
+
when uri.host =~ HOSTS[:bitbucket]
|
74
|
+
URI "https://#{uri.host}#{uri.path}"
|
75
|
+
else
|
76
|
+
uri
|
77
|
+
end
|
56
78
|
end
|
57
79
|
|
58
80
|
# Try to find where changelog might be.
|
59
81
|
#
|
60
82
|
# @param doc [Nokogiri::XML::Element] document of source page
|
61
|
-
def find_changelog(
|
62
|
-
case
|
83
|
+
def find_changelog(doc)
|
84
|
+
case uri.host
|
63
85
|
when 'github.com'
|
64
|
-
GitHubParser.new(
|
86
|
+
GitHubParser.new(doc, version).changelog
|
65
87
|
end
|
66
88
|
end
|
67
89
|
|
@@ -70,9 +92,9 @@ module GemUpdater
|
|
70
92
|
#
|
71
93
|
# @return [Array] list of possible names
|
72
94
|
def changelog_names
|
73
|
-
|
74
|
-
|
75
|
-
|
95
|
+
CHANGELOG_NAMES.flat_map do |name|
|
96
|
+
[name, name.upcase, name.capitalize]
|
97
|
+
end.uniq
|
76
98
|
end
|
77
99
|
|
78
100
|
# Some documents like the one written in markdown may contain
|
@@ -80,18 +102,20 @@ module GemUpdater
|
|
80
102
|
#
|
81
103
|
# @param file_name [String] file name of changelog
|
82
104
|
# @return [Boolean] true if file may contain an anchor
|
83
|
-
def changelog_may_contain_anchor?(
|
84
|
-
|
105
|
+
def changelog_may_contain_anchor?(file_name)
|
106
|
+
MARKUP_FILES.include?(File.extname(file_name))
|
85
107
|
end
|
86
108
|
|
87
109
|
# GitHubParser is responsible for parsing source code
|
88
110
|
# hosted on github.com.
|
89
111
|
class GitHubParser < SourcePageParser
|
90
|
-
BASE_URL = 'https://github.com'
|
112
|
+
BASE_URL = 'https://github.com'.freeze
|
113
|
+
|
114
|
+
attr_reader :doc, :version
|
91
115
|
|
92
116
|
# @param doc [Nokogiri::XML::Element] document of source page
|
93
117
|
# @param version [String] version of gem
|
94
|
-
def initialize(
|
118
|
+
def initialize(doc, version)
|
95
119
|
@doc = doc
|
96
120
|
@version = version
|
97
121
|
end
|
@@ -105,8 +129,8 @@ module GemUpdater
|
|
105
129
|
if url
|
106
130
|
full_url = BASE_URL + url
|
107
131
|
|
108
|
-
if changelog_may_contain_anchor?(
|
109
|
-
anchor = find_anchor(
|
132
|
+
if changelog_may_contain_anchor?(full_url)
|
133
|
+
anchor = find_anchor(full_url)
|
110
134
|
full_url += anchor if anchor
|
111
135
|
end
|
112
136
|
|
@@ -121,10 +145,9 @@ module GemUpdater
|
|
121
145
|
# @return [String, nil] url of changelog
|
122
146
|
def find_changelog_link
|
123
147
|
changelog_names.find do |name|
|
124
|
-
node =
|
125
|
-
|
126
|
-
|
127
|
-
end
|
148
|
+
node = doc.at_css(%(table.files a[title^="#{name}"]))
|
149
|
+
|
150
|
+
break node.attr('href') if node
|
128
151
|
end
|
129
152
|
end
|
130
153
|
|
@@ -132,13 +155,13 @@ module GemUpdater
|
|
132
155
|
#
|
133
156
|
# @param url [String] url of changelog
|
134
157
|
# @return [String, nil] anchor's href
|
135
|
-
def find_anchor(
|
136
|
-
changelog_page = Nokogiri::HTML(
|
137
|
-
anchor = changelog_page.css(
|
138
|
-
|
139
|
-
if anchor
|
140
|
-
anchor.attr( 'href' )
|
158
|
+
def find_anchor(url)
|
159
|
+
changelog_page = Nokogiri::HTML(open(url))
|
160
|
+
anchor = changelog_page.css(%(a.anchor)).find do |element|
|
161
|
+
element.attr('href').match(version.delete('.'))
|
141
162
|
end
|
163
|
+
|
164
|
+
anchor.attr('href') if anchor
|
142
165
|
end
|
143
166
|
end
|
144
167
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
* <%= gem %> <%= details[
|
2
|
-
<% if details[
|
3
|
-
[changelog](<%= details[
|
1
|
+
* <%= gem %> <%= details[:versions][:old] %> → <%= details[:versions][:new] %>
|
2
|
+
<% if details[:changelog] %>
|
3
|
+
[changelog](<%= details[:changelog] %>)
|
4
4
|
<% end %>
|
5
5
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gem_updater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxime Demolin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
100
|
version: '0'
|
101
101
|
requirements: []
|
102
102
|
rubyforge_project:
|
103
|
-
rubygems_version: 2.
|
103
|
+
rubygems_version: 2.5.1
|
104
104
|
signing_key:
|
105
105
|
specification_version: 4
|
106
106
|
summary: Update your gems and find their changelogs
|