gem-search 0.1.9 → 0.2.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
  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