kat 2.0.5 → 2.0.6

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: d11aee9f32fb16ec8fc1c4927eeeb2561bab56f1
4
- data.tar.gz: 9e2a9bc6ffa80b85424ee74f2617da798c1e6ba4
3
+ metadata.gz: ef9cbd9bc5edfd5bcbbab270423445df0e0ffec2
4
+ data.tar.gz: 57b4de26084d944ad5e369f38e8727442bad663a
5
5
  SHA512:
6
- metadata.gz: 1ccc89e6bcfd9e9a9e88a0cc97b210a79b5a9250e72071aa4a1e23a3203275d3d9785f870be21da77adea98cca34cb55c63efd8835ffec3259b6c87a57786454
7
- data.tar.gz: a7cf13cf4d2300a6d4616e49c17afe7688089109ee0d346de8f5ba8d60a48cccb5399da78e5d69b9e5491f828974a4cf5c788a1cd9ac957c8c6027c96631f7dd
6
+ metadata.gz: 509efe18911af40dfdaf17f3c86196d4990f833a500ebc2c68925d6fc0710fa86690beb266343dbb7d1134c6e171d2c578a31ded5830a2eb837378178663a135
7
+ data.tar.gz: 588d18d574b744c7f026d51fcfb2da40e8e0689dc64a94bf9f2ca9722ed3c8fac230281ee0c59007d30d2eadc9b2c65f74c97465ee6044ea3ac0ac3ed0e719b7
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
 
4
- task :default => :test
4
+ task default: :test
5
5
 
6
6
  Rake::TestTask.new do |t|
7
7
  t.libs.push 'lib'
@@ -15,9 +15,9 @@ Gem::Specification.new do |s|
15
15
  s.summary = 'Kickass Torrents Interface'
16
16
  s.description = 'A Ruby interface to Kickass Torrents'
17
17
 
18
- s.files = `git ls-files`.split $/
19
- s.executables = s.files.grep(%r{^bin/}) { |f| File.basename f }
20
- s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+ s.files = `git ls-files`.split "\n"
19
+ s.executables = s.files.grep(/^bin\//) { |f| File.basename f }
20
+ s.test_files = s.files.grep(/^(test|spec|features)\//)
21
21
  s.require_paths = ['lib']
22
22
 
23
23
  s.add_runtime_dependency 'nokogiri', '~> 1.6'
@@ -6,14 +6,11 @@ require 'highline'
6
6
  require 'yaml'
7
7
 
8
8
  module Kat
9
-
10
- class << self
11
- #
12
- # Convenience method for the App class
13
- #
14
- def app(args = ARGV)
15
- App.new(args).main
16
- end
9
+ #
10
+ # Convenience method for the App class
11
+ #
12
+ def self.app(args = ARGV)
13
+ App.new(args).main
17
14
  end
18
15
 
19
16
  class App
@@ -48,16 +45,16 @@ module Kat
48
45
  #
49
46
  def init_options(args = nil)
50
47
  @args = case args
51
- when nil then []
52
- when String then args.split
53
- else args
54
- end
48
+ when nil then []
49
+ when String then args.split
50
+ else args
51
+ end
55
52
 
56
53
  @options = load_config || {}
57
54
 
58
- Kat.options(@args).tap { |o|
55
+ Kat.options(@args).tap do |o|
59
56
  @options.merge!(o) { |k, ov, nv| o["#{ k }_given".intern] ? nv : ov }
60
- }
57
+ end
61
58
 
62
59
  Kat::Colour.colour = @options[:colour]
63
60
  rescue NoMethodError => e
@@ -82,16 +79,16 @@ module Kat
82
79
  def main
83
80
  puts VERSION_STR
84
81
 
85
- Kat::Search.selects.select { |k, v| @options[v[:select]] }.tap { |lists|
82
+ Kat::Search.selects.select { |k, v| @options[v[:select]] }.tap do |lists|
86
83
  if lists.empty?
87
84
  while running; end
88
85
  else
89
86
  puts format_lists lists
90
87
  end
91
- }
88
+ end
92
89
  end
93
90
 
94
- private
91
+ private
95
92
 
96
93
  #
97
94
  # Get the width of the terminal window
@@ -139,7 +136,7 @@ module Kat
139
136
 
140
137
  -> {
141
138
  i = 0
142
- while searching do
139
+ while searching
143
140
  print "\rSearching...".yellow + '\\|/-'[i % 4]
144
141
  i += 1
145
142
  sleep 0.1
@@ -147,7 +144,7 @@ module Kat
147
144
  }
148
145
  ].map { |w| Thread.new { w.call } }.each(&:join)
149
146
 
150
- puts (res = format_results)
147
+ puts((res = format_results))
151
148
 
152
149
  if res.size > 1
153
150
  case (answer = prompt)
@@ -156,7 +153,7 @@ module Kat
156
153
  when 'p' then @page -= 1 if prev?
157
154
  when 'q' then return false
158
155
  else
159
- if (1..@kat.results[@page].size).include? (answer = answer.to_i)
156
+ if (1..@kat.results[@page].size).include?((answer = answer.to_i))
160
157
  print "\nDownloading".yellow <<
161
158
  ": #{ @kat.results[@page][answer - 1][:title] }... "
162
159
  puts download @kat.results[@page][answer - 1]
@@ -173,21 +170,21 @@ module Kat
173
170
  # Format a list of options
174
171
  #
175
172
  def format_lists(lists)
176
- lists.inject([nil]) { |buf, (k, v)|
177
- opts = Kat::Search.send(v[:select])
178
- buf << v[:select].to_s.capitalize
179
- buf << nil unless Array === opts.values.first
173
+ lists.inject([nil]) do |buf, (_, val)|
174
+ opts = Kat::Search.send(val[:select])
175
+ buf << val[:select].to_s.capitalize
176
+ buf << nil unless opts.values.first.is_a? Array
180
177
  width = opts.keys.sort { |a, b| b.size <=> a.size }.first.size
181
- opts.each { |k, v|
182
- buf += if Array === v
183
- [nil, "%#{ width }s => #{ v.shift }" % k] +
184
- v.map { |e| ' ' * (width + 4) + e }
185
- else
186
- ["%-#{ width }s => #{ v }" % k]
187
- end
188
- }
178
+ opts.each do |k, v|
179
+ buf += if v.is_a? Array
180
+ [nil, "%#{ width }s => #{ v.shift }" % k] +
181
+ v.map { |e| ' ' * (width + 4) + e }
182
+ else
183
+ ["%-#{ width }s => #{ v }" % k]
184
+ end
185
+ end
189
186
  buf << nil
190
- }
187
+ end
191
188
  end
192
189
 
193
190
  #
@@ -208,14 +205,14 @@ module Kat
208
205
  buf << ("\r%-#{ main_width + 5 }s#{ ' Size Age Seeds Leeches' if !hide_info? || @show_info }" %
209
206
  ["Page #{ page + 1 } of #{ @kat.pages }", nil]).yellow
210
207
 
211
- @kat.results[@page].each_with_index { |t, i|
208
+ @kat.results[@page].each_with_index do |t, i|
212
209
  age = t[:age].split "\xC2\xA0"
213
- age = "%3d %-6s" % age
210
+ age = '%3d %-6s' % age
214
211
  # Filter out the crap that invariably infests torrent names
215
212
  title = t[:title].codepoints.map { |c| c > 31 && c < 127 ? c.chr : '?' }.join[0...main_width]
216
213
  buf << ("%2d. %-#{ main_width }s#{ ' %10s %10s %7d %7d' if !hide_info? or @show_info }" %
217
214
  [i + 1, title, t[:size], age, t[:seeds], t[:leeches]]).tap { |s| s.red! if t[:seeds] == 0 }
218
- }
215
+ end
219
216
 
220
217
  buf << nil
221
218
  end
@@ -242,12 +239,12 @@ module Kat
242
239
  "#{ ', ' << '(n)'.cyan(true) << 'ext' if next? }" <<
243
240
  "#{ ', ' << '(p)'.cyan(true) << 'rev' if prev? }" <<
244
241
  "#{ ", #{ @show_info ? 'hide' : 'show' } " << '(i)'.cyan(true) << 'nfo' if hide_info? }" <<
245
- ', ' << '(q)'.cyan(true) << 'uit: ') { |q|
242
+ ', ' << '(q)'.cyan(true) << 'uit: ') do |q|
246
243
  q.responses[:not_valid] = 'Invalid option.'
