gem-search 0.1.9 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f59b9866583c10a920326889b8a6b81e9f87d5a0
4
- data.tar.gz: 8cf2b2a2de452c660297409afd505fef339b15f7
3
+ metadata.gz: 7e18e85e48c445d971dddeca013aa98f5f7ec0b9
4
+ data.tar.gz: fb23bd1dbe81f483ddceebae35af484d92ff0052
5
5
  SHA512:
6
- metadata.gz: 7e9b33d495af55046bc9473b49f7e91efc7faeda3f22b16c009cb05807e7b917fcfd6296e5c071f9dc4c2576433a300cb9e9a1b4c47a15296feacc616fb97eb9
7
- data.tar.gz: 4dcdacf41d66b29f6f05f36e8dc737ce5fc1ab6af7d1e57ffc657505ff13ab5340bf982c74e72324736d68a48b4588299a429ff3de1f837964815bbe4d4ec457
6
+ metadata.gz: d9ed2f1eaa162fa053322886f9efdb9eed66eb8352347158cbb1f6373d658e28b349cce3507668e5f4932af47fde793d5f9709022ac059341d93af765deaed1f
7
+ data.tar.gz: b55f56810fff31b4f613c566f5e0350f5247685e620776507781d72653651b8fc6e8d4d5982d94e1df65e6b25b3aa2f2e363e2d46351497a0a35eaef5027d706
@@ -0,0 +1,17 @@
1
+ Metrics/LineLength:
2
+ Max: 120
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Style/ClassAndModuleChildren:
8
+ Enabled: false
9
+
10
+ Style/FileName:
11
+ Exclude: ['bin/gem-search']
12
+
13
+ Style/FormatString:
14
+ Enabled: false
15
+
16
+ Style/NumericLiterals:
17
+ Enabled: false
@@ -1,3 +1,8 @@
1
+ ## v0.2.0
2
+
3
+ - Add a Interrupt handling [a93beb1]
4
+ When it detect Interrupt(Ctrl-C), it display results of searching part way through.
5
+
1
6
  ## v0.1.9
2
7
 
3
8
  - Show homepage url as default option [ddae051]
@@ -11,7 +16,7 @@ variations: a/all/, n/name, v/ver/version
11
16
 
12
17
  ## v0.1.7
13
18
 
14
- - Remove reduncancy dots [17ae8cd]
19
+ - Remove redundancy dots [54c34af]
15
20
  The request was one extra.
16
21
 
17
22
  ## v0.1.6
@@ -30,7 +35,7 @@ unsupported Ruby 1.9.0
30
35
 
31
36
  ## v0.1.3
32
37
 
33
- - Implement a feature that is shown a homepage url when detail ooption is selected [f89d9c2]
38
+ - Implement a feature that is shown a homepage url when detail option is selected [f89d9c2]
34
39
 
35
40
  ## v0.1.2
36
41
 
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
- gem 'rake', :require => false
5
- gem 'rspec', :require => false
4
+ gem 'rake', require: false
5
+ gem 'rspec', require: false
data/README.md CHANGED
@@ -5,9 +5,8 @@
5
5
 
6
6
  # gem-search
7
7
 
8
- Now gem-search is not published as a rubygem.
9
- gem-search is a command line utitlity like rubygems('gem search').
10
- You can see a download total gem and sort columns.
8
+ `gem-search` is a command line utility like 'gem search'.
9
+ You can see downloads total and sort columns.
11
10
 
12
11
  ## Requirements
13
12
 
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => [:spec]
6
+ task default: [:spec]
@@ -2,10 +2,10 @@ module GemSearch
2
2
  require 'mem'
3
3
  require 'slop'
4
4
 
5
- class LibraryNotFound < StandardError; end
5
+ RUBYGEMS_URL = 'https://rubygems.org'
6
6
 
7
7
  require 'gem_search/command_builder'
8
8
  require 'gem_search/commands'
