metal_archives 2.2.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +93 -0
- data/.gitignore +6 -6
- data/.overcommit.yml +35 -0
- data/.rspec +2 -0
- data/.rubocop.yml +66 -6
- data/CHANGELOG.md +33 -0
- data/Gemfile +1 -1
- data/LICENSE.md +17 -4
- data/README.md +65 -86
- data/Rakefile +8 -7
- data/bin/console +38 -0
- data/bin/setup +8 -0
- data/config/inflections.rb +7 -0
- data/config/initializers/.keep +0 -0
- data/docker-compose.yml +23 -0
- data/lib/metal_archives.rb +82 -27
- data/lib/metal_archives/cache/base.rb +40 -0
- data/lib/metal_archives/cache/memory.rb +68 -0
- data/lib/metal_archives/cache/null.rb +22 -0
- data/lib/metal_archives/cache/redis.rb +49 -0
- data/lib/metal_archives/{utils/collection.rb → collection.rb} +3 -5
- data/lib/metal_archives/configuration.rb +33 -50
- data/lib/metal_archives/{error.rb → errors.rb} +9 -1
- data/lib/metal_archives/http_client.rb +45 -44
- data/lib/metal_archives/models/artist.rb +90 -45
- data/lib/metal_archives/models/band.rb +77 -52
- data/lib/metal_archives/models/base.rb +225 -0
- data/lib/metal_archives/models/label.rb +14 -15
- data/lib/metal_archives/models/release.rb +25 -29
- data/lib/metal_archives/parsers/artist.rb +86 -50
- data/lib/metal_archives/parsers/band.rb +155 -88
- data/lib/metal_archives/parsers/base.rb +14 -0
- data/lib/metal_archives/parsers/country.rb +21 -0
- data/lib/metal_archives/parsers/date.rb +31 -0
- data/lib/metal_archives/parsers/genre.rb +67 -0
- data/lib/metal_archives/parsers/label.rb +39 -31
- data/lib/metal_archives/parsers/parser.rb +18 -63
- data/lib/metal_archives/parsers/release.rb +98 -89
- data/lib/metal_archives/parsers/year.rb +31 -0
- data/lib/metal_archives/version.rb +12 -1
- data/metal_archives.env.example +10 -0
- data/metal_archives.gemspec +43 -28
- data/nginx/default.conf +60 -0
- metadata +179 -74
- data/.travis.yml +0 -12
- data/lib/metal_archives/middleware/cache_check.rb +0 -20
- data/lib/metal_archives/middleware/encoding.rb +0 -16
- data/lib/metal_archives/middleware/headers.rb +0 -38
- data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
- data/lib/metal_archives/models/base_model.rb +0 -215
- data/lib/metal_archives/utils/lru_cache.rb +0 -61
- data/lib/metal_archives/utils/nil_date.rb +0 -99
- data/lib/metal_archives/utils/range.rb +0 -66
- data/spec/configuration_spec.rb +0 -96
- data/spec/factories/artist_factory.rb +0 -37
- data/spec/factories/band_factory.rb +0 -60
- data/spec/factories/nil_date_factory.rb +0 -9
- data/spec/factories/range_factory.rb +0 -8
- data/spec/models/artist_spec.rb +0 -138
- data/spec/models/band_spec.rb +0 -164
- data/spec/models/base_model_spec.rb +0 -219
- data/spec/models/release_spec.rb +0 -133
- data/spec/parser_spec.rb +0 -19
- data/spec/spec_helper.rb +0 -111
- data/spec/support/factory_girl.rb +0 -5
- data/spec/support/metal_archives.rb +0 -33
- data/spec/utils/collection_spec.rb +0 -72
- data/spec/utils/lru_cache_spec.rb +0 -53
- data/spec/utils/nil_date_spec.rb +0 -156
- data/spec/utils/range_spec.rb +0 -62
data/.travis.yml
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'faraday'
|
4
|
-
|
5
|
-
module MetalArchives
|
6
|
-
module Middleware
|
7
|
-
##
|
8
|
-
# Log cache status
|
9
|
-
#
|
10
|
-
class CacheCheck < Faraday::Response::Middleware # :nodoc:
|
11
|
-
def on_complete(env)
|
12
|
-
return unless MetalArchives.config.endpoint
|
13
|
-
|
14
|
-
if env[:response_headers].has_key? 'x-cache-status'
|
15
|
-
MetalArchives.config.logger.info "Cache #{env[:response_headers]['x-cache-status'].downcase} for #{env.url.to_s}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'faraday'
|
4
|
-
|
5
|
-
module MetalArchives
|
6
|
-
module Middleware
|
7
|
-
##
|
8
|
-
# Force UTF-8 conversion
|
9
|
-
#
|
10
|
-
class Encoding < Faraday::Response::Middleware # :nodoc:
|
11
|
-
def on_complete(env)
|
12
|
-
env.response.body.force_encoding('utf-8')
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'faraday'
|
4
|
-
|
5
|
-
module MetalArchives
|
6
|
-
module Middleware
|
7
|
-
##
|
8
|
-
# Add appropriate request headers
|
9
|
-
#
|
10
|
-
class Headers < Faraday::Middleware # :nodoc:
|
11
|
-
def call(env)
|
12
|
-
headers = {
|
13
|
-
'User-Agent' => user_agent_string,
|
14
|
-
'Via' => via_string,
|
15
|
-
'Accept' => accept_string
|
16
|
-
}
|
17
|
-
|
18
|
-
env[:request_headers].merge! headers
|
19
|
-
|
20
|
-
@app.call env
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def user_agent_string
|
26
|
-
"#{MetalArchives.config.app_name}/#{MetalArchives.config.app_version} ( #{MetalArchives.config.app_contact} )"
|
27
|
-
end
|
28
|
-
|
29
|
-
def accept_string
|
30
|
-
'application/json'
|
31
|
-
end
|
32
|
-
|
33
|
-
def via_string
|
34
|
-
"gem metal_archives/#{VERSION}"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'faraday'
|
4
|
-
|
5
|
-
module MetalArchives
|
6
|
-
module Middleware
|
7
|
-
##
|
8
|
-
# Dynamically rewrite endpoints
|
9
|
-
#
|
10
|
-
class RewriteEndpoint < Faraday::Middleware
|
11
|
-
def call(env)
|
12
|
-
env[:url] = RewriteEndpoint.rewrite(env[:url])
|
13
|
-
|
14
|
-
@app.call env
|
15
|
-
end
|
16
|
-
|
17
|
-
class << self
|
18
|
-
def rewrite(uri)
|
19
|
-
return uri unless MetalArchives.config.endpoint
|
20
|
-
|
21
|
-
new_uri = uri.clone
|
22
|
-
|
23
|
-
default_uri = URI MetalArchives.config.default_endpoint
|
24
|
-
rewritten_uri = URI MetalArchives.config.endpoint
|
25
|
-
|
26
|
-
if uri.host == default_uri.host && uri.scheme == default_uri.scheme
|
27
|
-
new_uri.host = rewritten_uri.host
|
28
|
-
new_uri.scheme = rewritten_uri.scheme
|
29
|
-
|
30
|
-
MetalArchives.config.logger.debug "Rewrite #{uri.to_s} to #{new_uri}"
|
31
|
-
end
|
32
|
-
|
33
|
-
new_uri
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,215 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module MetalArchives
|
3
|
-
##
|
4
|
-
# Abstract model class
|
5
|
-
#
|
6
|
-
class BaseModel
|
7
|
-
##
|
8
|
-
# Generic shallow copy constructor
|
9
|
-
#
|
10
|
-
def initialize(hash = {})
|
11
|
-
raise Errors::NotImplementedError, 'no :id property in model' unless respond_to? :id?, true
|
12
|
-
|
13
|
-
hash.each do |property, value|
|
14
|
-
instance_variable_set("@#{property}", value) if self.class.properties.include? property
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
##
|
19
|
-
# Returns true if two objects have the same type and id
|
20
|
-
#
|
21
|
-
def ==(obj)
|
22
|
-
obj.instance_of?(self.class) && id ==(obj.id)
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Fetch, parse and load the data
|
27
|
-
#
|
28
|
-
# [Raises]
|
29
|
-
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no id
|
30
|
-
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
31
|
-
#
|
32
|
-
def load!
|
33
|
-
raise Errors::InvalidIDError, 'no id present' unless !!id
|
34
|
-
|
35
|
-
# Use constructor to set attributes
|
36
|
-
initialize assemble
|
37
|
-
|
38
|
-
# Set empty properties to nil
|
39
|
-
self.class.properties.each do |prop|
|
40
|
-
instance_variable_set("@#{prop}", nil) unless instance_variable_defined? "@#{prop}"
|
41
|
-
end
|
42
|
-
|
43
|
-
@loaded = true
|
44
|
-
self.class.cache[id] = self
|
45
|
-
rescue => e
|
46
|
-
# Don't cache invalid requests
|
47
|
-
self.class.cache.delete id
|
48
|
-
raise e
|
49
|
-
end
|
50
|
-
|
51
|
-
##
|
52
|
-
# Whether or not the object is currently loaded
|
53
|
-
#
|
54
|
-
def loaded?
|
55
|
-
!@loaded.nil?
|
56
|
-
end
|
57
|
-
|
58
|
-
##
|
59
|
-
# Whether or not the object is currently cached
|
60
|
-
#
|
61
|
-
def cached?
|
62
|
-
loaded? && self.class.cache.include?(id)
|
63
|
-
end
|
64
|
-
|
65
|
-
protected
|
66
|
-
|
67
|
-
##
|
68
|
-
# Fetch the data and assemble the model
|
69
|
-
#
|
70
|
-
# Override this method
|
71
|
-
#
|
72
|
-
# [Raises]
|
73
|
-
# - rdoc-ref:MetalArchives::Errors::InvalidIDError when no or invalid id
|
74
|
-
# - rdoc-ref:MetalArchives::Errors::APIError when receiving a status code >= 400 (except 404)
|
75
|
-
#
|
76
|
-
def assemble
|
77
|
-
raise Errors::NotImplementedError, 'method :assemble not implemented'
|
78
|
-
end
|
79
|
-
|
80
|
-
class << self
|
81
|
-
##
|
82
|
-
# +Array+ of declared properties
|
83
|
-
#
|
84
|
-
attr_accessor :properties
|
85
|
-
|
86
|
-
##
|
87
|
-
# Get class-level object cache
|
88
|
-
#
|
89
|
-
def cache
|
90
|
-
@cache ||= MetalArchives::LRUCache.new MetalArchives.config.cache_size
|
91
|
-
end
|
92
|
-
|
93
|
-
protected
|
94
|
-
|
95
|
-
##
|
96
|
-
# Defines a model property.
|
97
|
-
#
|
98
|
-
# [+name+]
|
99
|
-
# Name of the property
|
100
|
-
#
|
101
|
-
# [+opts+]
|
102
|
-
# [+type+]
|
103
|
-
# Data type of property (a constant)
|
104
|
-
#
|
105
|
-
# Default: +String+
|
106
|
-
#
|
107
|
-
# [+multiple+]
|
108
|
-
# Whether or not the property has multiple values (which
|
109
|
-
# turns it into an +Array+ of +type+)
|
110
|
-
#
|
111
|
-
def property(name, opts = {})
|
112
|
-
(@properties ||= []) << name
|
113
|
-
|
114
|
-
# property
|
115
|
-
define_method(name) do
|
116
|
-
load! unless loaded? && instance_variable_defined?("@#{name}") || name ==(:id)
|
117
|
-
instance_variable_get("@#{name}")
|
118
|
-
end
|
119
|
-
|
120
|
-
# property?
|
121
|
-
define_method("#{name}?") do
|
122
|
-
load! unless loaded? && instance_variable_defined?("@#{name}") || name ==(:id)
|
123
|
-
|
124
|
-
property = instance_variable_get("@#{name}")
|
125
|
-
property.respond_to?(:empty?) ? !property.empty? : !!property
|
126
|
-
end
|
127
|
-
|
128
|
-
# property=
|
129
|
-
define_method("#{name}=") do |value|
|
130
|
-
if value.nil?
|
131
|
-
instance_variable_set "@#{name}", value
|
132
|
-
return
|
133
|
-
end
|
134
|
-
|
135
|
-
# Check value type
|
136
|
-
type = opts[:type] || String
|
137
|
-
if opts[:multiple]
|
138
|
-
raise MetalArchives::Errors::TypeError, "invalid type #{value.class}, must be Array for #{name}" unless value.is_a? Array
|
139
|
-
value.each do |val|
|
140
|
-
raise MetalArchives::Errors::TypeError, "invalid type #{val.class}, must be #{type} for #{name}" unless val.is_a? type
|
141
|
-
end
|
142
|
-
else
|
143
|
-
raise MetalArchives::Errors::TypeError, "invalid type #{value.class}, must be #{type} for #{name}" unless value.is_a? type
|
144
|
-
end
|
145
|
-
|
146
|
-
instance_variable_set "@#{name}", value
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
##
|
151
|
-
# Defines a model enum property.
|
152
|
-
#
|
153
|
-
# [+name+]
|
154
|
-
# Name of the property
|
155
|
-
#
|
156
|
-
# [+opts+]
|
157
|
-
# [+values+]
|
158
|
-
# Required. An array of possible values
|
159
|
-
#
|
160
|
-
# [+multiple+]
|
161
|
-
# Whether or not the property has multiple values (which
|
162
|
-
# turns it into an +Array+ of +type+)
|
163
|
-
#
|
164
|
-
def enum(name, opts)
|
165
|
-
raise ArgumentError, 'opts[:values] is required' unless opts and opts[:values]
|
166
|
-
|
167
|
-
(@properties ||= []) << name
|
168
|
-
|
169
|
-
# property
|
170
|
-
define_method(name) do
|
171
|
-
load! unless loaded? && instance_variable_defined?("@#{name}")
|
172
|
-
instance_variable_get("@#{name}")
|
173
|
-
end
|
174
|
-
|
175
|
-
# property?
|
176
|
-
define_method("#{name}?") do
|
177
|
-
load! unless loaded? && instance_variable_defined?("@#{name}")
|
178
|
-
|
179
|
-
property = instance_variable_get("@#{name}")
|
180
|
-
property.respond_to?(:empty?) ? !property.empty? : !!property
|
181
|
-
end
|
182
|
-
|
183
|
-
# property=
|
184
|
-
define_method("#{name}=") do |value|
|
185
|
-
# Check enum type
|
186
|
-
if opts[:multiple]
|
187
|
-
raise MetalArchives::Errors::TypeError, "invalid enum value #{value}, must be Array for #{name}" unless value.is_a? Array
|
188
|
-
value.each do |val|
|
189
|
-
raise MetalArchives::Errors::TypeError, "invalid enum value #{val} for #{name}" unless opts[:values].include? val
|
190
|
-
end
|
191
|
-
else
|
192
|
-
raise MetalArchives::Errors::TypeError, "invalid enum value #{value} for #{name}" unless opts[:values].include? value
|
193
|
-
end
|
194
|
-
|
195
|
-
instance_variable_set name, value
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
##
|
200
|
-
# Defines a model boolean property. This method is an alias for <tt>enum name, :values => [true, false]</tt>
|
201
|
-
#
|
202
|
-
# [+name+]
|
203
|
-
# Name of the property
|
204
|
-
#
|
205
|
-
# [+opts+]
|
206
|
-
# [+multiple+]
|
207
|
-
# Whether or not the property has multiple values (which
|
208
|
-
# turns it into an +Array+ of +type+)
|
209
|
-
#
|
210
|
-
def boolean(name, opts = {})
|
211
|
-
enum name, opts.merge(:values => [true, false])
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module MetalArchives
|
4
|
-
##
|
5
|
-
# Generic LRU memory cache
|
6
|
-
#
|
7
|
-
class LRUCache
|
8
|
-
def initialize(size = 100)
|
9
|
-
@size = size
|
10
|
-
|
11
|
-
# Underlying data store
|
12
|
-
@cache = {}
|
13
|
-
|
14
|
-
# Array of keys in order of insertion
|
15
|
-
@keys = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def []=(key, value)
|
19
|
-
@cache[key] = value
|
20
|
-
|
21
|
-
@keys.delete key if @keys.include? key
|
22
|
-
|
23
|
-
@keys << key
|
24
|
-
|
25
|
-
pop if @keys.size > @size
|
26
|
-
end
|
27
|
-
|
28
|
-
def [](key)
|
29
|
-
if @keys.include? key
|
30
|
-
MetalArchives.config.logger.debug "Cache hit for #{key}"
|
31
|
-
@keys.delete key
|
32
|
-
@keys << key
|
33
|
-
else
|
34
|
-
MetalArchives.config.logger.debug "Cache miss for #{key}"
|
35
|
-
end
|
36
|
-
|
37
|
-
@cache[key]
|
38
|
-
end
|
39
|
-
|
40
|
-
def clear
|
41
|
-
@cache.clear
|
42
|
-
@keys.clear
|
43
|
-
end
|
44
|
-
|
45
|
-
def include?(key)
|
46
|
-
@cache.include? key
|
47
|
-
end
|
48
|
-
|
49
|
-
def delete(key)
|
50
|
-
@cache.delete key
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def pop
|
56
|
-
to_remove = @keys.shift @keys.size - @size
|
57
|
-
|
58
|
-
to_remove.each { |key| @cache.delete key }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'date'
|
4
|
-
|
5
|
-
module MetalArchives
|
6
|
-
##
|
7
|
-
# Date with nullable year, month and day
|
8
|
-
#
|
9
|
-
# WARNING: No validation on actual date is performed
|
10
|
-
#
|
11
|
-
class NilDate
|
12
|
-
include Comparable
|
13
|
-
|
14
|
-
attr_accessor :year, :month, :day
|
15
|
-
|
16
|
-
def initialize(year = nil, month = nil, day = nil)
|
17
|
-
@year = year
|
18
|
-
@month = month
|
19
|
-
@day = day
|
20
|
-
end
|
21
|
-
|
22
|
-
def year?
|
23
|
-
!@year.nil?
|
24
|
-
end
|
25
|
-
|
26
|
-
def month?
|
27
|
-
!@month.nil?
|
28
|
-
end
|
29
|
-
|
30
|
-
def day?
|
31
|
-
!@day.nil?
|
32
|
-
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# Return a +Date+ object
|
36
|
-
#
|
37
|
-
def date
|
38
|
-
raise MetalArchives::Errors::ArgumentError, "Invalid conversion to Date: #{self.to_s}" unless year?
|
39
|
-
|
40
|
-
::Date.new @year, month || 1, day || 1
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
|
-
# Parse YYYY[-MM[-DD]]
|
45
|
-
#
|
46
|
-
def self.parse(value)
|
47
|
-
split = value.split('-')
|
48
|
-
|
49
|
-
year = Integer(split[0], 10) if split.any? && split[0] && !split[0].empty?
|
50
|
-
year = nil if year == 0
|
51
|
-
|
52
|
-
month = Integer(split[1], 10) if split.length > 1 && split[1] && !split[1].empty?
|
53
|
-
month = nil if month == 0
|
54
|
-
|
55
|
-
day = Integer(split[2], 10) if split.length > 2 && split[2] && !split[2].empty?
|
56
|
-
day = nil if day == 0
|
57
|
-
|
58
|
-
return MetalArchives::NilDate.new year, month, day
|
59
|
-
rescue => e
|
60
|
-
raise MetalArchives::Errors::ArgumentError, "Invalid date: #{value}: #{e}"
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_s
|
64
|
-
year = (@year || 0).to_s.rjust 2, '0'
|
65
|
-
month = (@month || 0).to_s.rjust 2, '0'
|
66
|
-
day = (@day || 0).to_s.rjust 2, '0'
|
67
|
-
|
68
|
-
"#{year}-#{month}-#{day}"
|
69
|
-
end
|
70
|
-
|
71
|
-
##
|
72
|
-
# Comparison operator
|
73
|
-
#
|
74
|
-
def <=>(other)
|
75
|
-
return nil if other.nil?
|
76
|
-
|
77
|
-
# Return nil if one of the two years is nil
|
78
|
-
return nil if (@year.nil? && !other.year.nil?) || !@year.nil? && other.year.nil?
|
79
|
-
|
80
|
-
# Return nil if one of the two months is nil
|
81
|
-
return nil if (@month.nil? && !other.month.nil?) || !@month.nil? && other.month.nil?
|
82
|
-
|
83
|
-
# Return nil if one of the two months is nil
|
84
|
-
return nil if (@day.nil? && !other.day.nil?) || !@day.nil? && other.day.nil?
|
85
|
-
|
86
|
-
comp_year = @year <=> other.year
|
87
|
-
if comp_year == 0
|
88
|
-
comp_month = @month <=> other.month
|
89
|
-
if comp_month == 0
|
90
|
-
return @day <=> other.day
|
91
|
-
else
|
92
|
-
return comp_month
|
93
|
-
end
|
94
|
-
else
|
95
|
-
comp_year
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|