247
244
  q.validate = validation_regex
248
- }
249
- rescue RegexpError => e
250
- puts (@kat.pages > 0 ? "Error reading the page\n" : "Could not connect to the site\n").red
245
+ end
246
+ rescue RegexpError
247
+ puts((@kat.pages > 0 ? "Error reading the page\n" : "Could not connect to the site\n").red)
251
248
 
252
249
  return 'q'
253
250
  end
@@ -260,12 +257,12 @@ module Kat
260
257
 
261
258
  uri = URI(URI.encode torrent[:download])
262
259
  uri.query = nil
263
- file = "#{ @options[:output] || '.' }/" <<
260
+ file = "#{ @options[:output] || '.' }/" \
264
261
  "#{ torrent[:title].tr(' ', ?.).gsub(/[^a-z0-9()_.-]/i, '') }.torrent"
265
262
 
266
- fail '404 File Not Found' if (res = Net::HTTP.start(uri.host) { |http|
263
+ fail '404 File Not Found' if (res = Net::HTTP.start(uri.host) do |http|
267
264
  http.get uri
268
- }).code == '404'
265
+ end).code == '404'
269
266
 
270
267
  File.open(File.expand_path(file), 'w') { |f| f.write res.body }
271
268
 
@@ -278,13 +275,11 @@ module Kat
278
275
  # Load options from CONFIG if it exists
279
276
  #
280
277
  def load_config
281
- (symbolise = -> h {
282
- Hash === h ? Hash[h.map { |k, v| [k.intern, symbolise[v]] }] : h
283
- })[YAML.load_file CONFIG] if File.readable? CONFIG
278
+ (symbolise = lambda do |h|
279
+ h.is_a?(Hash) ? Hash[h.map { |k, v| [k.intern, symbolise[v]] }] : h
280
+ end)[YAML.load_file CONFIG] if File.readable? CONFIG
284
281
  rescue => e
285
282
  warn "Failed to load #{ CONFIG }: #{ e }"
286
283
  end
287
-
288
284
  end
289
-
290
285
  end
@@ -1,23 +1,19 @@
1
1
  module Kat
2
-
3
2
  module Colour
4
-
5
3
  COLOURS = %w(black red green yellow blue magenta cyan white)
6
4
 