9
- require 'gem_search/executor'
9
+ require 'gem_search/request'
10
10
  require 'gem_search/version'
11
11
  end
@@ -31,7 +31,7 @@ module GemSearch
31
31
  'default [a]ll',
32
32
  '[a]ll :DL(all) e.g.: gem-search webkit -s a',
33
33
  '[v]er :DL(ver) e.g.: gem-search webkit -s v',
34
- '[n]ame : e.g.: gem-search webkit -s n',
34
+ '[n]ame : e.g.: gem-search webkit -s n'
35
35
  ])
36
36
  opts.string('-s', '--sort', sort_msg)
37
37
  opts.string('-b', '--browse', "Open rubygem's homepage in the system's default web browser.")
@@ -6,6 +6,20 @@ module GemSearch
6
6
  def initialize(options)
7
7
  @options = options
8
8
  end
9
+
10
+ private
11
+
12
+ def puts_abort(args)
13
+ puts args
14
+ abort
15
+ end
16
+
17
+ def unexpected_error(e)
18
+ puts "\nAn unexpected #{e.class} has occurred."
19
+ puts e.message
20
+ puts e.backtrace if ENV['DEBUG']
21
+ abort
22
+ end
9
23
  end
10
24
  end
11
25
  end
@@ -1,16 +1,32 @@
1
+ require 'gem_search/utils/system_util'
2
+
1
3
  module GemSearch
2
4
  module Commands
3
5
  class Browse < Base
6
+ include Utils::SystemUtil
7
+
8
+ GEM_URL = "#{RUBYGEMS_URL}/gems/%s"
9
+
4
10
  def call
5
- executor = Executor.new
6
- executor.browse(options[:browse])
11
+ gem_name = options[:browse]
12
+ result = Request.new.search_for_browse(gem_name)
13
+ url = extract_uri(result['homepage_uri'], gem_name)
14
+ puts "Opening #{url}"
15
+ browser_open(url)
7
16
  rescue OpenURI::HTTPError
8
- puts 'No such gem.'
9
- abort
17
+ puts_abort('No such a gem.')
10
18
  rescue => e
11
- puts e.message
12
- puts e.stacktrace if ENV['DEBUG']
13
- abort
19
+ unexpected_error(e)
20
+ end
21
+
22
+ private
23
+
24
+ def extract_uri(homepage_uri, gem_name)
25
+ if homepage_uri.nil? || homepage_uri.empty?
26
+ GEM_URL % gem_name
27
+ else
28
+ homepage_uri
29
+ end
14
30
  end
15
31
  end
16
32
  end
@@ -1,43 +1,55 @@
1
+ require 'gem_search/rendering'
1
2
  module GemSearch
2
3
  module Commands
3
4
  class Run < Base
5
+ include Mem
6
+ include Rendering
7
+
4
8
  ENABLE_SORT_OPTS = {
5
9
  'a' => 'downloads',
6
10
  'n' => 'name',
7
- 'v' => 'version_downloads',
11
+ 'v' => 'version_downloads'
8
12
  }
9
13
 
10
14
  def call
11
- unless valid?(options.arguments)
12
- puts options
13
- abort
14
- end
15
- executor = Executor.new
16
- gem = options.arguments[0]
17
- executor.search(gem, setup_opts)
18
- rescue GemSearch::LibraryNotFound => e
19
- puts e.message
20
- abort
15
+ puts_abort(options) unless valid?(options.arguments)
16
+ gems = search_gems
17
+ puts_abort('We did not find results.') if gems.size.zero?
18
+ gems_sort!(gems)
19
+ render(gems, !options['no-homepage'])
21
20
  rescue => e
22
- puts "An unexpected #{e.class} has occurred."
23
- puts e.message
24
- puts e.backtrace if ENV['DEBUG']
25
- abort
21
+ unexpected_error(e)
26
22
  end
27
23
 
28
24
  private
29
25
 
