metal_archives 2.1.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +6 -6
  4. data/.overcommit.yml +35 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +69 -6
  7. data/CHANGELOG.md +29 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.md +17 -4
  10. data/README.md +65 -86
  11. data/Rakefile +8 -7
  12. data/bin/console +38 -0
  13. data/bin/setup +8 -0
  14. data/config/inflections.rb +7 -0
  15. data/config/initializers/.keep +0 -0
  16. data/docker-compose.yml +23 -0
  17. data/lib/metal_archives.rb +82 -25
  18. data/lib/metal_archives/cache/base.rb +40 -0
  19. data/lib/metal_archives/cache/memory.rb +68 -0
  20. data/lib/metal_archives/cache/null.rb +22 -0
  21. data/lib/metal_archives/cache/redis.rb +49 -0
  22. data/lib/metal_archives/{utils/collection.rb → collection.rb} +3 -5
  23. data/lib/metal_archives/configuration.rb +33 -50
  24. data/lib/metal_archives/{error.rb → errors.rb} +9 -1
  25. data/lib/metal_archives/http_client.rb +45 -44
  26. data/lib/metal_archives/models/artist.rb +90 -45
  27. data/lib/metal_archives/models/band.rb +80 -55
  28. data/lib/metal_archives/models/base.rb +218 -0
  29. data/lib/metal_archives/models/label.rb +14 -15
  30. data/lib/metal_archives/models/release.rb +349 -0
  31. data/lib/metal_archives/parsers/artist.rb +86 -50
  32. data/lib/metal_archives/parsers/band.rb +155 -88
  33. data/lib/metal_archives/parsers/base.rb +14 -0
  34. data/lib/metal_archives/parsers/country.rb +21 -0
  35. data/lib/metal_archives/parsers/date.rb +31 -0
  36. data/lib/metal_archives/parsers/genre.rb +67 -0
  37. data/lib/metal_archives/parsers/label.rb +39 -31
  38. data/lib/metal_archives/parsers/parser.rb +16 -63
  39. data/lib/metal_archives/parsers/release.rb +242 -0
  40. data/lib/metal_archives/parsers/year.rb +29 -0
  41. data/lib/metal_archives/version.rb +12 -1
  42. data/metal_archives.env.example +10 -0
  43. data/metal_archives.gemspec +43 -28
  44. data/nginx/default.conf +60 -0
  45. metadata +181 -72
  46. data/.travis.yml +0 -12
  47. data/lib/metal_archives/middleware/cache_check.rb +0 -20
  48. data/lib/metal_archives/middleware/encoding.rb +0 -16
  49. data/lib/metal_archives/middleware/headers.rb +0 -38
  50. data/lib/metal_archives/middleware/rewrite_endpoint.rb +0 -38
  51. data/lib/metal_archives/models/base_model.rb +0 -215
  52. data/lib/metal_archives/utils/lru_cache.rb +0 -61
  53. data/lib/metal_archives/utils/nil_date.rb +0 -99
  54. data/lib/metal_archives/utils/range.rb +0 -66
  55. data/spec/configuration_spec.rb +0 -96
  56. data/spec/factories/artist_factory.rb +0 -37
  57. data/spec/factories/band_factory.rb +0 -60
  58. data/spec/factories/nil_date_factory.rb +0 -9
  59. data/spec/factories/range_factory.rb +0 -8
  60. data/spec/models/artist_spec.rb +0 -138
  61. data/spec/models/band_spec.rb +0 -164
  62. data/spec/models/base_model_spec.rb +0 -219
  63. data/spec/parser_spec.rb +0 -19
  64. data/spec/spec_helper.rb +0 -111
  65. data/spec/support/factory_girl.rb +0 -5
  66. data/spec/support/metal_archives.rb +0 -33
  67. data/spec/utils/collection_spec.rb +0 -72
  68. data/spec/utils/lru_cache_spec.rb +0 -53
  69. data/spec/utils/nil_date_spec.rb +0 -156
  70. data/spec/utils/range_spec.rb +0 -62
data/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.1.10
6
- - 2.2.7
7
- - 2.3.4
8
- - 2.4.1
9
- env:
10
- - RAILS_ENV=test CI=true TRAVIS=true
11
- script:
12
- - bundle exec rspec
@@ -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