7
- class << self
8
- # From AwesomePrint.colorize? by Michael Dvorkin
9
- # https://github.com/michaeldv/awesome_print/blob/master/lib/awesome_print/inspector.rb
10
- def capable?
11
- STDOUT.tty? && (ENV['TERM'] && ENV['TERM'] != 'dumb' || ENV['ANSICON'])
12
- end
5
+ # From AwesomePrint.colorize? by Michael Dvorkin
6
+ # https://github.com/michaeldv/awesome_print/blob/master/lib/awesome_print/inspector.rb
7
+ def self.capable?
8
+ STDOUT.tty? && (ENV['TERM'] && ENV['TERM'] != 'dumb' || ENV['ANSICON'])
9
+ end
13
10
 
14
- def colour=(f)
15
- @@colour = f && capable?
16
- end
11
+ def self.colour=(f)
12
+ @@colour = f && capable?
13
+ end
17
14
 
18
- def colour?
19
- @@colour
20
- end
15
+ def self.colour?
16
+ @@colour
21
17
  end
22
18
 
23
19
  @@colour = capable?
@@ -26,14 +22,14 @@ module Kat
26
22
  @@colour
27
23
  end
28
24
 
29
- COLOURS.each { |c|
25
+ COLOURS.each do |c|
30
26
  define_method(c) { |*args| colour c, args[0] }
31
27
  define_method("#{ c }!") { |*args| colour! c, args[0] }
32
- }
28
+ end
33
29
 
34
30
  def uncolour
35
31
  case self
