kagu 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2dd607618f6af1f48b5c1372e6231e7ac1ce276c
4
- data.tar.gz: dbc913a388fd6472861c5278f7a8aae2d7b0774e
3
+ metadata.gz: dc8ff8418f368b1c12f3d2f8b01d6af8e7d7a3a6
4
+ data.tar.gz: e7be4e6197998c38421f7208d0c84a898c83b6e9
5
5
  SHA512:
6
- metadata.gz: 2eb43b8406afce2f01ebabea8c2e24f9cff4990699ef867dfbb725cc135a048330acd34a8c078ce40fded93431d07676a71382f1f7734a151898807996ba8400
7
- data.tar.gz: 158bf6e51ecd12fafa3f645ebe7f0e90bab7a793ff9a9143ce31e6ca191ab9cc7d89462adbff8cc60b5892485bf0723a19ff08a64b0e05fdff5691d371b2c0fe
6
+ metadata.gz: c7a69aeed895d34f70b4cda33304f48cd284a81105b79d772c7c1e3e1fe3d472110aad768241edbe2c651486f494d851e7380d60cc5b7c70ad2e9ecabfbc8bdb
7
+ data.tar.gz: eb07e916b94813b67ed2872bc94897bacaf06873b25df46b71bdeddebcca8c586dc22d335833d71222e5c7b0678e375cd0dc445334ff8690a563965fe3479def
data/README.mdown CHANGED
@@ -6,7 +6,9 @@ Ruby API to manage iTunes tracks and playlists.
6
6
 
7
7
  Just add this into your `Gemfile`:
8
8
 
9
- gem 'kagu'
9
+ ```ruby
10
+ gem 'kagu'
11
+ ```
10
12
 
11
13
  Then, just run `bundle install`.
12
14
 
@@ -14,26 +16,41 @@ Then, just run `bundle install`.
14
16
 
15
17
  ### Displaying all tracks artist
16
18
 
17
- library = Kagu::Library.new
19
+ ```ruby
20
+ library = Kagu::Library.new
18
21
 
19
- library.tracks.each do |track|
20
- puts track.artist
21
- end
22
+ library.tracks.each do |track|
23
+ puts track.artist
24
+ end
25
+ ```
22
26
 
23
27
  ### Displaying all playlists and its tracks count
24
28
 
25
- library = Kagu::Library.new
29
+ ```ruby
30
+ library = Kagu::Library.new
26
31
 
27
- library.playlists.each do |playlist|
28
- puts "#{playlist.name}: #{playlist.tracks.count}"
29
- end
32
+ library.playlists.each do |playlist|
33
+ puts "#{playlist.name}: #{playlist.tracks.count}"
34
+ end
35
+ ```
30
36
 
31
37
  ### Creating a playlist
32
38
 
33
- library = Kagu::Library.new
39
+ ```ruby
40
+ library = Kagu::Library.new
34
41
 
35
- tracks = library.tracks.select { |track| track.genre == 'Drum & Bass' && track.added_at > 1.week.ago }
36
- library.playlists.create(name: 'Recent - D&B', tracks: tracks)
42
+ tracks = library.tracks.select { |track| track.genre == 'Drum & Bass' && track.added_at > 1.week.ago }
43
+ library.playlists.create(name: 'Recent - D&B', tracks: tracks)
44
+ ```
45
+
46
+ ### Finding some tracks
47
+
48
+ ```ruby
49
+ library = Kagu::Library.new
50
+ library.finder.find(artist: 'Serial Killaz', title: 'Walk and Skank').each do
51
+ puts track.id
52
+ end
53
+ ```
37
54
 
38
55
  ## Executing test suite
39
56
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.4.0
data/kagu.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency 'applescript', '>= 1.0', '< 2.0'
21
21
  s.add_dependency 'htmlentities', '>= 4.3.0', '< 4.4.0'
22
22
 
23
- s.add_development_dependency 'byebug', '>= 3.2.0', '< 3.6.0'
23
+ s.add_development_dependency 'byebug', '>= 3.2.0', '< 7.0.0'
24
24
  s.add_development_dependency 'rake', '>= 10.3.0', '< 10.5.0'
25
- s.add_development_dependency 'rspec', '>= 3.1.0', '< 3.2.0'
25
+ s.add_development_dependency 'rspec', '>= 3.1.0', '< 3.4.0'
26
26
  end
