bundler-alive 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,113 +10,118 @@ module Bundler
10
10
  # Diagnoses a `Gemfile.lock` with a TOML file
11
11
  #
12
12
  class Doctor
13
- attr_reader :all_alive, :rate_limit_exceeded_error
14
-
15
13
  #
16
14
  # A new instance of Doctor
17
15
  #
18
16
  # @param [String] lock_file lock file of gem
19
17
  # @param [String] result_file file of result
20
18
  #
21
- def initialize(lock_file, result_file = "result.toml")
19
+ def initialize(lock_file, result_file)
22
20
  @lock_file = lock_file
23
21
  @result_file = result_file
24
- @gem_client = Client::GemsApi.new
22
+ @gem_client = Client::GemsApiClient.new
25
23
  @result = nil
26
- @all_alive = nil
27
- @rate_limit_exceeded_error = false
24
+ @rate_limit_exceeded = false
25
+ @announcer = Announcer.new
26
+ @error_messages = []
28
27
  end
29
28
 
30
29
  #
31
30
  # Diagnoses gems in lock file of gem
32
31
  #
32
+ # @raise [Client::SourceCodeClient::RateLimitExceededError]
33
+ # When exceeded access rate limit
34
+ #
35
+ # @raise [StandardError]
36
+ # When raised unexpected error
37
+ #
38
+ # @return [Report]
39
+ #
33
40
  def diagnose
34
- @result = gems.each_with_object(GemStatusCollection.new) do |spec, collection|
35
- gem_name = spec.name
36
- gem_status = diagnose_gem(gem_name)
37
-
38
- collection.add(gem_name, gem_status)
39
- end
41
+ $stdout.puts "#{collection_from_gemfile.total_size} gems are in Gemfile.lock"
42
+ result = _diagnose
43
+ Report.new(result)
40
44
  end
41
45
 
46
+ private
47
+
48
+ attr_reader :lock_file, :result_file, :gem_client, :announcer,
49
+ :result, :error_messages, :rate_limit_exceeded
50
+
42
51
  #
43
- # Reports the result
52
+ # @return [Array<String>]
44
53
  #
45
- def report
46
- need_to_report_gems = result.need_to_report_gems
47
- @all_alive = need_to_report_gems.size.zero?
48
- need_to_report_gems.each do |_name, gem_status|
49
- print gem_status.report
54
+ def no_need_to_get_gems
55
+ return [] unless File.exist?(result_file)
56
+
57
+ toml_hash = TomlRB.load_file(result_file)
58
+ toml_hash.each_with_object([]) do |(gem_name, v), array|
59
+ alive = v["alive"]
60
+ array << gem_name unless alive
50
61
  end
51
62
  end
52
63
 
53
- #
54
- # Saves result to file
55
- #
56
- def save_as_file
57
- body = TomlRB.dump(result.to_h)
58
- File.write(result_file, body)
64
+ def diagnose_by_service(service, urls)
65
+ client = Client::SourceCodeClient.new(service_name: service)
66
+ client.query(urls: urls) do
67
+ announcer.announce
68
+ end
59
69
  end
60
70
 
61
- private
71
+ def result_by_search(collection)
72
+ gems_api_response = gem_client.gems_api_response(collection.names) do
73
+ announcer.announce
74
+ end
62
75
 
63
- attr_reader :lock_file, :result_file, :gem_client, :result
76
+ service_with_urls = gems_api_response.service_with_urls
77
+ error_messages.concat(gems_api_response.error_messages)
64
78
 
65
- def diagnosed_gem?(gem_status)
66
- !gem_status.nil? && !gem_status.unknown?
79
+ result = StatusResult.new
80
+ service_with_urls.each do |service, urls|
81
+ result = result.merge(diagnose_by_service(service, urls))
82
+ end
83
+ result
67
84
  end
68
85
 
69
- def collection_from_file
70
- return @collection_from_file if instance_variable_defined?(:@collection_from_file)
71
-
72
- return GemStatusCollection.new unless File.exist?(result_file)
86
+ def fetch_target_collection(base_collection, gem_names)
87
+ collection = StatusCollection.new
88
+ base_collection.each do |name, status|
89
+ next if gem_names.include?(name)
73
90
 
74
- toml_hash = TomlRB.load_file(result_file)
75
- @collection_from_file = collection_from_hash(toml_hash)
91
+ collection = collection.add(name, status)
92
+ end
93
+ collection
76
94
  end
