metal_archives 2.2.0 → 3.1.1

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.
Files changed (71) 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 +66 -6
  7. data/CHANGELOG.md +33 -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 -27
  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 +77 -52
  28. data/lib/metal_archives/models/base.rb +225 -0
  29. data/lib/metal_archives/models/label.rb +14 -15
  30. data/lib/metal_archives/models/release.rb +25 -29
  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 +18 -63
  39. data/lib/metal_archives/parsers/release.rb +98 -89
  40. data/lib/metal_archives/parsers/year.rb +31 -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 +179 -74
  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/models/release_spec.rb +0 -133
  64. data/spec/parser_spec.rb +0 -19
  65. data/spec/spec_helper.rb +0 -111
  66. data/spec/support/factory_girl.rb +0 -5
  67. data/spec/support/metal_archives.rb +0 -33
  68. data/spec/utils/collection_spec.rb +0 -72
  69. data/spec/utils/lru_cache_spec.rb +0 -53
  70. data/spec/utils/nil_date_spec.rb +0 -156
  71. 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