gem_updater 6.1.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 242ea33b7c63f871adaadd0e90a562c10d069e9409b2da9df55972de156ce734
4
- data.tar.gz: 899cdaba9b9208c33a916f7f27be10aee6347cbe28ec3509ae760bb1975ba4a7
3
+ metadata.gz: dc62f5f20f9d239d4eea066d409fd9900ba139630287a2884fe2497c09ce91e7
4
+ data.tar.gz: 0dc7e3a0446c91dc1a9d24786f381623a162f95218ce1e212d701768f2f99082
5
5
  SHA512:
6
- metadata.gz: 14d744975c7a423f7b467d86e262da0c5431cbaa0b76408a89f479280403d6c81d7d8214df5e6ab3b4251b84ca1b1d266d1228a3b6848149135681778a788230
7
- data.tar.gz: 73e57ed85b57033c36c55a30353a29c5a698444ea45edb9ad1d1db538f629217c5962b1de524a81e4ee3180ddba2c9c28103a55aa7976ba336d5da91c728cb84
6
+ metadata.gz: d7d95be3b9d660ea9af8b2b469e49613305230b6431a4c5c0ea351e49192b035546ff0da342c904062a0518fcc1c1b2327462c4380eed69b9fe189b32fa28b9f
7
+ data.tar.gz: 722d4d3924adb67a00b844c517da2b664685a23a0ebd4964e5bf3e16fc7f4fd69ce20ee79b7c905455bef6a511b1759dc4aa6382391a99fa55b15a0bc2180825
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'open-uri'
5
+
6
+ module GemUpdater
7
+ class ChangelogParser
8
+ # ChangelogParser is responsible for parsing a changelog hosted on github.
9
+ class GithubParser
10
+ attr_reader :uri, :version
11
+
12
+ # @param uri [String] changelog uri
13
+ # @param version [String] version of gem
14
+ def initialize(uri:, version:)
15
+ @uri = uri
16
+ @version = version
17
+ end
18
+
19
+ # Finds anchor in changelog, otherwise return the base uri.
20
+ #
21
+ # @return [String] the URL of changelog
22
+ def changelog
23
+ uri + find_anchor(document).to_s
24
+ end
25
+
26
+ private
27
+
28
+ # Opens changelog url and parses it.
29
+ #
30
+ # @return [Nokogiri::HTML4::Document] the changelog
31
+ def document
32
+ Nokogiri::HTML(URI.parse(uri).open, nil, Encoding::UTF_8.to_s)
33
+ end
34
+
35
+ # Looks into document to find it there is an anchor to new gem version.
36
+ #
37
+ # @param doc [Nokogiri::HTML4::Document] document
38
+ # @return [String, nil] anchor's href
39
+ def find_anchor(doc)
40
+ anchor = doc.xpath('//a[contains(@class, "heading-link")]').find do |element|
41
+ element.attr('href').match(version.delete('.'))
42
+ end
43
+ return unless anchor
44
+
45
+ anchor.attr('href').gsub(%(\\"), '')
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'open-uri'
5
+ require 'gem_updater/changelog_parser/github_parser'
6
+
7
+ module GemUpdater
8
+ # ChangelogParser is responsible for parsing a source page where
9
+ # the gem code is hosted.
10
+ class ChangelogParser
11
+ MARKUP_FILES = %w[.md .rdoc .textile].freeze
12
+
13
+ attr_reader :uri, :version
14
+
15
+ # @param uri [String] uri of changelog
16
+ # @param version [String] version of gem
17
+ def initialize(uri:, version:)
18
+ @uri = uri
19
+ @version = version
20
+ end
21
+
22
+ # Get the changelog in an uri.
23
+ #
24
+ # @return [String, nil] URL of changelog
25
+ def changelog
26
+ return uri unless changelog_may_contain_anchor?
27
+
28
+ parse_changelog
29
+ rescue OpenURI::HTTPError # Uri points to nothing
30
+ log_error_and_return_uri("Cannot find #{uri}")
31
+ rescue Errno::ETIMEDOUT # timeout
32
+ log_error_and_return_uri("#{URI.parse(uri).host} is down")
33
+ rescue ArgumentError => e # x-oauth-basic raises userinfo not supported. [RFC3986]
34
+ log_error_and_return_uri(e)
35
+ end
36
+
37
+ private
38
+
39
+ # Try to find where changelog might be.
40
+ #
41
+ # @param doc [Nokogiri::XML::Element] document of source page
42
+ def parse_changelog
43
+ case URI.parse(uri).host
44
+ when 'github.com'
45
+ GithubParser.new(uri: uri, version: version).changelog
46
+ else
47
+ uri
48
+ end
49
+ end
50
+
51
+ # Some documents like the one written in markdown may contain
52
+ # a direct anchor to specific version.
53
+ #
54
+ # @return [Boolean] true if file may contain an anchor
55
+ def changelog_may_contain_anchor?
56
+ MARKUP_FILES.include?(File.extname(uri.to_s))
57
+ end
58
+
59
+ def log_error_and_return_uri(error_message)
60
+ Bundler.ui.error error_message
61
+ uri
62
+ end
63
+ end
64
+ end
@@ -4,8 +4,8 @@ require 'bundler'
4
4
  require 'bundler/cli'
5
5
 
6
6
  module GemUpdater
7
- # GemFile is responsible for handling `Gemfile`
8
- class GemFile
7
+ # Gemfile is responsible for handling `Gemfile`
8
+ class Gemfile
9
9
  attr_accessor :changes
10
10
  attr_reader :old_spec_set, :new_spec_set
11
11
 
@@ -8,23 +8,22 @@ module GemUpdater
8
8
  # RubyGemsFetcher is a wrapper around rubygems API.
9
9
  class RubyGemsFetcher
10
10
  HTTP_TOO_MANY_REQUESTS = '429'
11
- GEM_HOMEPAGES = %w[source_code_uri homepage_uri].freeze
12
11
 
13
- attr_reader :gem_name, :source
12
+ attr_reader :gem_name
14
13
 
15
14
  # @param gem_name [String] name of the gem
16
- # @param source [Bundler::Source] source of gem
17
- def initialize(gem_name, source)
15
+ def initialize(gem_name)
18
16
  @gem_name = gem_name
19
- @source = source
20
17
  end
21
18
 
22
- # Finds where code is hosted.
23
- # Most likely in will be on rubygems, else look in other sources.
19
+ # Finds the changelog uri.
20
+ # It asks rubygems.org for changelog_uri of gem.
21
+ # See API: http://guides.rubygems.org/rubygems-org-api/#gem-methods
24
22
  #
25
- # @return [String|nil] url of gem source code
26
- def source_uri
27
- uri_from_rubygems || uri_from_other_sources
23
+ # @return [String|nil] uri of changelog
24
+ def changelog_uri
25
+ response = query_rubygems
26
+ response.to_h['changelog_uri']
28
27
  end
29
28
 
30
29
  private
@@ -37,19 +36,6 @@ module GemUpdater
37
36
  JSON.parse(URI.parse(url).open.read)
38
37
  end
39
38
 
40
- # Ask rubygems.org for source uri of gem.
41
- # See API: http://guides.rubygems.org/rubygems-org-api/#gem-methods
42
- #
43
- # @return [String|nil] uri of source code
44
- def uri_from_rubygems
45
- return unless source.remotes.map(&:host).include?('rubygems.org')
46
-
47
- response = query_rubygems
48
- return unless response
49
-
50
- response[GEM_HOMEPAGES.find { |key| response[key] && !response[key].empty? }]
51
- end
52
-
53
39
  # Make the real query to rubygems
54
40
  # It may fail in case we trigger too many requests
55
41
  #
@@ -63,43 +49,5 @@ module GemUpdater
63
49
  sleep 1 && retry if tries < 2
64
50
  end
65
51
  end
66
-
67
- # Look if gem can be found in another remote
68
- #
69
- # @return [String|nil] uri of source code
70
- def uri_from_other_sources
71
- source.remotes.find do |remote|
72
- case remote.host
73
- when 'rubygems.org' then next # already checked
74
- when 'rails-assets.org'
75
- return uri_from_railsassets
76
- else
77
- Bundler.ui.error "Source #{remote} is not supported, ' \
78
- 'feel free to open a PR or an issue on https://github.com/MaximeD/gem_updater"
79
- end
80
- end
81
- end
82
-
83
- # Ask rails-assets.org for source uri of gem.
84
- # API is at : https://rails-assets.org/packages/package_name
85
- #
86
- # @return [String|nil] uri of source code
87
- def uri_from_railsassets
88
- response = query_railsassets
89
- return unless response
90
-
91
- response['url'].gsub(/^git/, 'http')
92
- end
93
-
94
- # Make the real query to railsassets
95
- # rubocop:disable Lint/SuppressedException
96
- def query_railsassets
97
- parse_remote_json("https://rails-assets.org/packages/#{gem_name.gsub('rails-assets-', '')}")
98
- rescue JSON::ParserError
99
- # if gem is not found, rails-assets returns a 200
100
- # with html (instead of json) containing a 500...
101
- rescue OpenURI::HTTPError
102
- end
103
- # rubocop:enable Lint/SuppressedException
104
52
  end
105
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GemUpdater
4
- VERSION = '6.1.0'
4
+ VERSION = '7.0.0'
5
5
  end
data/lib/gem_updater.rb CHANGED
@@ -1,22 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'memoist'
5
- require 'gem_updater/gem_file'
4
+ require 'gem_updater/changelog_parser'
5
+ require 'gem_updater/gemfile'
6
6
  require 'gem_updater/ruby_gems_fetcher'
7
- require 'gem_updater/source_page_parser'
8
7
 
9
8
  # Base lib.
10
9
  module GemUpdater
11
10
  # Updater's main responsability is to fill changes
12
11
  # happened before and after update of `Gemfile`, and then format them.
13
12
  class Updater
14
- extend Memoist
13
+ RUBYGEMS_SOURCE_NAME = 'rubygems repository https://rubygems.org/'
15
14
 
16
15
  attr_accessor :gemfile
17
16
 
18
17
  def initialize
19
- @gemfile = GemUpdater::GemFile.new
18
+ @gemfile = GemUpdater::Gemfile.new
20
19
  end
21
20
 
22
21
  # Update process.
@@ -52,35 +51,23 @@ module GemUpdater
52
51
  # For each gem, retrieve its changelog
53
52
  def fill_changelogs
54
53
  [].tap do |threads|
55
- gemfile.changes.each do |gem_name, details|
56
- threads << Thread.new { retrieve_gem_changes(gem_name, details) }
54
+ gemfile.changes.each do |gem_changes, details|
55
+ threads << Thread.new { retrieve_changelog(gem_changes, details) }
57
56
  end
58
57
  end.each(&:join)
59
58
  end
60
59
 
61
- # Find where is hosted the source of a gem
62
- #
63
- # @param gem [String] the name of the gem
64
- # @param source [Bundler::Source] gem's source
65
- # @return [String] url where gem is hosted
66
- def find_source(gem, source)
67
- case source
68
- when Bundler::Source::Rubygems
69
- GemUpdater::RubyGemsFetcher.new(gem, source).source_uri
70
- when Bundler::Source::Git
71
- source.uri.gsub(/^git/, 'http').chomp('.git')
72
- end
73
- end
60
+ # Get the changelog URL.
61
+ def retrieve_changelog(gem_name, details)
62
+ return unless details[:source].name == RUBYGEMS_SOURCE_NAME
74
63
 
75
- def retrieve_gem_changes(gem_name, details)
76
- source_uri = find_source(gem_name, details[:source])
77
- return unless source_uri
64
+ changelog_uri = RubyGemsFetcher.new(gem_name).changelog_uri
78
65
 
79
- source_page = GemUpdater::SourcePageParser.new(
80
- url: source_uri, version: details[:versions][:new]
81
- )
66
+ return unless changelog_uri
82
67
 
83
- gemfile.changes[gem_name][:changelog] = source_page.changelog if source_page.changelog
68
+ changelog = ChangelogParser
69
+ .new(uri: changelog_uri, version: details.dig(:versions, :new)).changelog
70
+ gemfile.changes[gem_name][:changelog] = changelog&.to_s
84
71
  end
85
72
 
86
73
  # Get the template for gem's diff.
@@ -92,6 +79,5 @@ module GemUpdater
92
79
  rescue Errno::ENOENT
93
80
  File.read(File.expand_path('../lib/gem_updater_template.erb', __dir__))
94
81
  end
95
- memoize :template
96
82
  end
97
83
  end
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: 6.1.0
4
+ version: 7.0.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: 2023-09-15 00:00:00.000000000 Z
11
+ date: 2023-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.6'
41
- - !ruby/object:Gem::Dependency
42
- name: memoist
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.16.2
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.16.2
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: nokogiri
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -160,9 +146,10 @@ extra_rdoc_files: []
160
146
  files:
161
147
  - bin/gem_update
162
148
  - lib/gem_updater.rb
163
- - lib/gem_updater/gem_file.rb
149
+ - lib/gem_updater/changelog_parser.rb
150
+ - lib/gem_updater/changelog_parser/github_parser.rb
151
+ - lib/gem_updater/gemfile.rb
164
152
  - lib/gem_updater/ruby_gems_fetcher.rb
165
- - lib/gem_updater/source_page_parser.rb
166
153
  - lib/gem_updater/version.rb
167
154
  - lib/gem_updater_template.erb
168
155
  homepage: https://github.com/MaximeD/gem_updater
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'nokogiri'
4
- require 'open-uri'
5
-
6
- module GemUpdater
7
- # SourcePageParser is responsible for parsing a source page where
8
- # the gem code is hosted.
9
- class SourcePageParser
10
- extend Memoist
11
-
12
- HOSTS = {
13
- github: /github.com/,
14
- bitbucket: /bitbucket.org/,
15
- rubygems: /rubygems.org/
16
- }.freeze
17
- MARKUP_FILES = %w[.md .rdoc .textile].freeze
18
- CHANGELOG_NAMES = %w[changelog ChangeLog history changes news].freeze
19
-
20
- attr_reader :uri, :version
21
-
22
- # @param url [String] url of page
23
- # @param version [String] version of gem
24
- def initialize(url: nil, version: nil)
25
- @uri = correct_uri(url)
26
- @version = version
27
- end
28
-
29
- # Get the changelog in an uri.
30
- #
31
- # @return [String, nil] URL of changelog
32
- def changelog
33
- return unless uri
34
-
35
- Bundler.ui.warn "Looking for a changelog in #{uri}"
36
- find_changelog(Nokogiri::HTML(uri.open))
37
- rescue OpenURI::HTTPError # Uri points to nothing
38
- log_error("Cannot find #{uri}")
39
- rescue Errno::ETIMEDOUT # timeout
40
- log_error("#{uri} is down")
41
- rescue ArgumentError => e # x-oauth-basic raises userinfo not supported. [RFC3986]
42
- log_error(e)
43
- end
44
- memoize :changelog
45
-
46
- private
47
-
48
- # Some gems have 'http://github.com' as URI which will redirect to https
49
- # leading `open_uri` to crash.
50
- #
51
- # @param url [String] the url to parse
52
- # @return [URI] valid URI
53
- def correct_uri(url)
54
- return unless url.is_a?(String) && !url.empty?
55
-
56
- uri = URI.parse(url)
57
- uri.scheme == 'http' ? known_https(uri) : uri
58
- end
59
-
60
- # Some uris are not https, but we know they should be,
61
- # in which case we have an https redirection
62
- # which is not properly handled by open-uri
63
- #
64
- # @param uri [URI::HTTP]
65
- # @return [URI::HTTPS|URI::HTTP]
66
- def known_https(uri)
67
- case uri.host
68
- when HOSTS[:github]
69
- # remove possible subdomain like 'wiki.github.com'
70
- URI "https://github.com#{uri.path}"
71
- when HOSTS[:bitbucket], HOSTS[:rubygems]
72
- URI "https://#{uri.host}#{uri.path}"
73
- else
74
- uri
75
- end
76
- end
77
-
78
- # Try to find where changelog might be.
79
- #
80
- # @param doc [Nokogiri::XML::Element] document of source page
81
- def find_changelog(doc)
82
- case uri.host
83
- when 'github.com'
84
- GitHubParser.new(doc, version).changelog
85
- end
86
- end
87
-
88
- # List possible names for a changelog
89
- # since humans may have many many ways to call it.
90
- #
91
- # @return [Array] list of possible names
92
- def changelog_names
93
- CHANGELOG_NAMES.flat_map do |name|
94
- [name, name.upcase, name.capitalize]
95
- end.uniq
96
- end
97
-
98
- # Some documents like the one written in markdown may contain
99
- # a direct anchor to specific version.
100
- #
101
- # @param file_name [String] file name of changelog
102
- # @return [Boolean] true if file may contain an anchor
103
- def changelog_may_contain_anchor?(file_name)
104
- MARKUP_FILES.include?(File.extname(file_name))
105
- end
106
-
107
- def log_error(error_message)
108
- Bundler.ui.error error_message
109
- false
110
- end
111
-
112
- # GitHubParser is responsible for parsing source code
113
- # hosted on github.com.
114
- class GitHubParser < SourcePageParser
115
- BASE_URL = 'https://github.com'
116
-
117
- attr_reader :doc, :version
118
-
119
- # @param doc [Nokogiri::XML::Element] document of source page
120
- # @param version [String] version of gem
121
- # rubocop:disable Lint/MissingSuper
122
- def initialize(doc, version)
123
- @doc = doc
124
- @version = version
125
- end
126
- # rubocop:enable Lint/MissingSuper
127
-
128
- # Finds url of changelog.
129
- #
130
- # @return [String] the URL of changelog
131
- def changelog
132
- url = find_changelog_link
133
- return unless url
134
-
135
- full_url = BASE_URL + url
136
-
137
- if changelog_may_contain_anchor?(full_url)
138
- anchor = find_anchor(full_url)
139
- full_url += anchor if anchor
140
- end
141
-
142
- full_url
143
- end
144
-
145
- private
146
-
147
- # Find which link corresponds to changelog.
148
- # Last one is taken as files come after directories. So if there is a directory plus a file
149
- # matching a changelog name, we ensure file is preferred.
150
- #
151
- # @return [String, nil] url of changelog
152
- def find_changelog_link
153
- changelog_names.filter_map do |name|
154
- doc.at_css(%([aria-labelledby="files"] a[title^="#{name}"]))
155
- end.last&.attr('href')
156
- end
157
-
158
- # Looks into document to find it there is an anchor to new gem version.
159
- #
160
- # @param url [String] url of changelog
161
- # @return [String, nil] anchor's href
162
- def find_anchor(url)
163
- changelog_page = Nokogiri::HTML(URI.parse(url).open)
164
- anchor = changelog_page.xpath('//a[contains(@class, "anchor")]').find do |element|
165
- element.attr('href').match(version.delete('.'))
166
- end
167
- return unless anchor
168
-
169
- anchor.attr('href').gsub(%(\\"), '')
170
- end
171
- end
172
- end
173
- end