77
95
 
78
- def collection_from_hash(hash)
79
- hash.each_with_object(GemStatusCollection.new) do |(gem_name, v), collection|
80
- url = v["repository_url"]
81
- next if url.to_sym == GemStatus::REPOSITORY_URL_UNKNOWN
82
-
83
- gem_status = GemStatus.new(name: gem_name,
84
- repository_url: SourceCodeRepositoryUrl.new(url),
85
- alive: v["alive"],
86
- checked_at: v["checked_at"])
87
- collection.add(gem_name, gem_status)
96
+ def collection_from_gemfile
97
+ gems_from_lockfile.each_with_object(StatusCollection.new) do |gem, collection|
98
+ gem_name = gem.name
99
+ status = Status.new(name: gem_name,
100
+ repository_url: nil,
101
+ alive: nil,
102
+ checked_at: nil)
103
+ collection.add(gem_name, status)
88
104
  end
89
105
  end
90
106
 
91
- def gems
92
- lock_file_body = File.read(@lock_file)
93
- lock_file = Bundler::LockfileParser.new(lock_file_body)
94
- lock_file.specs.each
107
+ def _diagnose
108
+ collection = fetch_target_collection(collection_from_gemfile, no_need_to_get_gems)
109
+ result = result_by_search(collection)
110
+ collection_from_toml_file = StatusCollection.new_from_toml_file(result_file)
111
+ new_collection = collection_from_gemfile.merge(collection_from_toml_file)
112
+ .merge(result.collection)
113
+
114
+ messages = error_messages.concat(result.error_messages)
115
+ StatusResult.new(collection: new_collection,
116
+ error_messages: messages,
117
+ rate_limit_exceeded: result.rate_limit_exceeded)
95
118
  end
96
119
 
97
- # rubocop:disable Metrics/MethodLength
98
- def diagnose_gem(gem_name)
99
- gem_status = collection_from_file.get_unchecked(gem_name)
100
- return gem_status if diagnosed_gem?(gem_status)
101
-
102
- unless @rate_limit_exceeded_error
103
- begin
104
- source_code_url = gem_client.get_repository_url(gem_name)
105
- is_alive = SourceCodeRepository.new(url: source_code_url).alive?
106
- rescue Client::SourceCodeClient::RateLimitExceededError => e
107
- @rate_limit_exceeded_error = true
108
- puts e.message
109
- rescue StandardError => e
110
- puts e.message
111
- end
112
- end
113
-
114
- GemStatus.new(name: gem_name,
115
- repository_url: source_code_url,
116
- alive: is_alive,
117
- checked_at: Time.now)
120
+ def gems_from_lockfile
121
+ lock_file_body = File.read(@lock_file)
122
+ lock_file = Bundler::LockfileParser.new(lock_file_body)
123
+ lock_file.specs
118
124
  end
119
- # rubocop:enable Metrics/MethodLength
120
125
  end
121
126
  end
122
127
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Alive
5
+ #
6
+ # Represents Report
7
+ #
8
+ class Report
9
+ attr_reader :result, :error_messages, :rate_limit_exceeded
10
+
11
+ #
12
+ # A result of report
13
+ #
14
+ # @param [StatusResult] result
15
+ #
16
+ def initialize(result)
17
+ @result = result.collection
18
+ @error_messages = result.error_messages
19
+ @rate_limit_exceeded = result.rate_limit_exceeded
20
+
21
+ freeze
22
+ end
23
+
24
+ #
25
+ # Save result to file
26
+ #
27
+ # @param [String] file_path
28
+ #
29
+ def save_as_file(file_path)
30
+ body = TomlRB.dump(result.to_h)
31
+ File.write(file_path, body)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+
5
+ require "thor"
6
+
7
+ module Bundler
8
+ module Alive
9
+ class CLI < ::Thor
10
+ #
11
+ # Reports
12
+ #
13
+ module Reportable
14
+ #
15
+ # Reports result
16
+ #
17
+ # @param [Report] report
18
+ #
19
+ def print_report(report)
20
+ result = report.result
21
+ error_messages = report.error_messages
22
+ print_error(error_messages)
23
+
24
+ gems = result.need_to_report_gems
25
+ $stdout.puts if gems.size.positive?
26
+ gems.each do |_name, gem|
27
+ $stdout.puts gem.report
28
+ end
29
+
30
+ print_summary(result)
31
+ print_message(result, report.rate_limit_exceeded)
32
+ end
33
+
34
+ private
35
+
36
+ def print_error(error_messages)
37
+ return if error_messages.nil?
38
+
39
+ $stdout.puts <<~ERROR
40
+
41
+ #{error_messages.join("\n")}
42
+ ERROR
43
+ end
44
+
45
+ def print_summary(result)
46
+ $stdout.puts <<~RESULT
47
+ Total: #{result.total_size} (Dead: #{result.dead_size}, Alive: #{result.alive_size}, Unknown: #{result.unknown_size})
48
+ RESULT
49
+ end
50
+
51
+ def print_message(result, rate_limit_exceeded)
52
+ if result.all_alive?
53
+ say "All gems are alive!", :green
54
+ return
55
+ end
56
+
57
+ say "Too many requested! Retry later.", :yellow if rate_limit_exceeded
58
+ if result.dead_size.positive?
59
+ say "Not alive gems are found!", :red
60
+ return
61
+ end
62
+ say "Unknown gems are found!", :yellow if result.unknown_size.positive?
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -17,21 +17,11 @@ module Bundler
17
17
  raise ArgumentError, "Unknown url: #{url}" unless url.instance_of?(SourceCodeRepositoryUrl)