@@ -0,0 +1,123 @@
1
+ module Kagu
2
+
3
+ class Finder
4
+
5
+ MANDATORY_ATTRIBUTES = []
6
+
7
+ attr_reader :library
8
+
9
+ delegate :replace, :transliterate, to: 'self.class'
10
+
11
+ def self.replace(value, replacements = {})
12
+ replaced = value.to_s.dup
13
+ replacements.each do |pattern, replacement|
14
+ replaced.gsub!(pattern, replacement)
15
+ end
16
+ replaced.presence
17
+ end
18
+
19
+ def self.transliterate(value)
20
+ ActiveSupport::Inflector.transliterate(value.to_s).squish.downcase.presence
21
+ end
22
+
23
+ def initialize(library, options = {})
24
+ raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
25
+ @library = library
26
+ reload(options)
27
+ end
28
+
29
+ def find(attributes = {})
30
+ attributes.stringify_keys!
31
+ results = [].tap do |matches|
32
+ tracks_digests.each_with_index do |hash, digests_index|
33
+ digests(attributes).each_with_index do |digest, digest_index|
34
+ tracks = hash[digest].presence || next
35
+ tracks = tracks.select { |track| !matches.any? { |match| match.include?(track) } }
36
+ next if tracks.empty?
37
+ index = [digests_index, digest_index].max
38
+ matches[index] ||= []
39
+ matches[index].push(*tracks)
40
+ end
41
+ end
42
+ end
43
+ results.compact!
44
+ replacements.any? ? results : results.flatten
45
+ end
46
+
47
+ def ignored
48
+ @ignored ||= []
49
+ end
50
+
51
+ def reload(options = {})
52
+ @ignored = nil
53
+ @replacements = nil
54
+ @tracks_digests = nil
55
+ options.each do |name, value|
56
+ send("#{name}=", value) if respond_to?("#{name}=", true)
57
+ end
58
+ self
59
+ end
60
+
61
+ def replacements
62
+ @replacements ||= []
63
+ end
64
+
65
+ private
66
+
67
+ def digests(attributes)
68
+ attributes.stringify_keys!
69
+ return [] if attributes['artist'].blank? || attributes['title'].blank?
70
+ digests = [transliterate("#{attributes['artist']} #{attributes['title']}")]
71
+ replacements.each do |item|
72
+ digests << replace(digests.last, item)
73
+ end
74
+ digests.uniq
75
+ end
76
+
77
+ def ignored=(values)
78
+ @ignored = [values].flatten.map do |value|
79
+ if value.is_a?(Hash)
80
+ value = value.stringify_keys
81
+ value = "#{value['artist']} #{value['title']}"
82
+ elsif value.is_a?(Track)
83
+ value = "#{value.artist} #{value.title}"
84
+ else
85
+ value = value.to_s
86
+ end
87
+ self.class.transliterate(value)
88
+ end.compact.uniq
89
+ end
90
+
91
+ def replacements=(value)
92
+ if value.nil?
93
+ @replacements = []
94
+ elsif value.is_a?(Hash)
95
+ @replacements = [value]
96
+ elsif value.is_a?(Array)
97
+ @replacements = value
98
+ else
99
+ raise("Replacements must be an array or a hash, #{value.inspect} given")
100
+ end
101
+ replacements.each do |item|
102
+ raise('Replacements must contain only hashes') unless item.is_a?(Hash)
103
+ end
104
+ end
105
+
106
+ def tracks_digests
107
+ @tracks_digests ||= begin
108
+ [].tap do |tracks_digests|
109
+ library.tracks.each do |track|
110
+ digests(artist: track.artist, title: track.title).each_with_index do |digest, index|
111
+ next if ignored.include?(digest)
112
+ tracks_digests[index] ||= {}
113
+ tracks_digests[index][digest] ||= []
114
+ tracks_digests[index][digest].push(track)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ end
data/lib/kagu/library.rb CHANGED
@@ -10,6 +10,10 @@ module Kagu
10
10
  self.path = path
11
11
  end
12
12
 
13
+ def finder(options = {})
14
+ Finder.new(self, options)
15
+ end
16
+
13
17
  def playlists
14
18
  Playlists.new(self)
15
19
  end
