bundler-alive 0.1.2 → 0.1.5

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.
@@ -7,9 +7,11 @@ module Bundler
7
7
  # Represents a source code client
8
8
  #
9
9
  class SourceCodeClient
10
+ # Error of searching repository
10
11
  class SearchRepositoryError < StandardError
11
12
  end
12
13
 
14
+ # Error of rate limit exceeded
13
15
  class RateLimitExceededError < StandardError
14
16
  end
15
17
 
@@ -31,10 +33,11 @@ module Bundler
31
33
  def initialize(service_name:)
32
34
  raise ArgumentError, "Unknown service: #{service_name}" unless SERVICE_WITH_STRATEGIES.key?(service_name)
33
35
 
34
- service = SERVICE_WITH_STRATEGIES[service_name]
35
- extend service
36
+ strategy = SERVICE_WITH_STRATEGIES[service_name]
37
+ extend strategy
36
38
 
37
39
  @client = create_client
40
+ @error_messages = []
38
41
 
39
42
  super()
40
43
  end
@@ -2,121 +2,103 @@
2
2
 
3
3
  require "bundler"
4
4
  require "octokit"
5
- require "toml-rb"
6
5
 
7
6
  module Bundler
8
7
  module Alive
9
8
  #
10
- # Diagnoses a `Gemfile.lock` with a TOML file
9
+ # Diagnoses a `Gemfile.lock`
11
10
  #
12
11
  class Doctor
13
- attr_reader :all_alive, :rate_limit_exceeded_error
14
-
15
12
  #
16
13
  # A new instance of Doctor
17
14
  #
18
15
  # @param [String] lock_file lock file of gem
19
- # @param [String] result_file file of result
16
+ # @param [String] config_file config file
17
+ # @param [Array<String>] ignore_gems ignore gems
20
18
  #
21
- def initialize(lock_file, result_file = "result.toml")
19
+ def initialize(lock_file, config_file, ignore_gems)
22
20
  @lock_file = lock_file
23
- @result_file = result_file
24
- @gem_client = Client::GemsApi.new
21
+ @gem_client = Client::GemsApiClient.new(config_file)
22
+ @ignore_gems = ignore_gems
25
23
  @result = nil
26
- @all_alive = nil
27
- @rate_limit_exceeded_error = false
24
+ @rate_limit_exceeded = false
25
+ @error_messages = []
28
26
  end
29
27
 
30
28
  #
31
- # Diagnoses gems in lock file of gem
29
+ # Diagnoses gems in Gemfile.lock
32
30
  #
33
- 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
40
- end
41
-
31
+ # @raise [Client::SourceCodeClient::RateLimitExceededError]
32
+ # When exceeded access rate limit
42
33
  #
43
- # Reports the result
34
+ # @raise [StandardError]
35
+ # When raised unexpected error
44
36
  #
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
50
- end
51
- end
52
-
37
+ # @return [Report]
53
38
  #
54
- # Saves result to file
55
- #
56
- def save_as_file
57
- body = TomlRB.dump(result.to_h)
58
- File.write(result_file, body)
39
+ def diagnose
40
+ message = "#{collection_from_gemfile.total_size + ignore_gems.size} gems are in Gemfile.lock"
41
+ message = "#{message} (#{ignore_gems.size} gems are ignored)" if ignore_gems.size.positive?
42
+ $stdout.puts message
43
+
44
+ result = _diagnose
45
+ Report.new(result)
59
46
  end
60
47
 
61
48
  private
62
49
 
63
- attr_reader :lock_file, :result_file, :gem_client, :result
50
+ attr_reader :lock_file, :gem_client, :ignore_gems,
51
+ :result, :error_messages, :rate_limit_exceeded
64
52
 
65
- def diagnosed_gem?(gem_status)
66
- !gem_status.nil? && !gem_status.unknown?
53
+ def diagnose_by_service(service, urls)
54
+ client = Client::SourceCodeClient.new(service_name: service)
55
+ client.query(urls: urls)
67
56
  end
68
57
 
69
- def collection_from_file
70
- return @collection_from_file if instance_variable_defined?(:@collection_from_file)
58
+ # @return [StatusResult]
59
+ def result_by_search(collection)
60
+ gems_api_response = gem_client.gems_api_response(collection.names)
61
+ service_with_urls = gems_api_response.service_with_urls
62
+ error_messages.concat(gems_api_response.error_messages)
71
63
 
72
- return GemStatusCollection.new unless File.exist?(result_file)
64
+ result = StatusResult.new
65
+ service_with_urls.each do |service, urls|
66
+ result = result.merge(diagnose_by_service(service, urls))
67
+ end
68
+ result
69
+ end
73
70
 
