metal_archives 0.8.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|