kat 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/kat.gemspec +3 -2
- data/lib/kat/app.rb +34 -27
- data/lib/kat/field_map.rb +23 -112
- data/lib/kat/search.rb +4 -3
- data/lib/kat/version.rb +1 -1
- data/test/kat/test_app.rb +14 -7
- data/test/kat/test_colour.rb +6 -0
- metadata +17 -5
- data/test/kat/test_field_map.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a787a58459b2ef07e56e8ea0dcbd2303b88d1a2e
|
4
|
+
data.tar.gz: 8044118ce50282384b57378d82c117b633a1f372
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47b3095fe9256337fe71d7248d0f36d3cee42f78794d66da09bedea20bb8120efe9bed80bfa24f45469b7bf25361ee236155f4d36bab343b8b4691640aec14ce
|
7
|
+
data.tar.gz: 5bfe7bd174afabe76d089625bc35054da1be957781061932db125c1e6fa74fc84be2fc22b7e3323cc8570d6f1d9f3fd99496b6e8855d99f203834bea89820e47
|
data/README.md
CHANGED
@@ -22,11 +22,11 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
### Quick search
|
24
24
|
|
25
|
-
Kat.
|
25
|
+
Kat.quick_search('game of thrones')
|
26
26
|
|
27
27
|
### Search for torrents
|
28
28
|
|
29
|
-
kat = Kat.
|
29
|
+
kat = Kat.search('game of thrones', { :category => 'tv' })
|
30
30
|
kat.search
|
31
31
|
|
32
32
|
### Specifying pages
|
data/kat.gemspec
CHANGED
@@ -16,11 +16,12 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.description = 'A Ruby interface to Kickass Torrents'
|
17
17
|
|
18
18
|
s.files = `git ls-files`.split $/
|
19
|
-
s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f }
|
19
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename f }
|
20
20
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
21
|
-
s.require_paths = [
|
21
|
+
s.require_paths = ['lib']
|
22
22
|
|
23
23
|
s.add_runtime_dependency 'nokogiri', '~> 1.6'
|
24
24
|
s.add_runtime_dependency 'highline', '~> 1.6'
|
25
25
|
s.add_runtime_dependency 'trollop', '~> 2.0'
|
26
|
+
s.add_runtime_dependency 'andand', '~> 1.3'
|
26
27
|
end
|
data/lib/kat/app.rb
CHANGED
@@ -18,6 +18,7 @@ module Kat
|
|
18
18
|
|
19
19
|
class App
|
20
20
|
MIN_WIDTH = 80
|
21
|
+
CONFIG = File.join ENV['HOME'], '.katrc'
|
21
22
|
|
22
23
|
# The current page number (0-based)
|
23
24
|
attr_accessor :page
|
@@ -46,20 +47,22 @@ module Kat
|
|
46
47
|
# Initialise the app's options
|
47
48
|
#
|
48
49
|
def init_options(args = nil)
|
49
|
-
@options = {}
|
50
50
|
@args = case args
|
51
51
|
when nil then []
|
52
52
|
when String then args.split
|
53
53
|
else args
|
54
54
|
end
|
55
55
|
|
56
|
-
load_config
|
56
|
+
@options = load_config || {}
|
57
57
|
|
58
58
|
Kat.options(@args).tap { |o|
|
59
59
|
@options.merge!(o) { |k, ov, nv| o["#{ k }_given".intern] ? nv : ov }
|
60
60
|
}
|
61
61
|
|
62
62
|
Kat::Colour.colour = @options[:colour]
|
63
|
+
rescue NoMethodError => e
|
64
|
+
@options = {}
|
65
|
+
warn "Wrong config file format: #{ e }"
|
63
66
|
end
|
64
67
|
|
65
68
|
#
|
@@ -144,28 +147,26 @@ module Kat
|
|
144
147
|
}
|
145
148
|
].map { |w| Thread.new { w.call } }.each(&:join)
|
146
149
|
|
147
|
-
|
148
|
-
puts "\rNo results ".red
|
149
|
-
puts @kat.error[:error] if @kat.error
|
150
|
-
return false
|
151
|
-
end
|
150
|
+
puts (res = format_results)
|
152
151
|
|
153
|
-
|
152
|
+
if res.size > 1
|
153
|
+
case (answer = prompt)
|
154
|
+
when 'i' then @show_info = !@show_info
|
155
|
+
when 'n' then @page += 1 if next?
|
156
|
+
when 'p' then @page -= 1 if prev?
|
157
|
+
when 'q' then return false
|
158
|
+
else
|
159
|
+
if (1..@kat.results[@page].size).include? (answer = answer.to_i)
|
160
|
+
print "\nDownloading".yellow <<
|
161
|
+
": #{ @kat.results[@page][answer - 1][:title] }... "
|
162
|
+
puts download @kat.results[@page][answer - 1]
|
163
|
+
end
|
164
|
+
end
|
154
165
|
|
155
|
-
|
156
|
-
when 'i' then @show_info = !@show_info
|
157
|
-
when 'n' then @page += 1 if next?
|
158
|
-
when 'p' then @page -= 1 if prev?
|
159
|
-
when 'q' then return false
|
166
|
+
true
|
160
167
|
else
|
161
|
-
|
162
|
-
print "\nDownloading".yellow <<
|
163
|
-
": #{ @kat.results[@page][answer - 1][:title] }... "
|
164
|
-
puts download @kat.results[@page][answer - 1]
|
165
|
-
end
|
168
|
+
false
|
166
169
|
end
|
167
|
-
|
168
|
-
true
|
169
170
|
end
|
170
171
|
|
171
172
|
#
|
@@ -195,6 +196,12 @@ module Kat
|
|
195
196
|
def format_results
|
196
197
|
main_width = @window_width - (!hide_info? || @show_info ? 42 : 4)
|
197
198
|
|
199
|
+
if @kat.error
|
200
|
+
return ["\rConnection failed".red]
|
201
|
+
elsif !@kat.results[@page]
|
202
|
+
return ["\rNo results ".red]
|
203
|
+
end
|
204
|
+
|
198
205
|
buf = ["\r%-#{ main_width + 5 }s#{ ' Size Age Seeds Leeches' if !hide_info? || @show_info }" %
|
199
206
|
"Page #{ page + 1 } of #{ @kat.pages }", nil].yellow!
|
200
207
|
|
@@ -242,7 +249,9 @@ module Kat
|
|
242
249
|
# Download the torrent to either the output directory or the working directory
|
243
250
|
#
|
244
251
|
def download(torrent)
|
245
|
-
|
252
|
+
return [:failed, 'no download link available'].red unless torrent[:download]
|
253
|
+
|
254
|
+
uri = URI(URI.encode torrent[:download])
|
246
255
|
uri.query = nil
|
247
256
|
file = "#{ @options[:output] || '.' }/" <<
|
248
257
|
"#{ torrent[:title].tr(' ', ?.).gsub(/[^a-z0-9()_.-]/i, '') }.torrent"
|
@@ -259,16 +268,14 @@ module Kat
|
|
259
268
|
end
|
260
269
|
|
261
270
|
#
|
262
|
-
# Load options from
|
271
|
+
# Load options from CONFIG if it exists
|
263
272
|
#
|
264
273
|
def load_config
|
265
|
-
|
266
|
-
|
267
|
-
@options = (symbolise = -> h {
|
274
|
+
(symbolise = -> h {
|
268
275
|
Hash === h ? Hash[h.map { |k, v| [k.intern, symbolise[v]] }] : h
|
269
|
-
})[YAML.load_file
|
276
|
+
})[YAML.load_file CONFIG] if File.readable? CONFIG
|
270
277
|
rescue => e
|
271
|
-
warn "Failed to load #{
|
278
|
+
warn "Failed to load #{ CONFIG }: #{ e }"
|
272
279
|
end
|
273
280
|
|
274
281
|
end
|
data/lib/kat/field_map.rb
CHANGED
@@ -1,122 +1,33 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
1
|
module Kat
|
4
2
|
|
5
|
-
FIELD_MAP =
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
else h
|
10
|
-
end
|
11
|
-
})[YAML.load(<<-FIELD_MAP
|
12
|
-
---
|
13
|
-
exact:
|
14
|
-
type: string
|
15
|
-
desc: Exact phrase
|
16
|
-
|
17
|
-
or:
|
18
|
-
type: string
|
19
|
-
desc: Optional words
|
20
|
-
multi: true
|
21
|
-
|
22
|
-
without:
|
23
|
-
type: string
|
24
|
-
desc: Without this word
|
25
|
-
multi: true
|
26
|
-
|
27
|
-
sort:
|
28
|
-
type: string
|
29
|
-
desc: Sort field (size, files, added, seeds, leeches)
|
30
|
-
|
31
|
-
asc:
|
32
|
-
desc: Ascending sort order (descending is default)
|
33
|
-
|
34
|
-
category:
|
35
|
-
select: categories
|
36
|
-
type: string
|
37
|
-
desc: Category
|
38
|
-
short: c
|
39
|
-
|
40
|
-
added:
|
41
|
-
select: times
|
42
|
-
sort: time_add
|
43
|
-
type: string
|
44
|
-
desc: Age of the torrent
|
45
|
-
id: age
|
46
|
-
short: a
|
47
|
-
|
48
|
-
size:
|
49
|
-
sort: size
|
50
|
-
|
51
|
-
user:
|
52
|
-
input: true
|
53
|
-
type: string
|
54
|
-
desc: Uploader
|
55
|
-
|
56
|
-
files:
|
57
|
-
input: true
|
58
|
-
sort: files_count
|
59
|
-
type: int
|
60
|
-
desc: Number of files
|
61
|
-
|
62
|
-
imdb:
|
63
|
-
input: true
|
64
|
-
type: int
|
65
|
-
desc: IMDB ID
|
66
|
-
|
67
|
-
seeds:
|
68
|
-
input: true
|
69
|
-
sort: seeders
|
70
|
-
type: int
|
71
|
-
desc: Min no of seeders
|
72
|
-
short: s
|
73
|
-
|
74
|
-
leeches:
|
75
|
-
sort: leechers
|
76
|
-
|
77
|
-
season:
|
78
|
-
input: true
|
79
|
-
type: int
|
80
|
-
desc: Television season
|
81
|
-
|
82
|
-
episode:
|
83
|
-
input: true
|
84
|
-
type: int
|
85
|
-
desc: Television episode
|
86
|
-
short: e
|
3
|
+
FIELD_MAP = {
|
4
|
+
exact: { type: :string, desc: 'Exact phrase' },
|
5
|
+
or: { type: :string, desc: 'Optional words', multi: true },
|
6
|
+
without: { type: :string, desc: 'Without this word', multi: true },
|
87
7
|
|
88
|
-
|
89
|
-
|
90
|
-
type: int
|
91
|
-
desc: Language
|
92
|
-
id: lang_id
|
8
|
+
sort: { type: :string, desc: 'Sort field (size, files, added, seeds, leeches)' },
|
9
|
+
asc: { desc: 'Ascending sort order (descending is default)' },
|
93
10
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
desc: Game platform
|
98
|
-
id: platform_id
|
11
|
+
category: { type: :string, desc: 'Category', select: :categories, short: :c },
|
12
|
+
added: { type: :string, desc: 'Age of the torrent', select: :times, short: :a, sort: :time_add, id: :age },
|
13
|
+
size: { sort: :size },
|
99
14
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
short:
|
15
|
+
user: { type: :string, desc: 'Uploader', input: true },
|
16
|
+
files: { type: :int, desc: 'Number of files', input: true, sort: :files_count },
|
17
|
+
imdb: { type: :int, desc: 'IMDB ID', input: true },
|
18
|
+
seeds: { type: :int, desc: 'Min no of seeders', input: true, sort: :seeders, short: :s },
|
19
|
+
leeches: { sort: :leechers },
|
20
|
+
season: { type: :int, desc: 'Television season', input: true },
|
21
|
+
episode: { type: :int, desc: 'Television episode', input: true, short: :e },
|
104
22
|
|
105
|
-
|
106
|
-
|
107
|
-
desc: Verified torrent
|
108
|
-
short: none
|
23
|
+
language: { type: :int, desc: 'Language', select: :languages, id: :lang_id },
|
24
|
+
platform: { type: :int, desc: 'Game platform', select: :platforms, id: :platform_id },
|
109
25
|
|
110
|
-
|
111
|
-
|
112
|
-
desc: Directory to save torrents in
|
113
|
-
short: o
|
26
|
+
safe: { desc: 'Family safe filter', check: true, short: :none},
|
27
|
+
verified: { desc: 'Verified torrent', check: true, short: :none },
|
114
28
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
short: none
|
119
|
-
FIELD_MAP
|
120
|
-
)].freeze
|
29
|
+
output: { type: :string, desc: 'Directory to save torrents in', short: :o },
|
30
|
+
colour: { desc: 'Output with colour', short: :none }
|
31
|
+
}.freeze
|
121
32
|
|
122
33
|
end
|
data/lib/kat/search.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/field_map'
|
2
2
|
require 'nokogiri'
|
3
3
|
require 'net/http'
|
4
|
+
require 'andand'
|
4
5
|
|
5
6
|
module Kat
|
6
7
|
|
@@ -197,10 +198,10 @@ module Kat
|
|
197
198
|
doc = Nokogiri::HTML(res.body)
|
198
199
|
|
199
200
|
@results[page] = doc.css('td.torrentnameCell').map { |node|
|
200
|
-
{ path: node.css('a.normalgrey').first.
|
201
|
+
{ path: node.css('a.normalgrey').first.andand.attr('href'),
|
201
202
|
title: node.css('a.normalgrey').text,
|
202
|
-
magnet: node.css('a.imagnet').first.
|
203
|
-
download: node.css('a.idownload').last.
|
203
|
+
magnet: node.css('a.imagnet').first.andand.attr('href'),
|
204
|
+
download: node.css('a.idownload').last.andand.attr('href'),
|
204
205
|
size: (node = node.next_element).text,
|
205
206
|
files: (node = node.next_element).text.to_i,
|
206
207
|
age: (node = node.next_element).text,
|
data/lib/kat/version.rb
CHANGED
data/test/kat/test_app.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require File.dirname(__FILE__) + '/../../lib/kat/app'
|
3
3
|
|
4
|
-
app = Kat::App.new %w(
|
4
|
+
app = Kat::App.new %w(aliens -c movies -o .)
|
5
5
|
app.kat.go(1).go(app.kat.pages - 1)
|
6
6
|
|
7
7
|
describe Kat::App do
|
@@ -14,7 +14,7 @@ describe Kat::App do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it 're-initialises options' do
|
17
|
-
k = Kat::App.new %w(
|
17
|
+
k = Kat::App.new %w(aliens)
|
18
18
|
k.init_options %w(bible -c books)
|
19
19
|
k.options.must_be_instance_of Hash
|
20
20
|
k.options[:category].must_equal 'books'
|
@@ -42,11 +42,13 @@ 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
47
|
validation_regex.must_equal(
|
46
48
|
/^([pq]|[1-#{ [9, n].min }]#{
|
47
|
-
|
48
|
-
|
49
|
-
)
|
49
|
+
"|1[0-#{ [9, n - 10].min }]" if n > 9
|
50
|
+
}#{ "|2[0-#{ n - 20 }]" if n > 19 })$/
|
51
|
+
) if n > 0
|
50
52
|
|
51
53
|
@page = 0
|
52
54
|
}
|
@@ -83,6 +85,7 @@ describe Kat::App do
|
|
83
85
|
|
84
86
|
it 'formats a list of torrents' do
|
85
87
|
Kat::Colour.colour = false
|
88
|
+
|
86
89
|
app.instance_exec {
|
87
90
|
set_window_width
|
88
91
|
list = format_results
|
@@ -90,8 +93,10 @@ describe Kat::App do
|
|
90
93
|
list.must_be_instance_of Array
|
91
94
|
list.wont_be_empty
|
92
95
|
|
93
|
-
list
|
94
|
-
|
96
|
+
list[1].must_be_nil
|
97
|
+
list.last.must_be_nil
|
98
|
+
|
99
|
+
(2..list.size - 2).each { |i|
|
95
100
|
list[i].must_match /^(\s[1-9]|[12][0-9])\. .*/
|
96
101
|
}
|
97
102
|
}
|
@@ -99,6 +104,7 @@ describe Kat::App do
|
|
99
104
|
|
100
105
|
it 'downloads data from a URL' do
|
101
106
|
Kat::Colour.colour = false
|
107
|
+
|
102
108
|
app.instance_exec {
|
103
109
|
s = 'foobar'
|
104
110
|
result = download({ download: 'http://google.com', title: s })
|
@@ -110,6 +116,7 @@ describe Kat::App do
|
|
110
116
|
|
111
117
|
it 'returns an error message when a download fails' do
|
112
118
|
Kat::Colour.colour = false
|
119
|
+
|
113
120
|
app.instance_exec {
|
114
121
|
result = download({ download: 'http://foo.bar', title: 'foobar' })
|
115
122
|
result.must_be_instance_of Array
|
data/test/kat/test_colour.rb
CHANGED
@@ -23,6 +23,8 @@ describe Kat::Colour do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'colours strings' do
|
26
|
+
Kat::Colour.colour = true
|
27
|
+
|
26
28
|
colours.each_with_index { |c, i|
|
27
29
|
str = 'foobar'
|
28
30
|
result = "\e[0;#{ 30 + i }mfoobar\e[0m"
|
@@ -55,6 +57,8 @@ describe Kat::Colour do
|
|
55
57
|
end
|
56
58
|
|
57
59
|
it 'colours symbols' do
|
60
|
+
Kat::Colour.colour = true
|
61
|
+
|
58
62
|
colours.each_with_index { |c, i|
|
59
63
|
sym = :foobar
|
60
64
|
result = "\e[0;#{ 30 + i }mfoobar\e[0m"
|
@@ -85,6 +89,8 @@ describe Kat::Colour do
|
|
85
89
|
end
|
86
90
|
|
87
91
|
it 'colours arrays of strings and symbols' do
|
92
|
+
Kat::Colour.colour = true
|
93
|
+
|
88
94
|
colours.each_with_index { |c, i|
|
89
95
|
s = ['foobar', :foobar, nil, ['foobar', :foobar, nil]]
|
90
96
|
t = ['foobar', :foobar, nil, ['foobar', :foobar, nil]]
|
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.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fission Xuiptz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: andand
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
55
69
|
description: A Ruby interface to Kickass Torrents
|
56
70
|
email: fissionxuiptz@softwaremojo.com
|
57
71
|
executables:
|
@@ -76,7 +90,6 @@ files:
|
|
76
90
|
- lib/kat/version.rb
|
77
91
|
- test/kat/test_app.rb
|
78
92
|
- test/kat/test_colour.rb
|
79
|
-
- test/kat/test_field_map.rb
|
80
93
|
- test/kat/test_options.rb
|
81
94
|
- test/kat/test_search.rb
|
82
95
|
homepage: http://github.com/fissionxuiptz/kat
|
@@ -99,13 +112,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
112
|
version: '0'
|
100
113
|
requirements: []
|
101
114
|
rubyforge_project:
|
102
|
-
rubygems_version: 2.0.
|
115
|
+
rubygems_version: 2.0.14
|
103
116
|
signing_key:
|
104
117
|
specification_version: 4
|
105
118
|
summary: Kickass Torrents Interface
|
106
119
|
test_files:
|
107
120
|
- test/kat/test_app.rb
|
108
121
|
- test/kat/test_colour.rb
|
109
|
-
- test/kat/test_field_map.rb
|
110
122
|
- test/kat/test_options.rb
|
111
123
|
- test/kat/test_search.rb
|
data/test/kat/test_field_map.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require File.dirname(__FILE__) + '/../../lib/kat/field_map'
|
3
|
-
|
4
|
-
describe Kat do
|
5
|
-
describe 'field map' do
|
6
|
-
let(:f) { Kat::FIELD_MAP }
|
7
|
-
|
8
|
-
it 'is a hash' do
|
9
|
-
f.must_be_instance_of Hash
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'has symbolised keys' do
|
13
|
-
f.keys.wont_be_empty
|
14
|
-
f.keys.each { |key|
|
15
|
-
key.must_be_instance_of Symbol
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'has a hash for values' do
|
20
|
-
f.values.wont_be_empty
|
21
|
-
f.values.each { |value|
|
22
|
-
value.must_be_instance_of Hash
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'has symbolised keys in each value' do
|
27
|
-
f.values.each { |value|
|
28
|
-
value.keys.wont_be_empty
|
29
|
-
value.keys.each { |key|
|
30
|
-
key.must_be_instance_of Symbol
|
31
|
-
case key
|
32
|
-
when :desc
|
33
|
-
value[key].must_be_instance_of String
|
34
|
-
when :multi, :check, :input
|
35
|
-
value[key].must_equal true
|
36
|
-
when :short
|
37
|
-
value[key].must_be_instance_of Symbol
|
38
|
-
value[key].must_match /\A([a-z]|none)\Z/
|
39
|
-
else
|
40
|
-
value[key].must_be_instance_of Symbol
|
41
|
-
end
|
42
|
-
}
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|