74
- toml_hash = TomlRB.load_file(result_file)
75
- @collection_from_file = collection_from_hash(toml_hash)
71
+ # @return [StatusCollection]
72
+ def collection_from_gemfile
73
+ gems_from_lockfile.each_with_object(StatusCollection.new) do |gem, collection|
74
+ gem_name = gem.name
75
+ next if ignore_gems.include?(gem_name)
76
+
77
+ status = Status.new(name: gem_name,
78
+ repository_url: nil,
79
+ alive: nil,
80
+ checked_at: nil)
81
+ collection.add(gem_name, status)
82
+ end
76
83
  end
77
84
 
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
85
+ # @return [StatusResult]
86
+ def _diagnose
87
+ collection = collection_from_gemfile
88
+ result = result_by_search(collection)
89
+ new_collection = collection.merge(result.collection)
90
+ messages = error_messages.concat(result.error_messages)
82
91
 
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)
88
- end
92
+ StatusResult.new(collection: new_collection,
93
+ error_messages: messages,
94
+ rate_limit_exceeded: result.rate_limit_exceeded)
89
95
  end
90
96
 
91
- def gems
97
+ def gems_from_lockfile
92
98
  lock_file_body = File.read(@lock_file)
93
99
  lock_file = Bundler::LockfileParser.new(lock_file_body)
94
- lock_file.specs.each
95
- end
96
-
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)
100
+ lock_file.specs
118
101
  end
119
- # rubocop:enable Metrics/MethodLength
120
102
  end
121
103
  end
122
104
  end
@@ -0,0 +1,25 @@
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
+ end
24
+ end
25
+ end
@@ -0,0 +1,79 @@
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.archived_gems
25
+ print_archived_gems(gems) if gems.size.positive?
26
+
27
+ print_summary(result)
28
+ print_message(result, report.rate_limit_exceeded)
29
+ end
30
+
31
+ private
32
+
33
+ # soo messy
34
+ def print_archived_gems(gems)
35
+ $stdout.puts
36
+ $stdout.puts "Archived gems:"
37
+ archived_gems = gems.map do |_name, gem|
38
+ gem.report.split("\n").each_with_object([]) do |line, gem_str|
39
+ gem_str << " #{line}"
40
+ end.join("\n")
41
+ end
42
+ $stdout.puts archived_gems.join("\n\n")
43
+ end
44
+
45
+ def print_error(error_messages)
46
+ return if error_messages.empty?
47
+
48
+ $stdout.puts <<~ERROR
49
+
50
+
51
+ Errors:
52
+ #{error_messages.join("\n ")}
53
+ ERROR
54
+ end
55
+
56
+ def print_summary(result)
57
+ $stdout.puts <<~RESULT
58
+
59
+ Total: #{result.total_size} (Archived: #{result.archived_size}, Alive: #{result.alive_size}, Unknown: #{result.unknown_size})
60
+ RESULT
61
+ end
62
+
63
+ def print_message(result, rate_limit_exceeded)
64
+ if result.all_alive?
65
+ say "All gems are alive!", :green
66
+ return
67
+ end
68
+
69
+ say "Too many requested! Retry later.", :yellow if rate_limit_exceeded
70
+ if result.archived_size.positive?
71
+ say "Not alive gems are found!", :red
72
+ return
73
+ end
74
+ say "Unknown gems are found!", :yellow if result.unknown_size.positive?
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -17,21 +17,13 @@ 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
20
 
23
- #
24
- # Returns alive or not
25
- #
26
- # @return [Boolean]
27
- #
28
- def alive?
29
- !client.archived?(url)
21
+ freeze
30
22
  end
31
23
 
32
24
  private
33
25
 
34
- attr_reader :url, :client
26
+ attr_reader :url
35
27
  end
36
28
  end
37
29
  end
@@ -4,41 +4,76 @@ 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)
47
+
48
+ freeze
18
49
  end
19
50
  end
20
51
 
21
- attr_reader :url, :service_name
52
+ attr_reader :url, :service_name, :gem_name
22
53
 
23
54
  #
24
55
  # Creates a `SourceCodeRepositoryUrl`
25
56
  #
26
57
  # @param [String] url
58
+ # @param [String] name
27
59
  #
28
60
  # @raise [UnSupportedUrl]
29
61
  #
30
- def initialize(url)
62
+ def initialize(url, name)
63
+ raise UnSupportedUrl.new(url: url, name: name) if url.nil?
64
+
31
65
  @url = url
32
- @service_name = service(url)
66
+ @service_name = service(url: url, name: name)
67
+ @gem_name = name
33
68
  end
34
69
 
35
70
  private
36
71
 
37
- def service(url)
72
+ def service(url:, name:)
38
73
  uri = URI.parse(url)
39
74
  host = uri.host
40
75
 
41
- raise UnSupportedUrl, url unless DOMAIN_WITH_SERVICES.key?(host)
76
+ raise UnSupportedUrl.new(url: url, name: name) unless DOMAIN_WITH_SERVICES.key?(host)
42
77
 