30
- def setup_opts
31
- sort = options['sort'] ? ENABLE_SORT_OPTS[options['sort'][0].downcase] : nil
32
- {
33
- sort: sort || 'downloads',
34
- has_homepage: !options['no-homepage']
35
- }
36
- end
37
-
38
26
  def valid?(arguments)
39
27
  arguments.size > 0 && arguments.none? { |arg| arg.match(/\A-/) }
40
28
  end
29
+
30
+ def search_gems
31
+ print 'Searching '
32
+ gems = Request.new.search(options.arguments[0]) do
33
+ print '.'
34
+ end
35
+ puts
36
+ gems
37
+ end
38
+
39
+ def gems_sort!(gems)
40
+ if sort_opt == 'name'
41
+ gems.sort! { |x, y| x[sort_opt] <=> y[sort_opt] }
42
+ else
43
+ gems.sort! { |x, y| y[sort_opt] <=> x[sort_opt] }
44
+ end
45
+ end
46
+
47
+ def sort_opt
48
+ sort_opt = options['sort'] ? ENABLE_SORT_OPTS[options['sort'][0].downcase] : nil
49
+ sort_opt = 'downloads' unless sort_opt
50
+ sort_opt
51
+ end
52
+ memoize :sort_opt
41
53
  end
42
54
  end
43
55
  end
@@ -3,10 +3,6 @@ module GemSearch
3
3
  # NAME', 'DL(ver)', 'DL(all)', 'HOMEPAGE'
4
4
  DEFAULT_RULED_LINE_SIZE = [50, 8, 9, 60]
5
5
 
6
- def self.included(base)
7
- base.extend(self)
8
- end
9
-
10
6
  def render(gems, has_homepage)
11
7
  @has_homepage = has_homepage
12
8
  ruled_line_size(gems)
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+
4
+ module GemSearch
5
+ class Request
6
+ SEARCH_API = "#{RUBYGEMS_URL}/api/v1/search.json?query=%s&page=%d"
7
+ GEM_API = "#{RUBYGEMS_URL}/api/v1/gems/%s.json"
8
+
9
+ MAX_REQUEST_COUNT = 20
10
+ MAX_GEMS_PER_PAGE = 30 # It has been determined by Rubygems API
11
+
12
+ def search(query, &post_hook)
13
+ gems = []
14
+ (1..MAX_REQUEST_COUNT).each do |n|
15
+ post_hook.call if post_hook
16
+ url = SEARCH_API % [query, n]
17
+ results = request_ruby_gems_api(url)
18
+ gems += results
19
+ break if search_ended?(results.size)
20
+ end
21
+ gems
22
+ rescue Interrupt
23
+ gems
24
+ end
25
+
26
+ def search_for_browse(gem)
27
+ api_url = GEM_API % gem
28
+ request_ruby_gems_api(api_url)
29
+ end
30
+
31
+ private
32
+
33
+ def search_ended?(size)
34
+ size < MAX_GEMS_PER_PAGE || size.zero?
35
+ end
36
+
37
+ def request_ruby_gems_api(url)
38
+ option = {}
39
+ proxy = URI.parse(url).find_proxy
40
+ if proxy
41
+ if proxy.user && proxy.password
42
+ option[:proxy_http_basic_authentication] = [proxy, proxy.user, proxy.password]
43
+ else
44
+ option[:proxy] = proxy
45
+ end
46
+ end
47
+ JSON.parse(open(url, option).read)
48
+ end
49
+ end
50
+ end
@@ -2,7 +2,7 @@ module GemSearch::Utils
2
2
  module SystemUtil
3
3
  # https://github.com/github/hub/blob/9c589396ae38f7b9f98319065ad491149954c152/lib/hub/context.rb#L517
4
4
  def browser_open(url)
5
- cmd = osx? ? 'open' : %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
5
+ cmd = osx? ? 'open' : %w(xdg-open cygstart x-www-browser firefox opera mozilla netscape).find { |com| which com }
6
6
  system(cmd, url)
