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.
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