kagu 0.3.3 → 0.4.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 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