7
7
  end
8
8
 
@@ -1,3 +1,3 @@
1
1
  module GemSearch
2
- VERSION = '0.1.9'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -5,9 +5,44 @@ RSpec.describe Commands::Browse do
5
5
  let(:query) { 'factory_girl' }
6
6
  let(:options) { { browse: query } }
7
7
 
8
- context 'with browse option' do
9
- it 'called Executor#browse with browse option' do
10
- expect_any_instance_of(Executor).to receive(:browse).with(query).once
8
+ context 'when a network error occurred' do
9
+ before do
10
+ stub_request(:get, build_gems_uri(query))
11
+ .to_return(status: 500, body: '[]')
12
+ end
13
+ let(:query) { 'network_error_orccurred' }
14
+ it { expect { Commands::Browse.new(options).call }.to raise_error(Exception) }
15
+ end
16
+
17
+ context 'when no matching gem' do
18
+ before { stub_request_gems_no_result(query) }
19
+ let(:query) { 'no_match_gem_name' }
20
+ it { expect { Commands::Browse.new(options).call }.to raise_error(SystemExit) }
21
+ end
22
+
23
+ context 'when no homepage_uri' do
24
+ before do
25
+ http_stub = load_http_stubs("gems/#{query}.json")
26
+ stub_request_gems(query, http_stub)
27
+ end
28
+ let(:query) { 'git-trend_no_homepage' }
29
+ let(:uri) { Commands::Browse::GEM_URL % query }
30
+ it 'open a rubygems uri' do
31
+ expect_any_instance_of(Kernel).to receive(:system).with(anything, uri)
32
+ Commands::Browse.new(options).call
33
+ end
34
+ end
35
+
36
+ context 'when homepage_uri is existed' do
37
+ before do
38
+ http_stub = load_http_stubs("gems/#{query}.json")
39
+ @uri = JSON.parse(http_stub)['homepage_uri']
40
+ stub_request_gems(query, http_stub)
41
+ end
42
+ let(:query) { 'git-trend' }
43
+ let(:uri) { @uri }
44
+ it 'open a homepage uri' do
45
+ expect_any_instance_of(Kernel).to receive(:system).with(anything, uri)
11
46
  Commands::Browse.new(options).call
12
47
  end
13
48
  end
@@ -1,11 +1,24 @@
1
1
  include GemSearch
2
2
 
3
3
  RSpec.describe Commands::Run do
4
- shared_examples 'sort example' do |expected_option, actual_option|
5
- before { allow(options).to receive(:[]).with('sort').and_return(actual_option) }
6
- it 'called Executor#search with sort option' do
7
- expect_any_instance_of(Executor).to receive(:search).with(query, default_opts(sort: expected_option)).once
8
- Commands::Run.new(options).call
4
+ shared_examples 'sort example by all' do |sort_option|
5
+ before { allow(options).to receive(:[]).with('sort').and_return(sort_option) }
6
+ it 'display rubygems sorted by DL(all)' do
7
+ expect { Commands::Run.new(options).call }.to output(dummy_search_result_sorted_by_all).to_stdout
8
+ end
9
+ end
10
+
11
+ shared_examples 'sort example by ver' do |sort_option|
12
+ before { allow(options).to receive(:[]).with('sort').and_return(sort_option) }
13
+ it 'display rubygems sorted by DL(ver)' do
14
+ expect { Commands::Run.new(options).call }.to output(dummy_search_result_sorted_by_ver).to_stdout
15
+ end
16
+ end
17
+
18
+ shared_examples 'sort example by name' do |sort_option|
19
+ before { allow(options).to receive(:[]).with('sort').and_return(sort_option) }
20
+ it 'display rubygems sorted by NAME' do
21
+ expect { Commands::Run.new(options).call }.to output(dummy_search_result_sorted_by_name).to_stdout
9
22
  end