data/lib/kagu.rb CHANGED
@@ -8,6 +8,7 @@ lib_path = "#{__dir__}/kagu"
8
8
 
9
9
  require "#{lib_path}/attributes_initializer"
10
10
  require "#{lib_path}/error"
11
+ require "#{lib_path}/finder"
11
12
  require "#{lib_path}/library"
12
13
  require "#{lib_path}/playlist"
13
14
  require "#{lib_path}/playlists"
@@ -0,0 +1,241 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kagu::Finder do
4
+
5
+ let(:finder) { Kagu::Finder.new(library) }
6
+ let(:library) { Kagu::Library.new }
7
+
8
+ describe '.replace' do
9
+
10
+ it 'replace given value with replacements' do
11
+ expect(Kagu::Finder.replace('Hello World!', 'World' => 'John', 'Hello' => 'Bye')).to eq('Bye John!')
12
+ end
13
+
14
+ it 'is case sensitive' do
15
+ expect(Kagu::Finder.replace('Hello World!', 'world' => 'John', 'Hello' => 'Bye')).to eq('Bye World!')
16
+ end
17
+
18
+ it 'replace given value with some regexp' do
19
+ expect(Kagu::Finder.replace('Hello World!', 'World' => 'John', /l+/ => 'i')).to eq('Heio John!')
20
+ end
21
+
22
+ it 'returns nil if blank' do
23
+ expect(Kagu::Finder.replace(' ', 'world' => 'John', 'Hello' => 'Bye')).to be_nil
24
+ end
25
+
26
+ end
27
+
28
+ describe '.transliterate' do
29
+
30
+ it 'removes accents' do
31
+ expect(Kagu::Finder.transliterate('éàlôrs')).to eq('ealors')
32
+ end
33
+
34
+ it 'removes consecutive white spaces' do
35
+ expect(Kagu::Finder.transliterate(" hello \t world\n ")).to eq('hello world')
36
+ end
37
+
38
+ it 'converts value to lower case' do
39
+ expect(Kagu::Finder.transliterate('Hello World')).to eq('hello world')
40
+ end
41
+
42
+ it 'removes nil if blank' do
43
+ expect(Kagu::Finder.transliterate(' ')).to be_nil
44
+ end
45
+
46
+ end
47
+
48
+ describe '#find' do
49
+
50
+ it 'returns some tracks' do
51
+ tracks = finder.find(artist: 'korn', title: 'blind')
52
+ expect(tracks).to be_an(Array)
53
+ expect(tracks.first).to be_a(Kagu::Track)
54
+ expect(tracks.first.title).to match(/blind/i)
55
+ end
56
+
57
+ it 'returns nothing if artist is blank' do
58
+ expect(finder.find(title: 'blind')).to be_empty
59
+ end
60
+
61
+ it 'returns nothing if title is blank' do
62
+ expect(finder.find(artist: 'korn')).to be_empty
63
+ end
64
+
65
+ it 'returns nothing if artist match an artist and a track' do
66
+ expect(finder.find(artist: 'korn blind')).to be_empty
67
+ end
68
+
69
+ it 'artist and title can be specified as string' do
70
+ expect(finder.find('artist' => 'KoRn', 'title' => 'Ball Tongue').first).to be_a(Kagu::Track)
71
+ end
72
+
73
+ it 'returns an array of array of tracks if there is some replacements' do
74
+ finder.reload(replacements: { 'ball tongue' => 'blind' })
75
+ results = finder.find(artist: 'korn', title: 'blind')
76
+ expect(results.size).to eq(2)
77
+ expect(results.first).to be_an(Array)
78
+ expect(results.first.first.title).to match(/blind/i)
79
+ expect(results.second.first.title).to match(/ball tongue/i)
80
+ end
81
+
82
+ it 'replacements are working with given attributes' do
83
+ finder.reload(replacements: { 'hello world' => 'blind' })
84
+ results = finder.find(artist: 'korn', title: 'hello world')
85
+ expect(results.size).to eq(1)
86
+ expect(results.first.first).to be_a(Kagu::Track)
87
+ expect(results.first.first.title).to match(/blind/i)
88
+ end
89
+
90
+ it 'put at first position the exact matches from given attributes' do
91
+ finder.reload(replacements: { 'ball tongue' => 'blind' })
92
+ results = finder.find(artist: 'korn', title: 'ball tongue')
93
+ expect(results.size).to eq(2)
94
+
95
+ expect(results.first).not_to be_empty
96
+ results.first.each do |track|
97
+ expect(track.title).to match(/ball tongue/i)
98
+ end
99
+
100
+ expect(results.second).not_to be_empty
101
+ results.second.each do |track|
102
+ expect(track.title).to match(/blind/i)
103
+ end
104
+ end
105
+
106
+ it 'does not include ignored tracks' do
107
+ expect {
108
+ finder.reload(ignored: 'korn blind')
109
+ }.to change { finder.find(artist: 'korn', title: 'blind').size }.to(0)
110
+ expect(finder.find(artist: 'korn', title: 'ball tongue')).not_to be_empty
111
+ end
112
+
113
+ end
114
+
115
+ describe '#ignored' do
116
+
117
+ it 'can be set as an array of string' do
118
+ expect {
119
+ finder.reload(ignored: ['test', 'foo'])
120
+ }.to change { finder.ignored }.from([]).to(['test', 'foo'])
121
+ end
122
+
123
+ it 'can be set as a simple string' do
124
+ expect {
125
+ finder.reload(ignored: 'test')
126
+ }.to change { finder.ignored }.from([]).to(['test'])
127
+ end
128
+
129
+ it 'is transliterated' do
130
+ expect {
131
+ finder.reload(ignored: ['testé HeLLo World', 'foo'])
132
+ }.to change { finder.ignored }.from([]).to(['teste hello world', 'foo'])
133
+ end
134
+
135
+ it 'can be set from a track' do
136
+ expect {
137
+ finder.reload(ignored: [finder.find(artist: 'KoRn', title: 'Blind').first])
138
+ }.to change { finder.ignored }.from([]).to(['korn blind'])
139
+ end
140
+
141
+ it 'can be set from an hash (as string keys)' do
142
+ expect {
143
+ finder.reload(ignored: { 'artist' => 'KoRn', 'title' => 'Blind' })
144
+ }.to change { finder.ignored }.from([]).to(['korn blind'])
145
+ end
146
+
147
+ it 'can be set from an hash (with symbol keys)' do
148
+ expect {
149
+ finder.reload(ignored: { artist: 'KoRn', title: 'Blind' })
150
+ }.to change { finder.ignored }.from([]).to(['korn blind'])
151
+ end
152
+
153
+ it 'removes duplicates' do
154
+ expect {
155
+ finder.reload(ignored: ['test', 'Foo ', 'foo', 'TEST'])
156
+ }.to change { finder.ignored }.from([]).to(['test', 'foo'])
157
+ end
158
+
159
+ end
160
+
161
+ describe '#library' do
162
+
163
+ it 'is library given at initialization' do
164
+ expect(finder.library).to be(library)
165
+ end
166
+
167
+ it 'raise an error if library is nil' do
168
+ expect {
169
+ Kagu::Finder.new(nil)
170
+ }.to raise_error(ArgumentError, 'Kagu::Finder#library must be a library, nil given')
171
+ end
172
+
173
+ end
174
+
175
+ describe '#reload' do
176
+
177
+ it 'sets replacements' do
178
+ expect {
179
+ finder.reload(replacements: { 'foo' => 'bar' })
180
+ }.to change { finder.replacements }.from([]).to([{ 'foo' => 'bar' }])
181
+ end
182
+
183
+ it 'removes replacements' do
184
+ finder.reload(replacements: { 'foo' => 'bar' })
185
+ expect {
186
+ finder.reload
187
+ }.to change { finder.replacements }.to([])
188
+ end
189
+
190
+ it 'removes ignored' do
191
+ finder.reload(ignored: 'foo')
192
+ expect {
193
+ finder.reload
194
+ }.to change { finder.ignored }.to([])
195
+ end
196
+
197
+ it 'returns finder' do
198
+ expect(finder.reload).to be(finder)
199
+ end
200
+
201
+ it 'reload tracks' do
202
+ expect {
203
+ finder.reload(replacements: { /\./ => '' })
204
+ }.to change { finder.find(artist: 'korn', title: 'adidas').present? }.from(false).to(true)
205
+ end
206
+
207
+ end
208
+
209
+ describe '#replacements' do
210
+
211
+ it 'can be ommited' do
212
+ expect(finder.reload.replacements).to eq([])
213
+ end
214
+
215
+ it 'can be specified with a string as key' do
216
+ expect(finder.reload('replacements' => { 'foo' => 'bar' }).replacements).to eq([{ 'foo' => 'bar' }])
217
+ end
218
+
219
+ it 'can be specified as hash' do
220
+ expect(finder.reload(replacements: { 'foo' => 'bar' }).replacements).to eq([{ 'foo' => 'bar' }])
221
+ end
222
+
223
+ it 'can be specified as array of hashes' do
224
+ expect(finder.reload(replacements: [{ 'foo' => 'bar' }, { 'titi' => 'toto' }]).replacements).to eq([{ 'foo' => 'bar' }, { 'titi' => 'toto' }])
225
+ end
226
+
227
+ it 'raise an error if a string is given' do
228
+ expect {
229
+ finder.reload(replacements: 'foo')
230
+ }.to raise_error('Replacements must be an array or a hash, "foo" given')
231
+ end
232
+
233
+ it 'raise an error if it contains something else than an hash' do
234
+ expect {
235
+ finder.reload(replacements: [{ 'foo' => 'bar' }, 'titi'])
236
+ }.to raise_error('Replacements must contain only hashes')
237
+ end
238
+
239
+ end
240
+
241
+ end
@@ -24,6 +24,18 @@ describe Kagu::Library do
24
24
 
