metal_archives 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE +0 -0
- data/README.md +88 -0
- data/Rakefile +14 -0
- data/lib/metal_archives.rb +22 -0
- data/lib/metal_archives/configuration.rb +81 -0
- data/lib/metal_archives/error.rb +35 -0
- data/lib/metal_archives/http_client.rb +75 -0
- data/lib/metal_archives/models/artist.rb +216 -0
- data/lib/metal_archives/models/band.rb +291 -0
- data/lib/metal_archives/models/base_model.rb +153 -0
- data/lib/metal_archives/models/label.rb +112 -0
- data/lib/metal_archives/models/range.rb +58 -0
- data/lib/metal_archives/parsers/artist.rb +103 -0
- data/lib/metal_archives/parsers/band.rb +160 -0
- data/lib/metal_archives/parsers/label.rb +68 -0
- data/lib/metal_archives/parsers/parser_helper.rb +79 -0
- data/lib/metal_archives/version.rb +6 -0
- data/metal_archives.gemspec +28 -0
- data/test/base_model_test.rb +87 -0
- data/test/configuration_test.rb +57 -0
- data/test/parser_helper_test.rb +37 -0
- data/test/property/artist_property_test.rb +43 -0
- data/test/property/band_property_test.rb +94 -0
- data/test/query/artist_query_test.rb +44 -0
- data/test/query/band_query_test.rb +84 -0
- data/test/range_test.rb +41 -0
- data/test/test_helper.rb +26 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 07ba21e5ea2f91a35d841543732f2cad9faf3943
|
4
|
+
data.tar.gz: fd48c5f0ae010184778ed9d6137004684beb1c64
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed2595557edfcb18ff9d74822039e6d8d846e9d9348ba73ffd41dd1d4bc8d06dddd48472ab833c2afd17cd120b3cd4e63c1ddb7f081a780a95b87c8bb4279149
|
7
|
+
data.tar.gz: 87d80f9d69daa76339513028aba3c6d0179b130847c9e1314ce40c9fe7a1a8f993370dd488e424fb1734bde1dc0fff48cce0540595e5fc8d546059803b01f638
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Metal Archives Web Service Wrapper
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
```shell
|
6
|
+
$ gem install metal_archives
|
7
|
+
```
|
8
|
+
|
9
|
+
or add it to your Gemfile
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'metal_archives'
|
13
|
+
```
|
14
|
+
|
15
|
+
```shell
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'active_support/cache'
|
23
|
+
|
24
|
+
MetalArchives.configure do |c|
|
25
|
+
# Application identity (required)
|
26
|
+
c.app_name = "My App"
|
27
|
+
c.app_version = "1.0"
|
28
|
+
c.app_contact = "support@mymusicapp.com"
|
29
|
+
|
30
|
+
# Cache config (optional)
|
31
|
+
c.enable_cache = true
|
32
|
+
c.cache_store = ActiveSupport::Cache.lookup_store(:file_store, '/tmp/metal_archives-cache')
|
33
|
+
|
34
|
+
# Request throttling (optional, overrides defaults)
|
35
|
+
c.request_rate = 1
|
36
|
+
c.request_timeout = 3
|
37
|
+
|
38
|
+
# Print debugging information
|
39
|
+
c.debug = false
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
require 'metal_archives'
|
47
|
+
|
48
|
+
# Search for bands
|
49
|
+
@alquimia_list = MetalArchives::Band.search('Alquimia')
|
50
|
+
|
51
|
+
# Find bands by name
|
52
|
+
@iron_maiden = MetalArchives::Band.find_by(:name => 'Iron Maiden')
|
53
|
+
|
54
|
+
# Find bands by attributes
|
55
|
+
require 'countries'
|
56
|
+
|
57
|
+
@bands_in_belgium = MetalArchives::Band.search_by :country => ISO3166::Country['BE']
|
58
|
+
@bands_formed_in_1990 = MetalArchives::Band.search_by :year => Range.new(Date.new(1990))
|
59
|
+
|
60
|
+
# Metal Archives' usual tips apply
|
61
|
+
|
62
|
+
@bands_containing_hell = MetalArchives::Band.search_by :name => '*hell*'
|
63
|
+
@non_melodic_death_bands = MetalArchives::Band.search_by :genre => 'death -melodic'
|
64
|
+
```
|
65
|
+
|
66
|
+
Refer to the model's RDoc documentation for full documentation.
|
67
|
+
|
68
|
+
## Debugging
|
69
|
+
|
70
|
+
Turn on `debug` in the configuration block to enable logging HTTP requests and responses.
|
71
|
+
|
72
|
+
```
|
73
|
+
$ irb -r metal_archives
|
74
|
+
```
|
75
|
+
|
76
|
+
## Testing
|
77
|
+
```
|
78
|
+
$ bundle exec rake test
|
79
|
+
```
|
80
|
+
|
81
|
+
## Documentation
|
82
|
+
```
|
83
|
+
$ bundle exec rake rdoc
|
84
|
+
```
|
85
|
+
|
86
|
+
## Copyright
|
87
|
+
|
88
|
+
Copyright 2016 Florian Dejonckheere. See `LICENSE` for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.test_files = FileList['test/*/*_test.rb']
|
7
|
+
t.verbose = true
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rdoc/task'
|
11
|
+
RDoc::Task.new do |rdoc|
|
12
|
+
rdoc.main = "README.md"
|
13
|
+
rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'metal_archives/version'
|
2
|
+
require 'metal_archives/configuration'
|
3
|
+
require 'metal_archives/error'
|
4
|
+
|
5
|
+
require 'metal_archives/models/base_model'
|
6
|
+
require 'metal_archives/models/range'
|
7
|
+
require 'metal_archives/models/label'
|
8
|
+
require 'metal_archives/models/artist'
|
9
|
+
require 'metal_archives/models/band'
|
10
|
+
|
11
|
+
require 'metal_archives/parsers/parser_helper'
|
12
|
+
require 'metal_archives/parsers/label'
|
13
|
+
require 'metal_archives/parsers/artist'
|
14
|
+
require 'metal_archives/parsers/band'
|
15
|
+
|
16
|
+
require 'metal_archives/http_client'
|
17
|
+
|
18
|
+
##
|
19
|
+
# Metal Archives Ruby API
|
20
|
+
#
|
21
|
+
module MetalArchives
|
22
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module MetalArchives
|
2
|
+
class << self
|
3
|
+
##
|
4
|
+
# API configuration
|
5
|
+
#
|
6
|
+
# Instance of rdoc-ref:MetalArchives::Configuration
|
7
|
+
#
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
##
|
11
|
+
# Configure API options.
|
12
|
+
#
|
13
|
+
# A block must be specified, to which a
|
14
|
+
# rdoc-ref:MetalArchives::Configuration parameter will be passed.
|
15
|
+
#
|
16
|
+
# Raises rdoc-ref:InvalidConfigurationException
|
17
|
+
#
|
18
|
+
def configure
|
19
|
+
raise MetalArchives::Errors::InvalidConfigurationError, 'No configuration block given' unless block_given?
|
20
|
+
yield MetalArchives.config ||= MetalArchives::Configuration.new
|
21
|
+
|
22
|
+
raise MetalArchives::Errors::InvalidConfigurationError, 'app_name has not been configured' unless MetalArchives.config.app_name and not MetalArchives.config.app_name.empty?
|
23
|
+
raise MetalArchives::Errors::InvalidConfigurationError, 'app_version has not been configured' unless MetalArchives.config.app_version and not MetalArchives.config.app_version.empty?
|
24
|
+
raise MetalArchives::Errors::InvalidConfigurationError, 'app_contact has not been configured' unless MetalArchives.config.app_contact and not MetalArchives.config.app_contact.empty?
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Contains configuration options
|
31
|
+
#
|
32
|
+
class Configuration
|
33
|
+
##
|
34
|
+
# *Required.* Application name (used in request header)
|
35
|
+
#
|
36
|
+
attr_accessor :app_name
|
37
|
+
|
38
|
+
##
|
39
|
+
# *Required.* Application version (used in request header)
|
40
|
+
#
|
41
|
+
attr_accessor :app_version
|
42
|
+
|
43
|
+
##
|
44
|
+
# *Required.* Application contact email (used in request header)
|
45
|
+
#
|
46
|
+
attr_accessor :app_contact
|
47
|
+
|
48
|
+
##
|
49
|
+
# Whether to enable the cache
|
50
|
+
#
|
51
|
+
attr_accessor :enable_cache
|
52
|
+
|
53
|
+
##
|
54
|
+
# ActiveSupport::Cache compatible store
|
55
|
+
#
|
56
|
+
attr_accessor :cache_store
|
57
|
+
|
58
|
+
##
|
59
|
+
# Request throttling rate (in seconds per request per path)
|
60
|
+
#
|
61
|
+
attr_accessor :request_rate
|
62
|
+
|
63
|
+
##
|
64
|
+
# Request timeout (in seconds per request per path)
|
65
|
+
#
|
66
|
+
attr_accessor :request_timeout
|
67
|
+
|
68
|
+
##
|
69
|
+
# Print debug information
|
70
|
+
#
|
71
|
+
attr_accessor :debug
|
72
|
+
|
73
|
+
##
|
74
|
+
# Default configuration values
|
75
|
+
#
|
76
|
+
def initialize
|
77
|
+
@throttle_rate = 1
|
78
|
+
@throttle_wait = 3
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module MetalArchives
|
2
|
+
##
|
3
|
+
# MetalArchives gem specific errors
|
4
|
+
#
|
5
|
+
module Errors
|
6
|
+
##
|
7
|
+
# Generic error
|
8
|
+
#
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Error in data
|
13
|
+
#
|
14
|
+
class DataError < Error; end
|
15
|
+
|
16
|
+
##
|
17
|
+
# No or invalid configuration found
|
18
|
+
#
|
19
|
+
class InvalidConfigurationError < Error; end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Error parsing value
|
23
|
+
#
|
24
|
+
class ParserError < Error; end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Functionality not implemented (yet)
|
28
|
+
class NotImplementedError < Error; end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Error in backend response
|
32
|
+
#
|
33
|
+
class APIError < Error; end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday-http-cache'
|
3
|
+
require 'faraday_throttler'
|
4
|
+
|
5
|
+
module MetalArchives
|
6
|
+
##
|
7
|
+
# HTTP request client
|
8
|
+
#
|
9
|
+
class HTTPClient # :nodoc:
|
10
|
+
class << self
|
11
|
+
|
12
|
+
##
|
13
|
+
# Retrieve a HTTP resource
|
14
|
+
#
|
15
|
+
def get(*params)
|
16
|
+
response = client.get *params
|
17
|
+
|
18
|
+
raise Errors::APIError, response.status if response.status >= 400
|
19
|
+
|
20
|
+
response
|
21
|
+
rescue Faraday::Error::ClientError => e
|
22
|
+
raise Errors::APIError, e
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
##
|
27
|
+
# Retrieve a HTTP client
|
28
|
+
#
|
29
|
+
def client
|
30
|
+
raise Errors::InvalidConfigurationError, 'Not configured yet' unless MetalArchives.config
|
31
|
+
|
32
|
+
@faraday ||= Faraday.new do |f|
|
33
|
+
f.request :url_encoded # form-encode POST params
|
34
|
+
f.response :logger if !!MetalArchives.config.debug # log requests to STDOUT
|
35
|
+
|
36
|
+
f.use MetalArchives::Middleware
|
37
|
+
f.use Faraday::HttpCache,
|
38
|
+
:store => MetalArchives.config.cache_store if !!MetalArchives.config.enable_cache
|
39
|
+
f.use :throttler,
|
40
|
+
:rate => MetalArchives.config.request_rate,
|
41
|
+
:wait => MetalArchives.config.request_timeout
|
42
|
+
|
43
|
+
f.adapter Faraday.default_adapter
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Faraday middleware
|
51
|
+
#
|
52
|
+
class Middleware < Faraday::Middleware # :nodoc:
|
53
|
+
def call(env)
|
54
|
+
env[:request_headers].merge!(
|
55
|
+
'User-Agent' => user_agent_string,
|
56
|
+
'Via' => via_string,
|
57
|
+
'Accept' => accept_string
|
58
|
+
)
|
59
|
+
@app.call(env)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def user_agent_string
|
64
|
+
"#{MetalArchives.config.app_name}/#{MetalArchives.config.app_version} ( #{MetalArchives.config.app_contact} )"
|
65
|
+
end
|
66
|
+
|
67
|
+
def accept_string
|
68
|
+
'application/json'
|
69
|
+
end
|
70
|
+
|
71
|
+
def via_string
|
72
|
+
"gem metal_archives/#{VERSION}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'countries'
|
3
|
+
|
4
|
+
module MetalArchives
|
5
|
+
|
6
|
+
##
|
7
|
+
# Represents a single performer (but not a solo artist)
|
8
|
+
#
|
9
|
+
class Artist < BaseModel
|
10
|
+
##
|
11
|
+
# :attr_reader: id
|
12
|
+
#
|
13
|
+
# Returns +Integer+
|
14
|
+
#
|
15
|
+
property :id, :type => Integer
|
16
|
+
|
17
|
+
##
|
18
|
+
# :attr_reader: name
|
19
|
+
#
|
20
|
+
# Returns +String+
|
21
|
+
#
|
22
|
+
property :name
|
23
|
+
|
24
|
+
##
|
25
|
+
# :attr_reader: aliases
|
26
|
+
#
|
27
|
+
# Returns +Array+ of +String+
|
28
|
+
#
|
29
|
+
property :aliases, :multiple => true
|
30
|
+
|
31
|
+
##
|
32
|
+
# :attr_reader: country
|
33
|
+
#
|
34
|
+
# Returns +ISO3166::Country+
|
35
|
+
#
|
36
|
+
property :country, :type => ISO3166::Country
|
37
|
+
|
38
|
+
##
|
39
|
+
# :attr_reader: location
|
40
|
+
#
|
41
|
+
# Returns +String+
|
42
|
+
#
|
43
|
+
property :location
|
44
|
+
|
45
|
+
##
|
46
|
+
# :attr_reader: date_of_birth
|
47
|
+
#
|
48
|
+
# Returns +Date+
|
49
|
+
#
|
50
|
+
property :date_of_birth, :type => Date
|
51
|
+
|
52
|
+
##
|
53
|
+
# :attr_reader: date_of_death
|
54
|
+
#
|
55
|
+
# Returns +Date+
|
56
|
+
#
|
57
|
+
property :date_of_death, :type => Date
|
58
|
+
|
59
|
+
##
|
60
|
+
# :attr_reader: cause_of_death
|
61
|
+
#
|
62
|
+
# Returns +String+
|
63
|
+
#
|
64
|
+
property :cause_of_death
|
65
|
+
|
66
|
+
##
|
67
|
+
# :attr_reader: gender
|
68
|
+
#
|
69
|
+
# Returns +Symbol+, either +:male+ or +:female+
|
70
|
+
#
|
71
|
+
enum :gender, :values => [:male, :female]
|
72
|
+
|
73
|
+
##
|
74
|
+
# :attr_reader: biography
|
75
|
+
#
|
76
|
+
# Returns raw HTML +String+
|
77
|
+
#
|
78
|
+
property :biography
|
79
|
+
|
80
|
+
##
|
81
|
+
# :attr_reader: trivia
|
82
|
+
#
|
83
|
+
# Returns raw HTML +String+
|
84
|
+
#
|
85
|
+
property :trivia
|
86
|
+
|
87
|
+
##
|
88
|
+
# :attr_reader: links
|
89
|
+
#
|
90
|
+
# Returns +Array+ of +Hash+ containing the following keys
|
91
|
+
#
|
92
|
+
# [+similar+]
|
93
|
+
# - +:url+: +String+
|
94
|
+
# - +:type+: +Symbol+, either +:official+, +:unofficial+ or +:unlisted_bands+
|
95
|
+
# - +:title+: +String+
|
96
|
+
#
|
97
|
+
property :links, :multiple => true
|
98
|
+
|
99
|
+
# TODO: active bands/albums
|
100
|
+
# TODO: past bands/albums
|
101
|
+
# TODO: guest bands/albums
|
102
|
+
# TODO: misc bands/albums
|
103
|
+
|
104
|
+
protected
|
105
|
+
##
|
106
|
+
# Fetch the data and assemble the model
|
107
|
+
#
|
108
|
+
# Raises rdoc-ref:MetalArchives::Errors::APIError
|
109
|
+
#
|
110
|
+
def assemble # :nodoc:
|
111
|
+
## Base attributes
|
112
|
+
url = "http://www.metal-archives.com/artist/view/id/#{id}"
|
113
|
+
response = HTTPClient.get url
|
114
|
+
|
115
|
+
properties = Parsers::Artist.parse_html response.body
|
116
|
+
|
117
|
+
## Biography
|
118
|
+
url = "http://www.metal-archives.com/artist/read-more/id/#{id}/field/biography"
|
119
|
+
response = HTTPClient.get url
|
120
|
+
|
121
|
+
properties[:biography] = response.body
|
122
|
+
|
123
|
+
## Trivia
|
124
|
+
url = "http://www.metal-archives.com/artist/read-more/id/#{id}/field/trivia"
|
125
|
+
response = HTTPClient.get url
|
126
|
+
|
127
|
+
properties[:trivia] = response.body
|
128
|
+
|
129
|
+
## Related links
|
130
|
+
url = "http://www.metal-archives.com/link/ajax-list/type/person/id/#{id}"
|
131
|
+
response = HTTPClient.get url
|
132
|
+
|
133
|
+
properties[:links] = Parsers::Artist.parse_links_html response.body
|
134
|
+
|
135
|
+
## Use constructor to fill properties
|
136
|
+
initialize properties
|
137
|
+
end
|
138
|
+
|
139
|
+
class << self
|
140
|
+
##
|
141
|
+
# Find by ID
|
142
|
+
#
|
143
|
+
# Returns rdoc-ref:Artist, even when ID is invalid (because the data is lazily fetched)
|
144
|
+
#
|
145
|
+
# [+id+]
|
146
|
+
# +Integer+
|
147
|
+
#
|
148
|
+
def find(id)
|
149
|
+
Artist.new :id => id
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Find by attributes
|
154
|
+
#
|
155
|
+
# Returns rdoc-ref:Artist or nil when ID is invalid
|
156
|
+
#
|
157
|
+
# [+query+]
|
158
|
+
# Hash containing one or more of the following keys:
|
159
|
+
# - +:name+: +String+
|
160
|
+
#
|
161
|
+
def find_by(query)
|
162
|
+
url = 'http://www.metal-archives.com/search/ajax-artist-search/'
|
163
|
+
params = Parsers::Artist.map_params query
|
164
|
+
|
165
|
+
response = HTTPClient.get url, params
|
166
|
+
json = JSON.parse response.body
|
167
|
+
|
168
|
+
return nil if json['aaData'].empty?
|
169
|
+
|
170
|
+
data = json['aaData'].first
|
171
|
+
id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.gsub('\\', '').split('/').last.gsub(/\D/, '').to_i
|
172
|
+
|
173
|
+
Artist.new :id => id
|
174
|
+
rescue Errors::APIError
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Search by name
|
180
|
+
#
|
181
|
+
# Returns (possibly empty) +Array+ of rdoc-ref:Artist
|
182
|
+
#
|
183
|
+
# [+name+]
|
184
|
+
# +String+
|
185
|
+
#
|
186
|
+
def search(name)
|
187
|
+
objects = []
|
188
|
+
|
189
|
+
url = 'http://www.metal-archives.com/search/ajax-artist-search/'
|
190
|
+
query = {
|
191
|
+
:name => name,
|
192
|
+
:iDisplayStart => 0
|
193
|
+
}
|
194
|
+
|
195
|
+
loop do
|
196
|
+
params = Parsers::Artist.map_params query
|
197
|
+
|
198
|
+
response = HTTPClient.get url, params
|
199
|
+
json = JSON.parse response.body
|
200
|
+
|
201
|
+
json['aaData'].each do |data|
|
202
|
+
# Create Artist object for every ID in the results list
|
203
|
+
id = Nokogiri::HTML(data.first).xpath('//a/@href').first.value.gsub('\\', '').split('/').last.gsub(/\D/, '').to_i
|
204
|
+
objects << Artist.new(:id => id)
|
205
|
+
end
|
206
|
+
|
207
|
+
break if objects.length == json['iTotalRecords']
|
208
|
+
|
209
|
+
query[:iDisplayStart] += 200
|
210
|
+
end
|
211
|
+
|
212
|
+
objects
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|