10
23
  end
11
24
 
@@ -14,56 +27,156 @@ RSpec.describe Commands::Run do
14
27
  allow(options).to receive(:arguments).and_return([query])
15
28
  allow(options).to receive(:[]).with('no-homepage')
16
29
  allow(options).to receive(:[]).with('sort')
17
- stub_request_search(query, 1, dummy_search_result)
18
- stub_request_search_no_result_with_page(query, 2)
19
30
  end
20
- let(:query) { 'factory_girl' }
21
31
  let(:options) { double('options') }
22
32
 
23
- describe 'sort option' do
33
+ context 'when a network error occurred' do
34
+ before do
35
+ stub_request(:get, build_search_uri(query, 1))
36
+ .to_return(status: 500, body: '[]')
37
+ end
38
+ let(:query) { 'network_error_orccurred' }
39
+ it { expect { Commands::Run.new(options).call }.to raise_error(SystemExit) }
40
+ end
41
+
42
+ context 'when no matching gem' do
43
+ before { stub_request_search_no_result_with_page(query, 1) }
44
+ let(:query) { 'no_matching_gem_name' }
45
+ it { expect { Commands::Run.new(options).call }.to raise_error(SystemExit) }
46
+ end
47
+
48
+ describe 'with no-homepage option' do
49
+ before do
50
+ allow(options).to receive(:[]).with('no-homepage').and_return(true)
51
+ stub_request_search(query, 1, dummy_search_result)
52
+ stub_request_search_no_result_with_page(query, 2)
53
+ end
54
+ let(:query) { 'factory_girl' }
55
+
56
+ context 'with no sort option' do
57
+ it 'display rubygems ordering by DL(all)' do
58
+ res = <<-'EOS'.unindent
59
+ |Searching .
60
+ |NAME DL(ver) DL(all)
61
+ |-------------------------------------------------- -------- ---------
62
+ |factory_girl (3.6.0) 541 2042859
63
+ |factory_girl_rails (3.5.0) 39724 1238780
64
+ |factory_girl_generator (0.0.3) 8015 15547
65
+ EOS
66
+ expect { Commands::Run.new(options).call }.to output(res).to_stdout
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'sorting' do
72
+ before do
73
+ stub_request_search(query, 1, dummy_search_result)
74
+ stub_request_search_no_result_with_page(query, 2)
75
+ end
76
+ let(:query) { 'factory_girl' }
77
+
24
78
  describe 'sort by all' do
25
- context 'without sort option' do
26
- include_examples 'sort example', 'downloads', nil
79
+ context 'with no sort option' do
80
+ include_examples 'sort example by all', nil
27
81
  end
28
- context 'with disalbe sort option' do
29
- include_examples 'sort example', 'downloads', 'xyz'
82
+
83
+ context 'with disallowed sort option' do
84
+ include_examples 'sort example by all', 'xyz'
30
85
  end
86
+
31
87
  context 'with a' do
32
- include_examples 'sort example', 'downloads', 'a'
88
+ include_examples 'sort example by all', 'a'
33
89
  end
90
+
34
91
  context 'with all' do
35
- include_examples 'sort example', 'downloads', 'all'
92
+ include_examples 'sort example by all', 'all'
36
93
  end
94
+
37
95
  context 'with ALL' do
38
- include_examples 'sort example', 'downloads', 'ALL'
96
+ include_examples 'sort example by all', 'ALL'
39
97
  end
40
98
  end
41
- end
42
99
 
43
- describe 'sort by name' do
44
- context 'with n' do
45
- include_examples 'sort example', 'name', 'n'
46
- end
47
- context 'with name' do
48
- include_examples 'sort example', 'name', 'name'
100
+ describe 'sort by ver' do
101
+ context 'with v' do
102
+ include_examples 'sort example by ver', 'v'
103
+ end
104
+
105
+ context 'with ver' do
106
+ include_examples 'sort example by ver', 'ver'
107
+ end
108
+
109
+ context 'with VER' do
110
+ include_examples 'sort example by ver', 'VER'
111
+ end
49
112
  end