43
78
  DOMAIN_WITH_SERVICES[host]
44
79
  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 [Time] :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
 
@@ -52,7 +67,6 @@ module Bundler
52
67
  <<~REPORT
53
68
  Name: #{name}
54
69
  URL: #{decorated_repository_url}
55
- Status: #{decorated_alive}
56
70
 
57
71
  REPORT
58
72
  end
@@ -61,7 +75,7 @@ module Bundler
61
75
 
62
76
  def decorated_repository_url
63
77
  if repository_url == REPOSITORY_URL_UNKNOWN
64
- REPOSITORY_URL_UNKNOWN.to_s
78
+ REPOSITORY_URL_UNKNOWN
65
79
  else
66
80
  repository_url.url
67
81
  end
@@ -69,7 +83,7 @@ module Bundler
69
83
 
70
84
  def decorated_alive
71
85
  if alive == ALIVE_UNKNOWN
72
- ALIVE_UNKNOWN.to_s
86
+ ALIVE_UNKNOWN
73
87
  else
74
88
  alive
75
89
  end
@@ -0,0 +1,127 @@
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, :archived_size, :unknown_size
14
+
15
+ #
16
+ # Generates instance of `StatusCollection`
17
+ #
18
+ # @param [StatusCollection|nil] collection
19
+ #
20
+ # @return [StatusCollection]
21
+ #
22
+ def initialize(collection = {})
23
+ @collection = collection
24
+ @statuses_values = collection.values || []
25
+
26
+ @alive_size = _alive_size
27
+ @unknown_size = _unknown_size
28
+ @archived_size = _archived_size
29
+
30
+ freeze
31
+ end
32
+
33
+ #
34
+ # Fetch `Status` of `name`
35
+ #
36
+ # @param [String] name
37
+ #
38
+ # @return [Status]
39
+ #
40
+ def [](name)
41
+ collection[name]
42
+ end
43
+
44
+ #
45
+ # Names of gems
46
+ #
47
+ # @return [Array<String>]
48
+ #
49
+ def names
50
+ values.map(&:name)
51
+ end
52
+
53
+ #
54
+ # Add status
55
+ #
56
+ # @param [String] name
57
+ # @param [Status] status
58
+ #
59
+ # @return [StatusCollection]
60
+ #
61
+ def add(name, status)
62
+ collection[name] = status
63
+
64
+ self.class.new(collection)
65
+ end
66
+
67
+ #
68
+ # Merge collection
69
+ #
70
+ # @param [StatusCollection] collection
71
+ #
72
+ # @return [StatusCollection]
73
+ #
74
+ def merge(collection)
75
+ return self.class.new(self.collection) if collection.nil?
76
+
77
+ collection.each do |k, v|
78
+ self.collection[k] = v
79
+ end
80
+
81
+ self.class.new(self.collection)
82
+ end
83
+
84
+ def to_h
85
+ collection.transform_values(&:to_h)
86
+ end
87
+
88
+ def archived_gems
89
+ collection.find_all { |_name, gem| !!!gem.alive }
90
+ end
91
+
92
+ #
93
+ # All of statuses are alive nor not
94
+ #
95
+ # @return [Boolean]
96
+ #
97
+ def all_alive?
98
+ collection.find { |_name, status| !!!status.alive || status.unknown? }.nil?
99
+ end
100
+
101
+ #
102
+ # Total size of collection
103
+ #
104
+ # @return [Integer]
105
+ #
106
+ def total_size
107
+ collection.size
108
+ end
109
+
110
+ private
111
+
112
+ attr_reader :collection, :statuses_values
113
+
114
+ def _alive_size
115
+ statuses_values.count { |gem| !!gem.alive && !gem.unknown? }
116
+ end
117
+
118
+ def _archived_size
119
+ statuses_values.count { |gem| !gem.alive && !gem.unknown? }
120
+ end
121
+
122
+ def _unknown_size
123
+ statuses_values.count { |gem| gem.alive == Status::ALIVE_UNKNOWN }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,42 @@
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
+
25
+ freeze
26
+ end
27
+
28
+ #
29
+ # Merge `StatusResult`, then returns new one
30
+ #
31
+ # @param [StatusResult] result
32
+ #
33
+ # @return [StatusResult]
34
+ #
35
+ def merge(result)
36
+ self.class.new(collection: result.collection,
37
+ error_messages: result.error_messages,
38
+ rate_limit_exceeded: result.rate_limit_exceeded)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bundler
4
4
  module Alive
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.5"
6
6
  end
7
7
  end
data/lib/bundler/alive.rb CHANGED
@@ -4,8 +4,12 @@ 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/report"
11
+ require_relative "alive/client/gems_api_client"
12
+ require_relative "alive/client/gems_api_response"
10
13
  require_relative "alive/client/git_hub_api"
11
14
  require_relative "alive/client/source_code_client"
15
+ require_relative "alive/reportable"