metal_archives 0.8.0 → 1.0.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 +4 -4
- data/.gitignore +59 -7
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -0
- data/{LICENSE → LICENSE.md} +0 -0
- data/README.md +77 -9
- data/Rakefile +5 -3
- data/lib/metal_archives.rb +8 -0
- data/lib/metal_archives/configuration.rb +28 -7
- data/lib/metal_archives/error.rb +37 -30
- data/lib/metal_archives/http_client.rb +21 -42
- data/lib/metal_archives/middleware/headers.rb +38 -0
- data/lib/metal_archives/middleware/rewrite_endpoint.rb +38 -0
- data/lib/metal_archives/models/artist.rb +51 -65
- data/lib/metal_archives/models/band.rb +41 -39
- data/lib/metal_archives/models/base_model.rb +88 -59
- data/lib/metal_archives/models/label.rb +7 -6
- data/lib/metal_archives/parsers/artist.rb +110 -99
- data/lib/metal_archives/parsers/band.rb +168 -156
- data/lib/metal_archives/parsers/label.rb +54 -52
- data/lib/metal_archives/parsers/parser.rb +73 -71
- data/lib/metal_archives/utils/collection.rb +7 -1
- data/lib/metal_archives/utils/lru_cache.rb +11 -4
- data/lib/metal_archives/utils/nil_date.rb +54 -0
- data/lib/metal_archives/utils/range.rb +16 -8
- data/lib/metal_archives/version.rb +3 -1
- data/metal_archives.gemspec +21 -11
- data/spec/configuration_spec.rb +101 -0
- data/spec/factories/artist_factory.rb +37 -0
- data/spec/factories/band_factory.rb +60 -0
- data/spec/factories/nil_date_factory.rb +9 -0
- data/spec/factories/range_factory.rb +8 -0
- data/spec/models/artist_spec.rb +142 -0
- data/spec/models/band_spec.rb +179 -0
- data/spec/models/base_model_spec.rb +217 -0
- data/spec/parser_spec.rb +19 -0
- data/spec/spec_helper.rb +111 -0
- data/spec/support/factory_girl.rb +5 -0
- data/spec/support/metal_archives.rb +26 -0
- data/spec/utils/collection_spec.rb +72 -0
- data/spec/utils/lru_cache_spec.rb +53 -0
- data/spec/utils/nil_date_spec.rb +98 -0
- data/spec/utils/range_spec.rb +62 -0
- metadata +142 -57
- data/test/base_model_test.rb +0 -111
- data/test/configuration_test.rb +0 -57
- data/test/parser_test.rb +0 -37
- data/test/property/artist_property_test.rb +0 -43
- data/test/property/band_property_test.rb +0 -94
- data/test/query/artist_query_test.rb +0 -109
- data/test/query/band_query_test.rb +0 -152
- data/test/test_helper.rb +0 -25
- data/test/utils/collection_test.rb +0 -51
- data/test/utils/lru_cache_test.rb +0 -22
- data/test/utils/range_test.rb +0 -42
@@ -1,88 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'date'
|
2
4
|
require 'countries'
|
3
5
|
|
4
6
|
module MetalArchives
|
5
|
-
##
|
6
|
-
# Mapping layer from and to MA Web Service
|
7
|
-
#
|
8
|
-
module Parsers # :nodoc:
|
9
7
|
##
|
10
|
-
#
|
8
|
+
# Mapping layer from and to MA Web Service
|
11
9
|
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
module Parsers # :nodoc:
|
11
|
+
##
|
12
|
+
# Parser base class
|
13
|
+
#
|
14
|
+
class Parser
|
15
|
+
class << self
|
16
|
+
##
|
17
|
+
# Parse a country
|
18
|
+
#
|
19
|
+
# Returns +ISO3166::Country+
|
20
|
+
#
|
21
|
+
def parse_country(input)
|
22
|
+
ISO3166::Country.find_country_by_name input
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
##
|
26
|
+
# Sanitize a string
|
27
|
+
#
|
28
|
+
# Return +String+
|
29
|
+
#
|
30
|
+
def sanitize(input)
|
31
|
+
input.gsub(/^"/, '').gsub(/"$/, '').strip
|
32
|
+
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# Duplicate all WIP genres
|
70
|
-
temp2 = temp.dup
|
34
|
+
##
|
35
|
+
# Opinionated parsing of genres
|
36
|
+
#
|
37
|
+
# Returns an +Array+ of +String+
|
38
|
+
#
|
39
|
+
# The following components are omitted:
|
40
|
+
# - Metal
|
41
|
+
# - (early)
|
42
|
+
# - (later)
|
43
|
+
#
|
44
|
+
# All genres are capitalized.
|
45
|
+
#
|
46
|
+
# For examples on how genres are parsed, refer to +ParserTest#test_parse_genre+
|
47
|
+
#
|
48
|
+
def parse_genre(input)
|
49
|
+
genres = []
|
50
|
+
# Split fields
|
51
|
+
input.split(',').each do |genre|
|
52
|
+
##
|
53
|
+
# Start with a single empty genre string. Split the genre by spaces
|
54
|
+
# and process each component. If a component does not have a slash,
|
55
|
+
# concatenate it to all genre strings present in +temp+. If it does
|
56
|
+
# have a slash present, duplicate all genre strings, and concatenate
|
57
|
+
# the first component (before the slash) to the first half, and the
|
58
|
+
# last component to the last half. +temp+ now has an array of genre
|
59
|
+
# combinations.
|
60
|
+
#
|
61
|
+
# 'Traditional Heavy/Power Metal' => ['Traditional Heavy', 'Traditional Power']
|
62
|
+
# 'Traditional/Classical Heavy/Power Metal' => [
|
63
|
+
# 'Traditional Heavy', 'Traditional Power',
|
64
|
+
# 'Classical Heavy', 'Classical Power']
|
65
|
+
#
|
66
|
+
temp = ['']
|
67
|
+
genre.downcase.split.reject { |g| ['(early)', '(later)', 'metal'].include? g }.each do |g|
|
68
|
+
if g.include? '/'
|
69
|
+
# Duplicate all WIP genres
|
70
|
+
temp2 = temp.dup
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
# Assign first and last components to temp and temp2 respectively
|
73
|
+
split = g.split '/'
|
74
|
+
temp.map! { |t| t.empty? ? split.first.capitalize : "#{t.capitalize} #{split.first.capitalize}" }
|
75
|
+
temp2.map! { |t| t.empty? ? split.last.capitalize : "#{t.capitalize} #{split.last.capitalize}" }
|
76
76
|
|
77
|
-
|
78
|
-
|
77
|
+
# Add both genre trees
|
78
|
+
temp += temp2
|
79
|
+
else
|
80
|
+
temp.map! { |t| t.empty? ? g.capitalize : "#{t.capitalize} #{g.capitalize}" }
|
81
|
+
end
|
79
82
|
end
|
83
|
+
genres += temp
|
80
84
|
end
|
81
|
-
genres
|
85
|
+
genres.uniq
|
82
86
|
end
|
83
|
-
genres.uniq
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
87
90
|
end
|
88
|
-
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MetalArchives
|
2
4
|
##
|
3
5
|
# Enumerable collection over a paginated resource
|
@@ -20,7 +22,7 @@ module MetalArchives
|
|
20
22
|
# Calls the given block once for each element, passing that element as a parameter.
|
21
23
|
# If no block is given, an Enumerator is returned.
|
22
24
|
#
|
23
|
-
def each
|
25
|
+
def each
|
24
26
|
return to_enum :each unless block_given?
|
25
27
|
|
26
28
|
loop do
|
@@ -33,5 +35,9 @@ module MetalArchives
|
|
33
35
|
break if items.empty?
|
34
36
|
end
|
35
37
|
end
|
38
|
+
|
39
|
+
def empty?
|
40
|
+
first.nil?
|
41
|
+
end
|
36
42
|
end
|
37
43
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MetalArchives
|
2
4
|
##
|
3
5
|
# Generic LRU memory cache
|
@@ -44,11 +46,16 @@ module MetalArchives
|
|
44
46
|
@cache.include? key
|
45
47
|
end
|
46
48
|
|
49
|
+
def delete(key)
|
50
|
+
@cache.delete key
|
51
|
+
end
|
52
|
+
|
47
53
|
private
|
48
|
-
def pop
|
49
|
-
to_remove = @keys.shift @keys.size - @size
|
50
54
|
|
51
|
-
|
52
|
-
|
55
|
+
def pop
|
56
|
+
to_remove = @keys.shift @keys.size - @size
|
57
|
+
|
58
|
+
to_remove.each { |key| @cache.delete key }
|
59
|
+
end
|
53
60
|
end
|
54
61
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetalArchives
|
4
|
+
##
|
5
|
+
# Date with nullable year, month and day
|
6
|
+
#
|
7
|
+
# WARNING: No validation on actual date is performed
|
8
|
+
#
|
9
|
+
class NilDate
|
10
|
+
attr_accessor :year, :month, :day
|
11
|
+
|
12
|
+
def initialize(year = nil, month = nil, day = nil)
|
13
|
+
@year = year
|
14
|
+
@month = month
|
15
|
+
@day = day
|
16
|
+
end
|
17
|
+
|
18
|
+
def year?
|
19
|
+
!@year.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def month?
|
23
|
+
!@month.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def day?
|
27
|
+
!@day.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Return a +Date+ object
|
32
|
+
#
|
33
|
+
def date
|
34
|
+
raise MetalArchives::Errors::ArgumentError, 'invalid date' unless year?
|
35
|
+
|
36
|
+
::Date.new @year, month || 1, day || 1
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Parse YYYY[-MM[-DD]]
|
41
|
+
#
|
42
|
+
def self.parse(value)
|
43
|
+
split = value.split('-')
|
44
|
+
|
45
|
+
year = Integer(split[0]) unless split.empty?
|
46
|
+
month = Integer(split[1]) if split.length > 1
|
47
|
+
day = Integer(split[2]) if split.length > 2
|
48
|
+
|
49
|
+
return MetalArchives::NilDate.new year, month, day
|
50
|
+
rescue
|
51
|
+
raise MetalArchives::Errors::ArgumentError, 'invalid date'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MetalArchives
|
2
4
|
##
|
3
5
|
# Range which can start and/or end with +nil+
|
@@ -32,27 +34,33 @@ module MetalArchives
|
|
32
34
|
# Whether start of range is present
|
33
35
|
#
|
34
36
|
def begin?
|
35
|
-
|
37
|
+
!!@begin
|
36
38
|
end
|
37
39
|
|
38
40
|
##
|
39
41
|
# Whether end of range is present
|
40
42
|
#
|
41
43
|
def end?
|
42
|
-
|
44
|
+
!!@end
|
43
45
|
end
|
44
46
|
|
45
47
|
##
|
46
48
|
# Comparison operator
|
47
49
|
#
|
48
50
|
def <=>(other)
|
49
|
-
|
51
|
+
comp_begin = self.begin <=> other.begin
|
52
|
+
comp_end = self.end <=> other.end
|
53
|
+
# Return nil if begin or end is uncomparable
|
54
|
+
return nil if comp_begin.nil? || comp_end.nil?
|
55
|
+
|
56
|
+
# Compare end if begin is equal
|
57
|
+
return comp_end if comp_begin.zero?
|
58
|
+
|
59
|
+
# Compare begin if end is equal
|
60
|
+
return comp_begin if comp_begin.zero?
|
50
61
|
|
51
|
-
|
52
|
-
|
53
|
-
else
|
54
|
-
return comparison
|
55
|
-
end
|
62
|
+
# Compare actual range
|
63
|
+
(self.end - self.begin) <=> (other.end - other.begin)
|
56
64
|
end
|
57
65
|
end
|
58
66
|
end
|
data/metal_archives.gemspec
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'metal_archives/version'
|
2
7
|
|
3
8
|
Gem::Specification.new do |gem|
|
4
9
|
gem.authors = ['Florian Dejonckheere']
|
5
10
|
gem.email = ['florian@floriandejonckheere.be']
|
6
|
-
gem.summary =
|
11
|
+
gem.summary = 'Metal Archives Ruby API'
|
7
12
|
gem.homepage = 'http://github.com/floriandejonckheere/metal_archives'
|
8
13
|
|
9
|
-
gem.files =
|
14
|
+
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
10
15
|
gem.executables = []
|
11
16
|
gem.test_files = gem.files.grep(%r{^spec/})
|
12
17
|
gem.name = 'metal_archives'
|
@@ -14,13 +19,18 @@ Gem::Specification.new do |gem|
|
|
14
19
|
gem.version = MetalArchives::VERSION
|
15
20
|
gem.license = 'MIT'
|
16
21
|
|
17
|
-
gem.add_development_dependency 'byebug'
|
18
|
-
gem.add_development_dependency 'rake'
|
19
|
-
gem.add_development_dependency 'rdoc'
|
20
|
-
gem.add_development_dependency '
|
22
|
+
gem.add_development_dependency 'byebug'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'rdoc'
|
25
|
+
gem.add_development_dependency 'rspec'
|
26
|
+
gem.add_development_dependency 'factory_girl'
|
27
|
+
gem.add_development_dependency 'faker'
|
28
|
+
gem.add_development_dependency 'rubocop'
|
29
|
+
gem.add_development_dependency 'coveralls'
|
30
|
+
gem.add_development_dependency 'debase'
|
21
31
|
|
22
|
-
gem.add_dependency 'faraday'
|
23
|
-
gem.add_dependency 'faraday_throttler'
|
24
|
-
gem.add_dependency 'nokogiri'
|
25
|
-
gem.add_dependency 'countries'
|
32
|
+
gem.add_dependency 'faraday'
|
33
|
+
gem.add_dependency 'faraday_throttler'
|
34
|
+
gem.add_dependency 'nokogiri'
|
35
|
+
gem.add_dependency 'countries'
|
26
36
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe MetalArchives::Configuration do
|
4
|
+
describe 'properties' do
|
5
|
+
it 'has the correct properties' do
|
6
|
+
expect(subject).to respond_to :app_name
|
7
|
+
expect(subject).to respond_to :app_name=
|
8
|
+
expect(subject).to respond_to :app_version
|
9
|
+
expect(subject).to respond_to :app_version=
|
10
|
+
expect(subject).to respond_to :app_contact
|
11
|
+
expect(subject).to respond_to :app_contact=
|
12
|
+
expect(subject).to respond_to :endpoint
|
13
|
+
expect(subject).to respond_to :endpoint=
|
14
|
+
expect(subject).to respond_to :default_endpoint
|
15
|
+
expect(subject).to respond_to :middleware
|
16
|
+
expect(subject).to respond_to :middleware=
|
17
|
+
expect(subject).to respond_to :request_rate
|
18
|
+
expect(subject).to respond_to :request_rate=
|
19
|
+
expect(subject).to respond_to :request_timeout
|
20
|
+
expect(subject).to respond_to :request_timeout=
|
21
|
+
expect(subject).to respond_to :logger
|
22
|
+
expect(subject).to respond_to :logger=
|
23
|
+
expect(subject).to respond_to :debug
|
24
|
+
expect(subject).to respond_to :debug=
|
25
|
+
expect(subject).to respond_to :cache_size
|
26
|
+
expect(subject).to respond_to :cache_size=
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'has default properties' do
|
30
|
+
expect(subject.default_endpoint).to eq 'https://www.metal-archives.com/'
|
31
|
+
expect(subject.logger).not_to be_nil
|
32
|
+
expect(subject.debug).to be false
|
33
|
+
expect(subject.cache_size).to be_an Integer
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'overrides defaults' do
|
37
|
+
subject.endpoint = 'http://my-proxy.com/'
|
38
|
+
logger = Logger.new STDERR
|
39
|
+
subject.logger = logger
|
40
|
+
subject.debug = true
|
41
|
+
subject.cache_size = 0
|
42
|
+
|
43
|
+
expect(subject.endpoint).to eq 'http://my-proxy.com/'
|
44
|
+
expect(subject.logger).to be logger
|
45
|
+
expect(subject.debug).to be true
|
46
|
+
expect(subject.cache_size).to eq 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'configuration' do
|
51
|
+
after(:each) do
|
52
|
+
# Reset configuration
|
53
|
+
load 'support/metal_archives.rb'
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is invalid without app_name' do
|
57
|
+
proc = -> do
|
58
|
+
MetalArchives.configure do |c|
|
59
|
+
c.app_version = MetalArchives::VERSION
|
60
|
+
c.app_contact = 'user@example.com'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
expect(proc).to raise_error MetalArchives::Errors::InvalidConfigurationError
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'is invalid without app_version' do
|
68
|
+
proc = -> do
|
69
|
+
MetalArchives.configure do |c|
|
70
|
+
c.app_name = 'MetalArchivesGemTestSuite'
|
71
|
+
c.app_contact = 'user@example.com'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
expect(proc).to raise_error MetalArchives::Errors::InvalidConfigurationError
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'is invalid without app_contact' do
|
79
|
+
proc = -> do
|
80
|
+
MetalArchives.configure do |c|
|
81
|
+
c.app_name = 'MetalArchivesGemTestSuite'
|
82
|
+
c.app_version = MetalArchives::VERSION
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
expect(proc).to raise_error MetalArchives::Errors::InvalidConfigurationError
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'is valid' do
|
90
|
+
proc = -> do
|
91
|
+
MetalArchives.configure do |c|
|
92
|
+
c.app_name = 'MetalArchivesGemTestSuite'
|
93
|
+
c.app_version = MetalArchives::VERSION
|
94
|
+
c.app_contact = 'user@example.com'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
expect(proc).not_to raise_error
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|