50
- context 'with NAME' do
51
- include_examples 'sort example', 'name', 'NAME'
113
+
114
+ describe 'sort by name' do
115
+ context 'with n' do
116
+ include_examples 'sort example by name', 'n'
117
+ end
118
+
119
+ context 'with name' do
120
+ include_examples 'sort example by name', 'name'
121
+ end
122
+
123
+ context 'with NAME' do
124
+ include_examples 'sort example by name', 'NAME'
125
+ end
52
126
  end
53
127
  end
54
128
 
55
- describe 'sort by ver' do
56
- context 'with v' do
57
- include_examples 'sort example', 'version_downloads', 'v'
129
+ describe 'multiple page' do
130
+ before do
131
+ allow(options).to receive(:[]).with('sort').and_return('name')
132
+ stub_request_search(query, 1, load_http_stubs('search/cucumber-_1.json'))
133
+ stub_request_search(query, 2, load_http_stubs('search/cucumber-_2.json'))
134
+ stub_request_search(query, 3, load_http_stubs('search/cucumber-_3.json'))
135
+ stub_request_search_no_result_with_page(query, 4)
58
136
  end
59
- context 'with ver' do
60
- include_examples 'sort example', 'version_downloads', 'ver'
137
+ let(:query) { 'cucumber-' }
138
+ it 'display rubygems ordering by name' do
139
+ expect { Commands::Run.new(options).call }.to output(dummy_search_results_multiple_pages).to_stdout
140
+ end
141
+ end
142
+
143
+ describe 'ruled NAME line' do
144
+ context 'NAME size is 42' do
145
+ before do
146
+ stub_request_search(query, 1, dummy_search_result_name_size_is_42)
147
+ stub_request_search_no_result_with_page(query, 2)
148
+ end
149
+ let(:query) { 'size_is_42_2345678901234567890123456789012' }
150
+ it 'is 50 characters' do
151
+ # rubocop:disable Metrics/LineLength, Style/TrailingWhitespace
152
+ res = <<-'EOS'.unindent
153
+ |Searching .
154
+ |NAME DL(ver) DL(all) HOMEPAGE
155
+ |-------------------------------------------------- -------- --------- ------------------------------------------------------------
156
+ |size_is_42_2345678901234567890123456789012 (0.0.1) 100 1000
157
+ EOS
158
+ # rubocop:enable Metrics/LineLength, Style/TrailingWhitespace
159
+ expect { Commands::Run.new(options).call }.to output(res).to_stdout
160
+ end
61
161
  end
62
- context 'with version' do
63
- include_examples 'sort example', 'version_downloads', 'version'
162
+ end
163
+
164
+ context 'NAME size is 43' do
165
+ before do
166
+ stub_request_search(query, 1, dummy_search_result_name_size_is_43)
167
+ stub_request_search_no_result_with_page(query, 2)
64
168
  end
65
- context 'with VER' do
66
- include_examples 'sort example', 'version_downloads', 'VER'
169
+ let(:query) { 'size_is_43_23456789012345678901234567890123' }
170
+ it 'is 51 characters' do
171
+ # rubocop:disable Metrics/LineLength, Style/TrailingWhitespace
172
+ res = <<-'EOS'.unindent
173
+ |Searching .
174
+ |NAME DL(ver) DL(all) HOMEPAGE
175
+ |--------------------------------------------------- -------- --------- ------------------------------------------------------------
176
+ |size_is_43_23456789012345678901234567890123 (0.0.2) 200 2000
177
+ EOS
178
+ # rubocop:enable Metrics/LineLength, Style/TrailingWhitespace
179
+ expect { Commands::Run.new(options).call }.to output(res).to_stdout
67
180
  end
68
181
  end
69
182
  end