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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +59 -7
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +14 -0
  5. data/.travis.yml +11 -0
  6. data/Gemfile +2 -0
  7. data/{LICENSE → LICENSE.md} +0 -0
  8. data/README.md +77 -9
  9. data/Rakefile +5 -3
  10. data/lib/metal_archives.rb +8 -0
  11. data/lib/metal_archives/configuration.rb +28 -7
  12. data/lib/metal_archives/error.rb +37 -30
  13. data/lib/metal_archives/http_client.rb +21 -42
  14. data/lib/metal_archives/middleware/headers.rb +38 -0
  15. data/lib/metal_archives/middleware/rewrite_endpoint.rb +38 -0
  16. data/lib/metal_archives/models/artist.rb +51 -65
  17. data/lib/metal_archives/models/band.rb +41 -39
  18. data/lib/metal_archives/models/base_model.rb +88 -59
  19. data/lib/metal_archives/models/label.rb +7 -6
  20. data/lib/metal_archives/parsers/artist.rb +110 -99
  21. data/lib/metal_archives/parsers/band.rb +168 -156
  22. data/lib/metal_archives/parsers/label.rb +54 -52
  23. data/lib/metal_archives/parsers/parser.rb +73 -71
  24. data/lib/metal_archives/utils/collection.rb +7 -1
  25. data/lib/metal_archives/utils/lru_cache.rb +11 -4
  26. data/lib/metal_archives/utils/nil_date.rb +54 -0
  27. data/lib/metal_archives/utils/range.rb +16 -8
  28. data/lib/metal_archives/version.rb +3 -1
  29. data/metal_archives.gemspec +21 -11
  30. data/spec/configuration_spec.rb +101 -0
  31. data/spec/factories/artist_factory.rb +37 -0
  32. data/spec/factories/band_factory.rb +60 -0
  33. data/spec/factories/nil_date_factory.rb +9 -0
  34. data/spec/factories/range_factory.rb +8 -0
  35. data/spec/models/artist_spec.rb +142 -0
  36. data/spec/models/band_spec.rb +179 -0
  37. data/spec/models/base_model_spec.rb +217 -0
  38. data/spec/parser_spec.rb +19 -0
  39. data/spec/spec_helper.rb +111 -0
  40. data/spec/support/factory_girl.rb +5 -0
  41. data/spec/support/metal_archives.rb +26 -0
  42. data/spec/utils/collection_spec.rb +72 -0
  43. data/spec/utils/lru_cache_spec.rb +53 -0
  44. data/spec/utils/nil_date_spec.rb +98 -0
  45. data/spec/utils/range_spec.rb +62 -0
  46. metadata +142 -57
  47. data/test/base_model_test.rb +0 -111
  48. data/test/configuration_test.rb +0 -57
  49. data/test/parser_test.rb +0 -37
  50. data/test/property/artist_property_test.rb +0 -43
  51. data/test/property/band_property_test.rb +0 -94
  52. data/test/query/artist_query_test.rb +0 -109
  53. data/test/query/band_query_test.rb +0 -152
  54. data/test/test_helper.rb +0 -25
  55. data/test/utils/collection_test.rb +0 -51
  56. data/test/utils/lru_cache_test.rb +0 -22
  57. 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
- # Parser base class
8
+ # Mapping layer from and to MA Web Service
11
9
  #
12
- class Parser
13
- class << self
14
- ##
15
- # Parse a country
16
- #
17
- # Returns +ISO3166::Country+
18
- #
19
- def parse_country(input)
20
- ISO3166::Country.find_country_by_name (input)
21
- end
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
- # Sanitize a string
25
- #
26
- # Return +String+
27
- #
28
- def sanitize(input)
29
- input.gsub(/^"/, '').gsub(/"$/, '').strip
30
- end
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
- # Opinionated parsing of genres
34
- #
35
- # Returns an +Array+ of +String+
36
- #
37
- # The following components are omitted:
38
- # - Metal
39
- # - (early)
40
- # - (later)
41
- #
42
- # All genres are capitalized.
43
- #
44
- # For examples on how genres are parsed, refer to +ParserTest#test_parse_genre+
45
- #
46
- def parse_genre(input)
47
- genres = []
48
- # Split fields
49
- input.split(',').each do |genre|
50
- ##
51
- # Start with a single empty genre string. Split the genre by spaces
52
- # and process each component. If a component does not have a slash,
53
- # concatenate it to all genre strings present in +temp+. If it does
54
- # have a slash present, duplicate all genre strings, and concatenate
55
- # the first component (before the slash) to the first half, and the
56
- # last component to the last half. +temp+ now has an array of genre
57
- # combinations.
58
- #
59
- # 'Traditional Heavy/Power Metal' => ['Traditional Heavy', 'Traditional Power']
60
- # 'Traditional/Classical Heavy/Power Metal' => [
61
- # 'Traditional Heavy', 'Traditional Power',
62
- # 'Classical Heavy', 'Classical Power']
63
- #
64
- temp = ['']
65
- genre.downcase.split.reject { |g| ['(early)', '(later)', 'metal'].include? g }.each do |g|
66
- unless g.include? '/'
67
- temp.map! { |t| t.empty? ? g.capitalize : "#{t.capitalize} #{g.capitalize}" }
68
- else
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
- # 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}" }
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
- # Add both genre trees
78
- temp += temp2
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 += temp
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(&block)
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
- to_remove.each { |key| @cache.delete key }
52
- end
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
- !!@begin
37
+ !!@begin
36
38
  end
37
39
 
38
40
  ##
39
41
  # Whether end of range is present
40
42
  #
41
43
  def end?
42
- !!@end
44
+ !!@end
43
45
  end
44
46
 
45
47
  ##
46
48
  # Comparison operator
47
49
  #
48
50
  def <=>(other)
49
- comparison = self.begin <=> other.begin
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
- if comparison == 0
52
- return self.end <=> other.end
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
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MetalArchives
2
4
  ##
3
5
  # MetalArchives API version
4
6
  #
5
- VERSION = '0.8.0'
7
+ VERSION = '1.0.0'
6
8
  end
@@ -1,12 +1,17 @@
1
- require File.expand_path('../lib/metal_archives/version', __FILE__)
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 = %q{Metal Archives Ruby API}
11
+ gem.summary = 'Metal Archives Ruby API'
7
12
  gem.homepage = 'http://github.com/floriandejonckheere/metal_archives'
8
13
 
9
- gem.files = %x{git ls-files}.split($\)
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', '~> 9.0'
18
- gem.add_development_dependency 'rake', '~> 11.0'
19
- gem.add_development_dependency 'rdoc', '~> 5.0'
20
- gem.add_development_dependency 'test-unit', '~> 3.0'
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', '~> 0.9'
23
- gem.add_dependency 'faraday_throttler', '~> 0.0.3'
24
- gem.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.8'
25
- gem.add_dependency 'countries', '~> 1.2', '>= 1.2.5'
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