18
18
 
19
19
  @url = url
20
- @client = Client::SourceCodeClient.new(service_name: url.service_name)
21
- end
22
-
23
- #
24
- # Returns alive or not
25
- #
26
- # @return [Boolean]
27
- #
28
- def alive?
29
- !client.archived?(url)
30
20
  end
31
21
 
32
22
  private
33
23
 
34
- attr_reader :url, :client
24
+ attr_reader :url
35
25
  end
36
26
  end
37
27
  end
@@ -4,41 +4,74 @@ module Bundler
4
4
  module Alive
5
5
  # Represents a source code repository
6
6
  class SourceCodeRepositoryUrl
7
+ # service domain with service
7
8
  DOMAIN_WITH_SERVICES = {
8
- "github.com" => SourceCodeRepository::Service::GITHUB
9
+ "github.com" => SourceCodeRepository::Service::GITHUB,
10
+ "www.github.com" => SourceCodeRepository::Service::GITHUB
9
11
  }.freeze
10
12
 
11
13
  private_constant :DOMAIN_WITH_SERVICES
12
14
 
15
+ #
16
+ # Judge supported url or not
17
+ #
18
+ # @param [String] url
19
+ #
20
+ # @return [Boolean]
21
+ #
22
+ def self.support_url?(url)
23
+ return false if url.nil?
24
+
25
+ uri = URI.parse(url)
26
+ host = uri.host
27
+
28
+ DOMAIN_WITH_SERVICES.key?(host)
29
+ end
30
+
13
31
  # No supported URL Error
14
32
  class UnSupportedUrl < StandardError
15
- def initialize(url)
16
- message = "UnSupported URL: #{url}"
33
+ #
34
+ # @param [String] :url
35
+ # @param [String] :name
36
+ #
37
+ # @return [UnSupportedUrl]
38
+ #
39
+ def initialize(url:, name:)
40
+ decorated_url = if url.nil? || url == ""
41
+ "(blank)"
42
+ else
43
+ url
44
+ end
45
+ message = "[#{name}] is not support URL: #{decorated_url}"
17
46
  super(message)
18
47
  end
19
48
  end
20
49
 
21
- attr_reader :url, :service_name
50
+ attr_reader :url, :service_name, :gem_name
22
51
 
23
52
  #
24
53
  # Creates a `SourceCodeRepositoryUrl`
25
54
  #
26
55
  # @param [String] url
56
+ # @param [String] name
27
57
  #
28
58
  # @raise [UnSupportedUrl]
29
59
  #
30
- def initialize(url)
60
+ def initialize(url, name)
61
+ raise UnSupportedUrl.new(url: url, name: name) if url.nil?
62
+
31
63
  @url = url
32
- @service_name = service(url)
64
+ @service_name = service(url: url, name: name)
65
+ @gem_name = name
33
66
  end
34
67
 
35
68
  private
36
69
 
37
- def service(url)
70
+ def service(url:, name:)
38
71
  uri = URI.parse(url)
39
72
  host = uri.host
40
73
 