25
25
  end
26
26
 
27
+ describe '#finder' do
28
+
29
+ it 'returns a Kagu::Finder instance' do
30
+ expect(library.finder).to be_a(Kagu::Finder)
31
+ end
32
+
33
+ it 'options can be specified' do
34
+ expect(library.finder(replacements: { 'foo' => 'bar' }).replacements).to eq([{ 'foo' => 'bar' }])
35
+ end
36
+
37
+ end
38
+
27
39
  describe '#playlists' do
28
40
 
29
41
  it 'returns a Playlists object' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kagu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Toulotte
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-16 00:00:00.000000000 Z
11
+ date: 2015-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -79,7 +79,7 @@ dependencies:
79
79
  version: 3.2.0
80
80
  - - "<"
81
81
  - !ruby/object:Gem::Version
82
- version: 3.6.0
82
+ version: 7.0.0
83
83
  type: :development
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
@@ -89,7 +89,7 @@ dependencies:
89
89
  version: 3.2.0
90
90
  - - "<"
91
91
  - !ruby/object:Gem::Version
92
- version: 3.6.0
92
+ version: 7.0.0
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: rake
95
95
  requirement: !ruby/object:Gem::Requirement
@@ -119,7 +119,7 @@ dependencies:
119
119
  version: 3.1.0