36
- when String then gsub /\e\[[0-9;]+?m(.*?)\e\[0m/, '\\1'
32
+ when String then gsub(/\e\[[0-9;]+?m(.*?)\e\[0m/, '\\1')
37
33
  when Array then map { |e| e.uncolour if e }
38
34
  else self
39
35
  end
@@ -48,12 +44,15 @@ module Kat
48
44
  self
49
45
  end
50
46
 
51
- private
47
+ private
52
48
 
53
49
  def colour(name, intense = false)
54
50
  return case self
55
- when String, Symbol then "\e[#{ intense ? 1 : 0 };#{ 30 + COLOURS.index(name) }m#{ self }\e[0m"
56
- when Array then map { |e| e.send name.to_s, intense if e }
51
+ when String, Symbol
52
+ "\e[#{ intense ? 1 : 0 };" \
53
+ "#{ 30 + COLOURS.index(name) }m#{ self }\e[0m"
54
+ when Array
55
+ map { |e| e.send name.to_s, intense if e }
57
56
  end if colour?
58
57
 
59
58
  self
@@ -62,14 +61,12 @@ module Kat
62
61
  def colour!(name, intense = false)
63
62
  case self
64
63
  when String then replace send(name.to_s, intense)
65
- when Array then each { |e| e.send "#{ name.to_s }!", intense if e }
64
+ when Array then each { |e| e.send "#{ name }!", intense if e }
66
65
  end if colour?
67
66
 
68
67
  self
69
68
  end
70
-
71
69
  end
72
-
73
70
  end
74
71
 
75
72
  class String; include Kat::Colour end
@@ -1,5 +1,4 @@
1
1
  module Kat
2
-
3
2
  FIELD_MAP = {
4
3
  exact: { type: :string, desc: 'Exact phrase' },
5
4
  or: { type: :string, desc: 'Optional words', multi: true },
@@ -29,5 +28,4 @@ module Kat
29
28
  output: { type: :string, desc: 'Directory to save torrents in', short: :o },
30
29
  colour: { desc: 'Output with colour', short: :none }
31
30
  }.freeze
32
-
33
31
  end
@@ -3,57 +3,50 @@ require File.dirname(__FILE__) + '/version'
3
3
  require 'trollop'
4
4
 
5
5
  module Kat
6
-
7
- class << self
8
- #
9
- # Convenience method for the Options class
10
- #
11
- def options(args)
12
- Options.parse args
13
- end
6
+ #
7
+ # Convenience method for the Options class
8
+ #
9
+ def self.options(args)
10
+ Options.parse args
14
11
  end
15
12
 
16
13
  class Options
14
+ #
15
+ # Pick out the invocation options from the field map
16
+ #
17
+ def self.options_map
18
+ fields = %i(desc type multi select short)
17
19
 
18
- class << self
19
- #
20
- # Pick out the invocation options from the field map
21
- #
22
- def options_map
23
- fields = %i(desc type multi select short)
24
-
25
- FIELD_MAP.inject({}) { |hash, (k, v)|
26
- hash.tap { |h| h[k] = v.select { |f| fields.include? f } if v[:desc] }
27
- }
20
+ FIELD_MAP.reduce({}) do |hash, (k, v)|
21
+ hash.tap { |h| h[k] = v.select { |f| fields.include? f } if v[:desc] }
28
22
  end
23
+ end
29
24
 
30
- def parse(args)
31
- Trollop::options(args) {
32
- version VERSION_STR
33
- banner <<-USAGE.gsub /^\s+\|/, ''
34
- |#{ VERSION_STR }
35
- |
36
- |Usage: #{ File.basename __FILE__ } [options] <query>+
37
- |
38
- | Options:
39
- USAGE
40
-
41
- Options.options_map.each { |k, v|
42
- opt k,
43
- v[:desc],
44
- { type: v[:type] || :boolean,
45
- multi: v[:multi],
46
- short: v[:short] }
47
- }
48
-
49
- Options.options_map.each { |k, v|
50
- opt v[:select],
51
- "List the #{ v[:select] } that may be used with --#{ k }",
52
- short: :none if v[:select]
53
- }
54
- }
25
+ def self.parse(args)
26
+ Trollop.options(args) do
27
+ version VERSION_STR
28
+ banner <<-USAGE.gsub(/^\s+\|/, '')
29
+ |#{ VERSION_STR }
30
+ |
31
+ |Usage: #{ File.basename __FILE__ } [options] <query>+
32
+ |
33
+ | Options:
34
+ USAGE
35
+
36
+ Options.options_map.each do |k, v|
37
+ opt k,
38
+ v[:desc],
39
+ type: v[:type] || :boolean,
40
+ multi: v[:multi],
41
+ short: v[:short]
42
+ end
43
+
44
+ Options.options_map.each do |k, v|
45
+ opt v[:select],
46
+ "List the #{ v[:select] } that may be used with --#{ k }",
47
+ short: :none if v[:select]
48
+ end
55
49
  end
56
50
  end
57
-
58
51
  end
59
52
  end
@@ -5,23 +5,20 @@ require 'net/http'
5
5
  require 'andand'
6
6
 
7
7
  module Kat
8
-
9
8
  BASE_URL = 'http://kickass.to'
10
9
  RECENT_PATH = 'new'
11
10
  SEARCH_PATH = 'usearch'
12
11
  ADVANCED_URL = "#{ BASE_URL }/torrents/search/advanced/"
13
12
 
14
- class << self
15
- #
16
- # Convenience methods for the Search class
17
- #
18
- def search(search_term = nil, opts = {})
19
- Search.new search_term, opts
20
- end
13
+ #
14
+ # Convenience methods for the Search class
15
+ #
16
+ def self.search(search_term = nil, opts = {})
17
+ Search.new search_term, opts
18
+ end
21
19
 
22
- def quick_search(search_term = nil)
23
- Search.quick_search search_term
24
- end
20
+ def self.quick_search(search_term = nil)
21
+ Search.quick_search search_term
25
22
  end
26
23
 
27
24
  class Search
@@ -35,86 +32,35 @@ module Kat
35
32
 
36
33
  @@doc = nil
37
34
 
38
- class << self
39
-
40
- #
41
- # Kat.quick_search will do a quick search and return the results
42
- #
43
- def quick_search(search_term = nil)
44
- new(search_term).search
45
- end
46
-
47
- def field_map(type = nil)
48
- return FIELD_MAP.dup unless type
49
-
50
- FIELD_MAP.inject({}) { |hash, (k, v)|
51
- hash.tap { |h|
52
- case type
53
- when :select then h[k] = { select: v[:select], id: v[:id] || k }
54
- when :sort then h[k] = v[:sort] and h[v[:sort]] = v[:sort]
55
- h[v[:id]] = v[:sort] if v[:id]
56
- else h[k] = v[type]
57
- end if v[type]
58
- }
59
- }
60
- end
61
-
62
- def checks; field_map :check end
63
- def inputs; field_map :input end
64
- def selects; field_map :select end
65
- def sorts; field_map :sort end
66
-
67
- private
35
+ #
36
+ # Kat.quick_search will do a quick search and return the results
37
+ #
38
+ def self.quick_search(search_term = nil)
39
+ new(search_term).search
40
+ end
68
41
 
69
- #
70
- # Get a list of options for a particular selection field from the advanced search form
71
- #
72
- def field_options(label)
73
- fail 'Unknown search field' unless selects.find { |k, v| k == label.intern }
74
-
75
- url = URI(ADVANCED_URL)
76
-
77
- @@doc ||= Nokogiri::HTML(Net::HTTP.start(url.host) { |http| http.get url }.body)
78
-
79
- opts = @@doc.css('table.formtable td').find { |e|
80
- e.text[/#{ label }/i]
81
- }.next_element.first_element_child.children
82
-
83
- unless (group = opts.css('optgroup')).empty?
84
- # Categories
85
- group.inject({}) { |cat, og|
86
- cat.tap { |c|
87
- c[og.attributes['label'].value] = og.children.map { |o|
88
- o.attributes['value'].value
89
- }
90
- }
91
- }
92
- else
93
- # Times, languages, platforms
94
- opts.reject { |o| o.attributes.empty? }.inject({}) { |p, o|
95
- p.tap { |p| p[o.text] = o.attributes['value'].value }
96
- }
42
+ def self.field_map(type = nil)
43
+ return FIELD_MAP.dup unless type
44
+
45
+ FIELD_MAP.reduce({}) do |hash, (k, v)|
46
+ hash.tap do |h|
47
+ case type
48
+ when :select
49
+ h[k] = { select: v[:select], id: v[:id] || k }
50
+ when :sort
51
+ h[k] = v[:sort] && h[v[:sort]] = v[:sort]
52
+ h[v[:id]] = v[:sort] if v[:id]
53
+ else
54
+ h[k] = v[type]
55
+ end if v[type]
97
56
  end
98
- rescue => e
99
- { error: e }
100
- end
101
-
102
- #
103
- # If method is a field name in SELECT_FIELDS, fetch the list of values.
104
- #
105
- def method_missing(method, *args, &block)
106
- return super unless respond_to? method
107
- field_options selects.find { |k, v| v[:select] == method }.first
108
- end
109
-
110
- #
111
- # If method is a field name in SELECT_FIELDS, we can fetch the list of values
112
- #
113
- def respond_to_missing?(method, include_private = false)
114
- !!selects.find { |k, v| v[:select] == method } || super
115
57
  end
58
+ end
116
59
 
117
- end # class methods
60
+ def self.checks; field_map :check end
61
+ def self.inputs; field_map :input end
62
+ def self.selects; field_map :select end
63
+ def self.sorts; field_map :sort end
118
64
 
119
65
  #
120
66
  # Create a new +Kat::Search+ object to search Kickass Torrents.
@@ -139,9 +85,9 @@ module Kat
139
85
  str = [RECENT_PATH] if str[1].empty?
140
86
  str << page + 1 if page > 0
141
87
 
142
- sorts.find { |k, v| @options[:sort] && k == @options[:sort].intern }.tap { |k, v|
88
+ sorts.detect { |k, v| @options[:sort] && k == @options[:sort].intern }.tap do |k, v|
143
89
  str << (k ? "?field=#{ v }&sorder=#{ options[:asc] ? 'asc' : 'desc' }" : '')
144
- }
90
+ end
145
91
 
146
92
  str.join '/'
147
93
  end
@@ -152,13 +98,14 @@ module Kat
152
98
  # Raises ArgumentError if search_term is not a String, Symbol or Array
153
99
  #
154
100
  def query=(search_term)
155
- @search_term = case search_term
101
+ @search_term =
102
+ case search_term
156
103
  when nil, '' then []
157
104
  when String, Symbol then [search_term]
158
105
  when Array then search_term.flatten.select { |e| [String, Symbol].include? e.class }
159
- else fail ArgumentError, "search_term must be a String, Symbol or Array. " <<
106
+ else fail ArgumentError, 'search_term must be a String, Symbol or Array. ' \
160
107
  "#{ search_term.inspect } given."
161
- end
108
+ end
162
109
 
163
110
  build_query
164
111
  end
@@ -177,8 +124,8 @@ module Kat
177
124
  # Raises ArgumentError if options is not a Hash
178
125
  #
179
126
  def options=(options)
180
- fail ArgumentError, "options must be a Hash. " <<
181
- "#{ options.inspect } given." unless Hash === options
127
+ fail ArgumentError, 'options must be a Hash. ' \
128
+ "#{ options.inspect } given." unless options.is_a?(Hash)
182
129
 
183
130
  @options.merge! options
184
131
 
@@ -194,13 +141,13 @@ module Kat
194
141
  # a result set limited to the 25 results Kickass Torrents returns itself. Will
195
142
  # cache results for subsequent calls of search with the same query string.
196
143
  #
197
- def search(page = 0)
144
+ def search(page_num = 0)
198
145
  @error = nil
199
146
  @message = nil
200
147
 
201
- search_proc = -> page {
148
+ search_proc = lambda do |page|
202
149
  begin
203
- uri = URI(URI::encode(to_s page))
150
+ uri = URI(URI.encode(to_s page))
204
151
  res = Net::HTTP.get_response(uri)
205
152
  if res.code == '301'
206
153
  path = Net::HTTP::Get.new(res.header['location'])
@@ -212,7 +159,7 @@ module Kat
212
159
 
213
160
  doc = Nokogiri::HTML(res.body)
214
161
 
215
- @results[page] = doc.xpath('//table[@class="data"]/tr[position()>1]/td[1]').map { |node|
162
+ @results[page] = doc.xpath('//table[@class="data"]/tr[position()>1]/td[1]').map do |node|
216
163
  { path: node.css('a.torType').first.andand.attr('href'),
217
164
  title: node.css('a.cellMainLink').text,
218
165
  magnet: node.css('a.imagnet').first.andand.attr('href'),
@@ -221,8 +168,8 @@ module Kat
221
168
  files: (node = node.next_element).text.to_i,
222
169
  age: (node = node.next_element).text,
223
170
  seeds: (node = node.next_element).text.to_i,
224
- leeches: (node = node.next_element).text.to_i }
225
- }
171
+ leeches: node.next_element.text.to_i }
172
+ end
226
173
 
227
174
  # If we haven't previously performed a search with this query string, get the
228
175
  # number of pages from the pagination bar at the bottom of the results page.
@@ -234,16 +181,16 @@ module Kat
234
181
  rescue => e
235
182
  @error = { error: e }
236
183
  end unless @results[page] || (@pages > -1 && page >= @pages)
237
- }
184
+ end
238
185
 
239
186
  # Make sure we do a query for the first page of results before getting
240
187
  # subsequent pages in order to correctly figure out the total number of
241
188
  # pages of results.
242
- pages = (Range === page ? page.to_a : [page])
189
+ pages = (page_num.is_a?(Range) ? page_num.to_a : [page_num])
243
190
  pages.unshift(0) if @pages == -1 && !pages.include?(0)
244
191
  pages.each { |i| search_proc.call i }
245
192
 
246
- results[Range === page ? page.max : page]
193
+ results[page_num.is_a?(Range) ? page_num.max : page_num]
247
194
  end
248
195
 
249
196
  #
@@ -276,7 +223,61 @@ module Kat
276
223
  "#{ BASE_URL }/#{ query_str page }"
277
224
  end
278
225
 
279
- private
226
+ private
227
+
228
+ #
229
+ # Get a list of options for a particular selection field from the advanced search form
230
+ #
231
+ def self.field_options(label)
232
+ fail 'Unknown search field' unless selects.detect do |k, v|
233
+ k == label.intern
234
+ end
235
+
236
+ url = URI(ADVANCED_URL)
237
+
238
+ req = Net::HTTP.start(url.host) { |http| http.get url }
239
+ @@doc ||= Nokogiri::HTML(req.body)
240
+
241
+ opts = @@doc.css('table.formtable td').detect do |e|
242
+ e.text[/#{ label }/i]
243
+ end
244
+
245
+ opts = opts.next_element.first_element_child.children
246
+
247
+ if (group = opts.css('optgroup')).empty?
248
+ # Times, languages, platforms
249
+ opts.reject { |o| o.attributes.empty? }.reduce({}) do |p, o|
250
+ p.tap { |p1| p1[o.text] = o.attributes['value'].value }
251
+ end
252
+ else
253
+ # Categories
254
+ group.reduce({}) do |cat, og|
255
+ cat.tap do |c|
256
+ c[og.attributes['label'].value] = og.children.map do |o|
257
+ o.attributes['value'].value
258
+ end
259
+ end
260
+ end
261
+ end
262
+ rescue => e
263
+ { error: e }
264
+ end
265
+
266
+ #
267
+ # If method is a field name in SELECT_FIELDS, fetch the list of values.
268
+ #
269
+ def self.method_missing(method, *args, &block)
270
+ return super unless respond_to? method
271
+ field_options selects.detect { |k, v| v[:select] == method }.first
272
+ end
273
+
274
+ #
275
+ # If method is a field name in SELECT_FIELDS,
276
+ # we can fetch the list of values
277
+ #
278
+ def self.respond_to_missing?(method, include_private = false)
279
+ !!selects.detect { |k, v| v[:select] == method } || super
280
+ end
280
281
 
281
282
  #
282
283
  # Clear out the query and rebuild it from the various stored options. Also clears out the
@@ -293,17 +294,22 @@ module Kat
293
294
 
294
295
  @query += inputs.select { |k, v| @options[k] }.map { |k, v| "#{ k }:#{ @options[k] }" }
295
296
  @query += checks.select { |k, v| @options[k] }.map { |k, v| "#{ k }:1" }
296
- @query += selects.select { |k, v|
297
+
298
+ byzantine = selects.select do |k, v|
297
299
  (v[:id].to_s[/^.*_id$/] && @options[k].to_s.to_i > 0) ||
298
300
  (v[:id].to_s[/^[^_]+$/] && @options[k])
299
- }.map { |k, v| "#{ v[:id] }:#{ @options[k] }" }
301
+ end
302
+
303
+ @query += byzantine.map { |k, v| "#{ v[:id] }:#{ @options[k] }" }
300
304
  end
301
305
 
302
306
  #
303
307
  # Fetch a list of values from the results set given by name
304
308
  #
305
309
  def results_column(name)
306
- @results.compact.map { |rs| rs.map { |r| r[name] || r[name[0...-1].intern] } }.flatten
310
+ @results.compact.map do |rs|
311
+ rs.map { |r| r[name] || r[name[0...-1].intern] }
312
+ end.flatten
307
313
  end
308
314
 
309
315
  #
@@ -318,7 +324,5 @@ module Kat
318
324
  !(@results.empty? || @results.first.empty?) &&
319
325
  (@results.first.first[method] || @results.first.first[method[0..-2].intern]) || super
320
326
  end
321
-
322
327
  end
323
-
324
328
  end
@@ -1,6 +1,6 @@
1
1
  module Kat
2
2
  NAME = 'Kickass Torrents Search'
3
- VERSION = '2.0.5'
3
+ VERSION = '2.0.6'
4
4
  MALEVOLENT_DICTATOR_FOR_LIFE = 'Fission Xuiptz'
5
5
  AUTHOR = MALEVOLENT_DICTATOR_FOR_LIFE
6
6
  VERSION_STR = "#{NAME} #{VERSION} (c) 2013 #{MALEVOLENT_DICTATOR_FOR_LIFE}"
@@ -23,7 +23,7 @@ describe Kat::App do
23
23
 
24
24
  it 'creates a validation regex' do
25
25
  app.page.must_equal 0
26
- app.instance_exec {
26
+ app.instance_exec do
27
27
  @window_width = 80
28
28
 
29
29
  prev?.wont_equal true
@@ -42,8 +42,9 @@ describe Kat::App do
42
42
 
43
43
  prev?.must_equal true
44
44
  next?.wont_equal true
45
- # Skip the test if there's no results. We really only want to test in ideal
46
- # network conditions and no results here are an indication that's not the case
45
+ # Skip the test if there's no results. We really only want to test in
46
+ # ideal network conditions and no results here are an indication that's
47
+ # not the case
47
48
  validation_regex.must_equal(
48
49
  /^([pq]|[1-#{ [9, n].min }]#{
49
50
  "|1[0-#{ [9, n - 10].min }]" if n > 9
@@ -51,19 +52,19 @@ describe Kat::App do
51
52
  ) if n > 0
52
53
 
53
54
  @page = 0
54
- }
55
+ end
55
56
  end
56
57
 
57
58
  it 'deals with terminal width' do
58
- app.instance_exec {
59
+ app.instance_exec do
59
60
  set_window_width
60
- hide_info?.must_equal (@window_width < 81)
61
- }
61
+ hide_info?.must_equal(@window_width < 81)
62
+ end
62
63
  end
63
64
 
64
65
  it 'formats a list of options' do
65
- app.instance_exec {
66
- %i(category added platform language).each { |s|
66
+ app.instance_exec do
67
+ %i(category added platform language).each do |s|
67
68
  list = format_lists(s => Kat::Search.selects[s])
68
69
 
69
70
  list.must_be_instance_of Array
@@ -71,22 +72,28 @@ describe Kat::App do
71
72
 
72
73
  [0, 2, list.size - 1].each { |i| list[i].must_be_nil }
73
74
 
74
- list[1].must_equal case s
75
- when :added then 'Times'
76
- when :category then 'Categories'
77
- else s.to_s.capitalize << 's'
78
- end
79
-
80
- 3.upto(list.size - 2) { |i| list[i].must_be_instance_of String } unless s == :category
81
- 3.upto(list.size - 2) { |i| list[i].must_match(/^\s*([A-Z]+ => )?[a-z0-9-]+/) if list[i] } if s == :category
82
- }
83
- }
75
+ str = case s
76
+ when :added then 'Times'
77
+ when :category then 'Categories'
78
+ else s.to_s.capitalize << 's'
79
+ end
80
+ list[1].must_equal str
81
+
82
+ 3.upto(list.size - 2) do |i|
83
+ list[i].must_be_instance_of String
84
+ end unless s == :category
85
+
86
+ 3.upto(list.size - 2) do |i|
87
+ list[i].must_match(/^\s*([A-Z]+ => )?[a-z0-9-]+/) if list[i]
88
+ end if s == :category
89
+ end
90
+ end
84
91
  end
85
92
 
86
93
  it 'formats a list of torrents' do
87
94
  Kat::Colour.colour = false
88
95
 
89
- app.instance_exec {
96
+ app.instance_exec do
90
97
  set_window_width
91
98
  list = format_results
92
99
 
@@ -95,33 +102,33 @@ describe Kat::App do
95
102
 
96
103
  list.last.must_be_nil
97
104
 
98
- (2..list.size - 2).each { |i|
99
- list[i].must_match /^(\s[1-9]|[12][0-9])\. .*/
100
- }
101
- }
105
+ (2..list.size - 2).each do |i|
106
+ list[i].must_match(/^(\s[1-9]|[12][0-9])\. .*/)
107
+ end
108
+ end
102
109
  end
103
110
 
104
111
  it 'downloads data from a URL' do
105
112
  Kat::Colour.colour = false
106
113
 
107
- app.instance_exec {
114
+ app.instance_exec do
108
115
  s = 'foobar'
109
- result = download({ download: 'http://google.com', title: s })
116
+ result = download(download: 'http://google.com', title: s)
110
117
  result.must_equal :done
111
- File.exists?(File.expand_path "./#{ s }.torrent").must_equal true
118
+ File.exist?(File.expand_path "./#{ s }.torrent").must_equal true
112
119
  File.delete(File.expand_path "./#{ s }.torrent")
113
- }
120
+ end
114
121
  end
115
122
 
116
123
  it 'returns an error message when a download fails' do
117
124
  Kat::Colour.colour = false
118
125
 
119
- app.instance_exec {
120
- result = download({ download: 'http://foo.bar', title: 'foobar' })
126
+ app.instance_exec do
127
+ result = download(download: 'http://foo.bar', title: 'foobar')
121
128
  result.must_be_instance_of Array
122
129
  result.first.must_equal :failed
123
- result.last.must_match /^getaddrinfo/
124
- }
130
+ result.last.must_match(/^getaddrinfo/)
131
+ end
125
132
  end
126
133
  end
127
134
  end
@@ -14,18 +14,18 @@ describe Kat::Colour do
14
14
  end
15
15
 
16
16
  it 'has colour methods' do
17
- colours.each { |c|
17
+ colours.each do |c|
18
18
  ''.respond_to?(c).must_equal true
19
19
  :s.respond_to?(c).must_equal true
20
20
  [].respond_to?(c).must_equal true
21
21
  {}.respond_to?(c).wont_equal true
22
- }
22
+ end
23
23
  end
24
24
 
25
25
  it 'colours strings' do
26
26
  Kat::Colour.colour = true
27
27
 
28
- colours.each_with_index { |c, i|
28
+ colours.each_with_index do |c, i|
29
29
  str = 'foobar'
30
30
  result = "\e[0;#{ 30 + i }mfoobar\e[0m"
31
31
  intense_result = "\e[1;#{ 30 + i }mfoobar\e[0m"
@@ -42,7 +42,7 @@ describe Kat::Colour do
42
42
  str = 'foobar'
43
43
  str.send("#{ c }!", true).must_equal intense_result
44
44
  str.must_equal intense_result
45
- }
45
+ end
46
46
  end
47
47
 
48
48
  it 'uncolour strings' do
@@ -59,7 +59,7 @@ describe Kat::Colour do
59
59
  it 'colours symbols' do
60
60
  Kat::Colour.colour = true
61
61
 
62
- colours.each_with_index { |c, i|
62
+ colours.each_with_index do |c, i|
63
63
  sym = :foobar
64
64
  result = "\e[0;#{ 30 + i }mfoobar\e[0m"
65
65
  intense_result = "\e[1;#{ 30 + i }mfoobar\e[0m"
@@ -75,7 +75,7 @@ describe Kat::Colour do
75
75
 
76
76
  sym.send("#{ c }!", true).must_equal :foobar
77
77
  sym.must_equal :foobar
78
- }
78
+ end
79
79
  end
80
80
 
81
81
  it 'does not uncolour symbols' do
@@ -91,7 +91,7 @@ describe Kat::Colour do
91
91
  it 'colours arrays of strings and symbols' do
92
92
  Kat::Colour.colour = true
93
93
 
94
- colours.each_with_index { |c, i|
94
+ colours.each_with_index do |c, i|
95
95
  s = ['foobar', :foobar, nil, ['foobar', :foobar, nil]]
96
96
  t = ['foobar', :foobar, nil, ['foobar', :foobar, nil]]
97
97
  result = [
@@ -110,7 +110,7 @@ describe Kat::Colour do
110
110
  result[3][1] = :foobar
111
111
  s.send("#{ c }!").must_equal result
112
112
  s.must_equal result
113
- }
113
+ end
114
114
  end
115
115
 
116
116
  it 'uncolours arrays of strings' do
@@ -13,22 +13,22 @@ describe Kat::Options do
13
13
  end
14
14
 
15
15
  it 'has symbolised keys' do
16
- opts.keys.each { |key|
16
+ opts.keys.each do |key|
17
17
  key.must_be_instance_of Symbol
18
18
  key.wont_equal :size
19
- }
19
+ end
20
20
  end
21
21
 
22
22
  it 'has a hash for values, each with a description' do
23
- opts.values.each { |value|
23
+ opts.values.each do |value|
24
24
  value.must_be_instance_of Hash
25
25
  value.keys.must_include :desc
26
- }
26
+ end
27
27
  end
28
28
 
29
29
  it 'has symbolised keys in each value' do
30
- opts.each { |key, value|
31
- value.each { |k, v|
30
+ opts.each do |key, value|
31
+ value.each do |k, v|
32
32
  k.must_be_instance_of Symbol
33
33
  case k
34
34
  when :desc
@@ -37,12 +37,12 @@ describe Kat::Options do
37
37
  v.must_equal true
38
38
  when :short
39
39
  v.must_be_instance_of Symbol
40
- v.must_match /\A([a-z]|none)\Z/
40
+ v.must_match(/\A([a-z]|none)\Z/)
41
41
  else
42
42
  v.must_be_instance_of Symbol
43
43
  end
44
- }
45
- }
44
+ end
45
+ end
46
46
  end
47
47
  end
48
48
 
@@ -55,9 +55,9 @@ describe Kat::Options do
55
55
 
56
56
  opts.must_be_instance_of Hash
57
57
  opts.wont_be_empty
58
- opts.values.each { |value|
58
+ opts.values.each do |value|
59
59
  [[], nil, false].must_include value
60
- }
60
+ end
61
61
  end
62
62
 
63
63
  it 'returns an options hash with some options switched on' do
@@ -68,9 +68,9 @@ describe Kat::Options do
68
68
 
69
69
  opts[:or].must_equal %w(baz)
70
70
  opts[:sort].must_equal 'age'
71
- %i(colour colour_given or_given sort_given).each { |key|
71
+ %i(colour colour_given or_given sort_given).each do |key|
72
72
  opts[key].must_equal true
73
- }
73
+ end
74
74
  end
75
75
  end
76
76
 
@@ -7,7 +7,7 @@ blue_peter.go.go 1
7
7
  describe Kat::Search do
8
8
 
9
9
  let(:kat) { Kat.search 'test' }
10
- let(:kat_opts) { Kat.search 'test', { category: 'books' } }
10
+ let(:kat_opts) { Kat.search 'test', category: 'books' }
11
11
 
12
12
  describe 'basic search' do
13
13
  it 'returns a full result set' do
@@ -33,15 +33,15 @@ describe Kat::Search do
33
33
  end
34
34
 
35
35
  it 'wont respond to result fields before a search' do
36
- %i(titles files).each { |s|
36
+ %i(titles files).each do |s|
37
37
  kat.respond_to?(s).must_equal false
38
- }
38
+ end
39
39
  end
40
40
 
41
41
  it 'responds to result fields after a search' do
42
- %i(titles files).each { |s|
42
+ %i(titles files).each do |s|
43
43
  blue_peter.respond_to?(s).must_equal true
44
- }
44
+ end
45
45
  end
46
46
 
47
47
  it 'returns identical result sets' do
@@ -56,7 +56,7 @@ describe Kat::Search do
56
56
  it 'returns a valid query string with options' do
57
57
  bp = blue_peter.dup
58
58
  bp.options = { user: :foobar }
59
- bp.options.must_equal({ user: :foobar })
59
+ bp.options.must_equal(user: :foobar)
60
60
  bp.query_str(1).must_equal 'usearch/user:foobar/2/'
61
61
  bp.results.must_be_empty
62
62
  bp.pages.must_equal(-1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kat
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fission Xuiptz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-31 00:00:00.000000000 Z
11
+ date: 2014-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
112
  version: '0'
113
113
  requirements: []
114
114
  rubyforge_project:
115
- rubygems_version: 2.2.0
115
+ rubygems_version: 2.2.2
116
116
  signing_key:
117
117
  specification_version: 4
118
118
  summary: Kickass Torrents Interface