41
- raise UnSupportedUrl, url unless DOMAIN_WITH_SERVICES.key?(host)
74
+ raise UnSupportedUrl.new(url: url, name: name) unless DOMAIN_WITH_SERVICES.key?(host)
42
75
 
43
76
  DOMAIN_WITH_SERVICES[host]
44
77
  end
@@ -3,15 +3,30 @@
3
3
  module Bundler
4
4
  module Alive
5
5
  #
6
- # Result of gem status
6
+ # Represents Status
7
7
  #
8
- class GemStatus
9
- REPOSITORY_URL_UNKNOWN = :unknown
10
- ALIVE_UNKNOWN = :unknown
8
+ class Status
9
+ # Value of repository URL unknown
10
+ REPOSITORY_URL_UNKNOWN = "unknown"
11
+
12
+ # Value off alive unknown
13
+ ALIVE_UNKNOWN = "unknown"
11
14
 
12
15
  attr_reader :name, :repository_url, :alive, :checked_at
13
16
 
17
+ #
18
+ # Creates instance of `Status`
19
+ #
20
+ # @param [String] :name
21
+ # @param [SourceCodeRepositoryUrl] :repository_url
22
+ # @param [Boolean] :alive
23
+ # @param [] :checked_at
24
+ #
25
+ # @return [Status]
26
+ #
14
27
  def initialize(name:, repository_url:, alive:, checked_at:)
28
+ raise ArgumentError if !repository_url.nil? && !repository_url.instance_of?(SourceCodeRepositoryUrl)
29
+
15
30
  repository_url = REPOSITORY_URL_UNKNOWN if repository_url.nil?
16
31
  alive = ALIVE_UNKNOWN if alive.nil?
17
32
 
@@ -24,7 +39,7 @@ module Bundler
24
39
  end
25
40
 
26
41
  #
27
- # Alive?
42
+ # Is status of alive unknown?
28
43
  #
29
44
  # @return [Boolean]
30
45
  #
@@ -39,7 +54,7 @@ module Bundler
39
54
  {
40
55
  repository_url: decorated_repository_url,
41
56
  alive: decorated_alive,
42
- checked_at: checked_at
57
+ checked_at: checked_at || ""
43
58
  }
44
59
  end
45
60
 
@@ -61,7 +76,7 @@ module Bundler
61
76
 
62
77
  def decorated_repository_url
63
78
  if repository_url == REPOSITORY_URL_UNKNOWN
64
- REPOSITORY_URL_UNKNOWN.to_s
79
+ REPOSITORY_URL_UNKNOWN
65
80
  else
66
81
  repository_url.url
67
82
  end
@@ -69,7 +84,7 @@ module Bundler
69
84
 
70
85
  def decorated_alive
71
86
  if alive == ALIVE_UNKNOWN
72
- ALIVE_UNKNOWN.to_s
87
+ ALIVE_UNKNOWN
73
88
  else
74
89
  alive
