reckless 0.1.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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +44 -0
- data/LICENSE +20 -0
- data/README.md +70 -0
- data/Rakefile +10 -0
- data/lib/reckless/client.rb +38 -0
- data/lib/reckless/normalizer.rb +25 -0
- data/lib/reckless/results_parser.rb +61 -0
- data/lib/reckless/search.rb +48 -0
- data/lib/reckless/version.rb +3 -0
- data/lib/reckless.rb +18 -0
- data/reckless.gemspec +24 -0
- data/spec/fixtures/no_results.html +322 -0
- data/spec/fixtures/page_with_results.html +958 -0
- data/spec/fixtures/pagination.html +1 -0
- data/spec/fixtures/recent_arrivals.html +976 -0
- data/spec/fixtures/single_result.html +17 -0
- data/spec/reckless/client_spec.rb +55 -0
- data/spec/reckless/normalizer_spec.rb +42 -0
- data/spec/reckless/results_parser_spec.rb +54 -0
- data/spec/reckless/search_spec.rb +81 -0
- data/spec/reckless_spec.rb +22 -0
- data/spec/spec_helper.rb +15 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 86f695a892834cf350aa6981ef710d7f9ad8cb14
|
4
|
+
data.tar.gz: b82ca334df61c7de41cc3db026f460beabe1e853
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ceeeb085d34c513e223aa5361eaea13809b113d97983ad7e6ea94737c876abe3e55c70deed8ca1b34c0c69541229d615feec0e874e5de3f3794601da0006c7c
|
7
|
+
data.tar.gz: ebb4f7980ad86c54b00f3bad118a498a164eeb3f91ae23ece37356307af360d6b3e1cd798e36ad0278cc8d434445dbc1efc9087f912795a54c6e082d251a7315
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
reckless (0.1.0)
|
5
|
+
faraday (~> 0.7)
|
6
|
+
json (~> 1.8)
|
7
|
+
nokogiri (~> 1.5)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
diff-lcs (1.2.5)
|
13
|
+
docile (1.1.0)
|
14
|
+
faraday (0.8.9)
|
15
|
+
multipart-post (~> 1.2.0)
|
16
|
+
json (1.8.1)
|
17
|
+
mini_portile (0.5.1)
|
18
|
+
multi_json (1.8.2)
|
19
|
+
multipart-post (1.2.0)
|
20
|
+
nokogiri (1.6.0)
|
21
|
+
mini_portile (~> 0.5.0)
|
22
|
+
rake (10.1.1)
|
23
|
+
rspec (2.14.1)
|
24
|
+
rspec-core (~> 2.14.0)
|
25
|
+
rspec-expectations (~> 2.14.0)
|
26
|
+
rspec-mocks (~> 2.14.0)
|
27
|
+
rspec-core (2.14.7)
|
28
|
+
rspec-expectations (2.14.4)
|
29
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
30
|
+
rspec-mocks (2.14.4)
|
31
|
+
simplecov (0.8.2)
|
32
|
+
docile (~> 1.1.0)
|
33
|
+
multi_json
|
34
|
+
simplecov-html (~> 0.8.0)
|
35
|
+
simplecov-html (0.8.0)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
rake
|
42
|
+
reckless!
|
43
|
+
rspec (~> 2.13)
|
44
|
+
simplecov (~> 0.7)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Dan Sosedoff, <dan.sosedoff@gmail.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Reckless
|
2
|
+
|
3
|
+
Ruby client to Chicago's records store Reckless.com
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add it to your Gemfile:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem "reckless"
|
11
|
+
```
|
12
|
+
|
13
|
+
Or install it manually:
|
14
|
+
|
15
|
+
```
|
16
|
+
gem install reckless
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
See examples:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require "reckless"
|
25
|
+
|
26
|
+
# Search for records
|
27
|
+
search = Reckless.search("Nirvana")
|
28
|
+
search = Reckless.recent_arrivals
|
29
|
+
|
30
|
+
# Get search details
|
31
|
+
search.url # http://www.reckless.com/index.phpkeywords=&format=&cond=&store=&is_search=true&srch=Search
|
32
|
+
search.total_pages # 15
|
33
|
+
search.total_results # 450
|
34
|
+
search.page # 1
|
35
|
+
search.results # returns an array of results
|
36
|
+
```
|
37
|
+
|
38
|
+
Each search result is a simple hash:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
{
|
42
|
+
:artist => "13th Floor Elevators",
|
43
|
+
:title => "Easter Everywhere (180g, Reissue)",
|
44
|
+
:label => "International Artist",
|
45
|
+
:price => 11.99,
|
46
|
+
:type => "New LP",
|
47
|
+
:condition => "Good Condition",
|
48
|
+
:store => "Milwaukee Ave"
|
49
|
+
}
|
50
|
+
```
|
51
|
+
|
52
|
+
### Search options
|
53
|
+
|
54
|
+
- `page` - Specify a page to fetch. Defaults to `1`
|
55
|
+
- `format` - Choose record format: `LP`, `CD`, `DVD`. Defaults to `LP`
|
56
|
+
- `store` - Choose store: `Broadway`, `Milwaukee` or `Loop`. Does not have a default.
|
57
|
+
|
58
|
+
## Testing
|
59
|
+
|
60
|
+
Execute test suite:
|
61
|
+
|
62
|
+
```
|
63
|
+
bundle exec rake test
|
64
|
+
```
|
65
|
+
|
66
|
+
### License
|
67
|
+
|
68
|
+
The MIT License (MIT)
|
69
|
+
|
70
|
+
Copyright (c) 2014 Dan Sosedoff, <dan.sosedoff@gmail.com>
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Reckless
|
2
|
+
class Client
|
3
|
+
BASE_URL = "http://www.reckless.com"
|
4
|
+
|
5
|
+
def search(keywords = "", options = {})
|
6
|
+
url = "#{BASE_URL}/index.php"
|
7
|
+
|
8
|
+
params = search_options(options).merge(
|
9
|
+
keywords: keywords,
|
10
|
+
is_search: true
|
11
|
+
)
|
12
|
+
|
13
|
+
Reckless::Search.new(url, params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def recent_arrivals(options = {})
|
17
|
+
url = "#{BASE_URL}/new_arrivals.php"
|
18
|
+
|
19
|
+
params = search_options(options).merge(
|
20
|
+
period: options[:period] || 1,
|
21
|
+
style: options[:style] || 0
|
22
|
+
)
|
23
|
+
|
24
|
+
Reckless::Search.new(url, params)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def search_options(options)
|
30
|
+
{
|
31
|
+
page: options[:page] || 1,
|
32
|
+
format: options[:format] || "LP",
|
33
|
+
cond: options[:cond] || "",
|
34
|
+
store: options[:store] || ""
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Reckless
|
2
|
+
module Normalizer
|
3
|
+
#
|
4
|
+
# Transform text from all caps to normal capitalized words
|
5
|
+
# Example: SOME GREAT BAND => Some Great Band
|
6
|
+
#
|
7
|
+
def normalize_text(text)
|
8
|
+
text.downcase.split.map(&:capitalize).join(" ")
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# Transform artist name to normal representation
|
13
|
+
# Example: Beck, Jeff => Jeff Beck
|
14
|
+
#
|
15
|
+
def normalize_artist(text)
|
16
|
+
if text =~ /[\d]+,[\d]+/
|
17
|
+
text
|
18
|
+
else
|
19
|
+
text.gsub(/(([\w]+),\s?([\w]+))/) do |m|
|
20
|
+
m = "#{$3} #{$2}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Reckless
|
2
|
+
class ResultsParser
|
3
|
+
include Reckless::Normalizer
|
4
|
+
|
5
|
+
def initialize(body)
|
6
|
+
@document = Nokogiri::HTML(body)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
find_result_elements.map do |node|
|
11
|
+
{
|
12
|
+
artist: normalize_artist(fetch_text(node, "artist")),
|
13
|
+
title: fetch_text(node, "title"),
|
14
|
+
label: fetch_text(node, "label"),
|
15
|
+
price: fetch_price(node.css("td").last),
|
16
|
+
type: fetch_type(node),
|
17
|
+
condition: fetch_condition(node),
|
18
|
+
store: fetch_store(node)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_result_elements
|
26
|
+
@document.css("table.item")
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch_price(node)
|
30
|
+
node.to_s.scan(/\$([\d\.]+)/).flatten.first.to_f
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_text(node, name)
|
34
|
+
element = node.css("span.#{name}").first
|
35
|
+
element ? normalize_text(element.children.first.text.strip) : ""
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_type(node)
|
39
|
+
element = node.css("tr:last-child td b").first
|
40
|
+
|
41
|
+
if element
|
42
|
+
text = element.children.first.text.strip
|
43
|
+
text.scan(/^([\w]+ [\w]+)/).flatten.first
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_condition(node)
|
48
|
+
element = node.css("tr:last-child td b").first
|
49
|
+
|
50
|
+
if element
|
51
|
+
text = element.children.first.text.gsub("Condition", "")
|
52
|
+
text.scan(/\(([\w\s]+)\)/).flatten.first.to_s.strip
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_store(node)
|
57
|
+
match = node.to_s.scan(/<span style="color:#a33; font-weight: bold;">([\w\s]+).<\/span>/)
|
58
|
+
match.flatten.first
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Reckless
|
2
|
+
class Search
|
3
|
+
attr_reader :url, :params
|
4
|
+
attr_reader :page, :total_results, :total_pages
|
5
|
+
attr_reader :results
|
6
|
+
|
7
|
+
def initialize(url, params = {})
|
8
|
+
@url = url
|
9
|
+
@params = params
|
10
|
+
@page = params[:page] || 1
|
11
|
+
@total_results = 0
|
12
|
+
@total_pages = 0
|
13
|
+
@results = []
|
14
|
+
|
15
|
+
fetch_page_contents
|
16
|
+
parse_results_count
|
17
|
+
parse_pagination
|
18
|
+
parse_results
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_page_contents
|
24
|
+
@body = Faraday.get(@url, @params).body
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_results_count
|
28
|
+
matches = @body.scan(/<b>([\d]+) found.<\/b>/).flatten
|
29
|
+
|
30
|
+
unless matches.empty?
|
31
|
+
@total_results = matches.first.to_i
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_pagination
|
36
|
+
matches = @body.scan(/This is page ([\d]+) of ([\d]+)/).flatten
|
37
|
+
|
38
|
+
unless matches.empty?
|
39
|
+
@page = matches[0].to_i
|
40
|
+
@total_pages = matches[1].to_i
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_results
|
45
|
+
@results = Reckless::ResultsParser.new(@body).parse
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/reckless.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "nokogiri"
|
2
|
+
require "faraday"
|
3
|
+
|
4
|
+
require "reckless/client"
|
5
|
+
require "reckless/search"
|
6
|
+
require "reckless/normalizer"
|
7
|
+
require "reckless/results_parser"
|
8
|
+
require "reckless/version"
|
9
|
+
|
10
|
+
module Reckless
|
11
|
+
def self.search(keywords = "", options = {})
|
12
|
+
Reckless::Client.new.search(keywords, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.recent_arrivals(options = {})
|
16
|
+
Reckless::Client.new.recent_arrivals(options)
|
17
|
+
end
|
18
|
+
end
|
data/reckless.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path("../lib/reckless/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "reckless"
|
5
|
+
spec.version = Reckless::VERSION
|
6
|
+
spec.summary = "Client for Reckless.com site"
|
7
|
+
spec.description = "Client for Reckless.com site"
|
8
|
+
spec.homepage = "http://github.com/sosedoff/reckless"
|
9
|
+
spec.authors = ["Dan Sosedoff"]
|
10
|
+
spec.email = ["dan.sosedoff@gmail.com"]
|
11
|
+
|
12
|
+
spec.add_dependency "json", "~> 1.8"
|
13
|
+
spec.add_dependency "faraday", "~> 0.7"
|
14
|
+
spec.add_dependency "nokogiri", "~> 1.5"
|
15
|
+
|
16
|
+
spec.add_development_dependency "rake"
|
17
|
+
spec.add_development_dependency "rspec", "~> 2.13"
|
18
|
+
spec.add_development_dependency "simplecov", "~> 0.7"
|
19
|
+
|
20
|
+
spec.files = `git ls-files`.split("\n")
|
21
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
end
|