120
120
  - - "<"
121
121
  - !ruby/object:Gem::Version
122
- version: 3.2.0
122
+ version: 3.4.0
123
123
  type: :development
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
@@ -129,7 +129,7 @@ dependencies:
129
129
  version: 3.1.0
130
130
  - - "<"
131
131
  - !ruby/object:Gem::Version
132
- version: 3.2.0
132
+ version: 3.4.0
133
133
  description: API to manage iTunes tracks and playlists
134
134
  email: al@alweb.org
135
135
  executables: []
@@ -147,12 +147,14 @@ files:
147
147
  - lib/kagu.rb
148
148
  - lib/kagu/attributes_initializer.rb
149
149
  - lib/kagu/error.rb
150
+ - lib/kagu/finder.rb
150
151
  - lib/kagu/library.rb
151
152
  - lib/kagu/playlist.rb
152
153
  - lib/kagu/playlists.rb
153
154
  - lib/kagu/track.rb
154
155
  - lib/kagu/tracks.rb
155
156
  - spec/kagu/error_spec.rb
157
+ - spec/kagu/finder_spec.rb
156
158
  - spec/kagu/library_spec.rb
157
159
  - spec/kagu/playlist_spec.rb
158
160
  - spec/kagu/playlists_spec.rb
@@ -185,6 +187,7 @@ specification_version: 4
185
187
  summary: API for iTunes
186
188
  test_files:
187
189
  - spec/kagu/error_spec.rb
190
+ - spec/kagu/finder_spec.rb
188
191
  - spec/kagu/library_spec.rb
189
192
  - spec/kagu/playlist_spec.rb
190
193
  - spec/kagu/playlists_spec.rb