75
90
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Bundler
6
+ module Alive
7
+ # Collection of `Status`
8
+ class StatusCollection
9
+ extend Forwardable
10
+ delegate each: :collection
11
+ delegate values: :collection
12
+
13
+ attr_reader :alive_size, :dead_size, :unknown_size
14
+
15
+ #
16
+ # Creates `StatusCollection` from TOML file
17
+ #
18
+ # @param [String] path
19
+ #
20
+ # @return [StatusCollection]
21
+ #
22
+ def self.new_from_toml_file(path)
23
+ return new unless File.exist?(path)
24
+
25
+ collection = new
26
+ TomlRB.load_file(path).each do |name, v|
27
+ next if v["alive"] == Status::ALIVE_UNKNOWN
28
+
29
+ url = SourceCodeRepositoryUrl.new(v["repository_url"], name)
30
+ status = Status.new(name: name, repository_url: url,
31
+ alive: v["alive"], checked_at: v["checked_at"])
32
+ collection = collection.add(name, status)
33
+ end
34
+
35
+ collection
36
+ end
37
+
38
+ #
39
+ # Generates instance of `StatusCollection`
40
+ #
41
+ # @param [StatusCollection|nil] collection
42
+ #
43
+ # @return [StatusCollection]
44
+ #
45
+ def initialize(collection = {})
46
+ @collection = collection
47
+ @statuses_values = collection.values || []
48
+
49
+ @alive_size = _alive_size
50
+ @unknown_size = _unknown_size
51
+ @dead_size = _dead_size
52
+ freeze
53
+ end
54
+
55
+ #
56
+ # Fetch `Status` of `name`
57
+ #
58
+ # @param [String] name
59
+ #
60
+ # @return [Status]
61
+ #
62
+ def [](name)
63
+ collection[name]
64
+ end
65
+
66
+ #
67
+ # Names of gems
68
+ #
69
+ # @return [Array<String>]
70
+ #
71
+ def names
72
+ values.map(&:name)
73
+ end
74
+
75
+ #
76
+ # Add status
77
+ #
78
+ # @param [String] name
79
+ # @param [Status] status
80
+ #
81
+ # @return [StatusCollection]
82
+ #
83
+ def add(name, status)
84
+ collection[name] = status
85
+
86
+ self.class.new(collection)
87
+ end
88
+
89
+ #
90
+ # Merge collection
91
+ #
92
+ # @param [StatusCollection] collection
93
+ #
94
+ # @return [StatusCollection]
95
+ #
96
+ def merge(collection)
97
+ return self.class.new(self.collection) if collection.nil?
98
+
99
+ collection.each do |k, v|
100
+ self.collection[k] = v
101
+ end
102
+
103
+ self.class.new(self.collection)
104
+ end
105
+
106
+ def to_h
107
+ collection.transform_values(&:to_h)
108
+ end
109
+
110
+ def need_to_report_gems
111
+ collection.find_all { |_name, gem| !!!gem.alive }
112
+ end
113
+
114
+ #
115
+ # All of statuses are alive nor not
116
+ #
117
+ # @return [Boolean]
118
+ #
119
+ def all_alive?
120
+ collection.find { |_name, status| !!!status.alive || status.unknown? }.nil?
121
+ end
122
+
123
+ #
124
+ # Total size of collection
125
+ #
126
+ # @return [Integer]
127
+ #
128
+ def total_size
129
+ collection.size
130
+ end
131
+
132
+ private
133
+
134
+ attr_reader :collection, :statuses_values
135
+
136
+ def _alive_size
137
+ statuses_values.count { |gem| !!gem.alive && !gem.unknown? }
138
+ end
139
+
140
+ def _dead_size
141
+ statuses_values.count { |gem| !gem.alive && !gem.unknown? }
142
+ end
143
+
144
+ def _unknown_size
145
+ statuses_values.count { |gem| gem.alive == Status::ALIVE_UNKNOWN }
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Alive
5
+ #
6
+ # Represents Result of Status
7
+ #
8
+ class StatusResult
9
+ attr_reader :collection, :error_messages, :rate_limit_exceeded
10
+
11
+ #
12
+ # Creates a new StatusResult instance
13
+ #
14
+ # @param [StatusCollection|nil] :collection
15
+ # @param [Array|nil] :error_messages
16
+ # @param [Boolean|nil] :rate_limit_exceeded
17
+ #
18
+ # @return [StatusResult]
19
+ #
20
+ def initialize(collection: nil, error_messages: nil, rate_limit_exceeded: nil)
21
+ @collection = collection
22
+ @error_messages = error_messages
23
+ @rate_limit_exceeded = rate_limit_exceeded
24
+ end
25
+
26
+ #
27
+ # Merge `StatusResult`, then returns new one
28
+ #
29
+ # @param [StatusResult] result
30
+ #
31
+ # @return [StatusResult]
32
+ #
33
+ def merge(result)
34
+ self.class.new(collection: result.collection,
35
+ error_messages: result.error_messages,
36
+ rate_limit_exceeded: result.rate_limit_exceeded)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bundler
4
4
  module Alive
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
data/lib/bundler/alive.rb CHANGED
@@ -4,8 +4,13 @@ require_relative "alive/version"
4
4
  require_relative "alive/doctor"
5
5
  require_relative "alive/source_code_repository"
6
6
  require_relative "alive/source_code_repository_url"
7
- require_relative "alive/gem_status"
8
- require_relative "alive/gem_status_collection"
9
- require_relative "alive/client/gems_api"
7
+ require_relative "alive/status"
8
+ require_relative "alive/status_result"
9
+ require_relative "alive/status_collection"
10
+ require_relative "alive/announcer"
11
+ require_relative "alive/report"
12
+ require_relative "alive/client/gems_api_client"
13
+ require_relative "alive/client/gems_api_response"
10
14
  require_relative "alive/client/git_hub_api"
11
15
  require_relative "alive/client/source_code_client"
16
+ require_relative "alive/reportable"