hallon 0.2.1 → 0.3.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.
data/CHANGELOG CHANGED
@@ -1,6 +1,13 @@
1
1
  Hallon’s Changelog
2
2
  ==================
3
3
 
4
+ v0.3.0
5
+ ------------------
6
+ - Don’t use bundler for :spec and :test rake tasks
7
+ - Add Error.table
8
+ - Add Track subsystem
9
+ - Fix spec:cov and spotify:coverage rake tasks
10
+
4
11
  v0.2.1
5
12
  ------------------
6
13
  - Fix compatibility with v1.8
data/QUIRKS CHANGED
@@ -1,5 +1,9 @@
1
1
  # Quirks, oddities and general notes about libspotify
2
2
 
3
+ - HTTP links with track offset (spotify:track:7N2Vc8u56VGA4KUrGbikC2#1:40) don’t
4
+ work, even if you URL encode the hash: http://open.spotify.com/track/7N2Vc8u56VGA4KUrGbikC2%231:40
5
+ this appears to have worked at least 7 months ago
6
+
3
7
  ## Session
4
8
  - `sp_session_release` will segfault (rare)
5
9
  - `login` callback fires even if the login fails; instead,
@@ -9,3 +13,6 @@
9
13
 
10
14
  ## Link
11
15
  - segfaults if created before a session
16
+
17
+ ## Track
18
+ - `sp_link_as_track_and_offset` operate in milliseconds, but documentation states seconds
data/README.markdown CHANGED
@@ -1,5 +1,8 @@
1
1
  What is Hallon?
2
2
  ===============
3
+
4
+ > [Hallon, delicious Ruby bindings for libspotify](http://burgestrand.se/articles/hallon-delicious-ruby-bindings-to-libspotify.html)
5
+
3
6
  We rubyists have this awesome [spotify gem][] allowing us to use [libspotify][] from within Ruby, but it has a significant drawback: the `libspotify` API is very hard to use. Now, we can’t have that, so what do we do? We make Hallon!
4
7
 
5
8
  Hallon is Swedish for “Raspberry”, and has been written to satisfy my needs for API simplicity. It provides you with a wrapper around the spotify gem, making the experience of using `libspotify` from Ruby much more enjoyable.
data/Rakefile CHANGED
@@ -9,11 +9,12 @@ YARD::Rake::YardocTask.new
9
9
 
10
10
  require 'rspec/core/rake_task'
11
11
  RSpec::Core::RakeTask.new('spec') do |task|
12
+ task.skip_bundler = true
12
13
  task.ruby_opts = '-W2'
13
14
  end
14
15
 
15
16
  desc "Run the full test suite and generate a coverage report"
16
- task 'spec:cov' => ['clean', 'spec:full'] do
17
+ task 'spec:cov' => ['clean', 'spec'] do
17
18
  require 'cover_me'
18
19
  require './spec/support/cover_me'
19
20
 
@@ -26,9 +27,21 @@ task 'spotify:coverage' do
26
27
  require 'set'
27
28
  require 'spotify'
28
29
 
29
- methods = Spotify.methods(false).map(&:to_s)
30
+ dynamicly_used = []
31
+ dynamicly_used << "link_create_from_track" # lib/hallon/track.rb
32
+ dynamicly_used << "track_add_ref" # lib/ext/spotify.rb
33
+ dynamicly_used << "track_release" # lib/ext/spotify.rb
34
+ dynamicly_used << "link_create_from_image" # lib/hallon/image.rb
35
+ dynamicly_used << "image_release" # lib/ext/spotify.rb
36
+ dynamicly_used << "link_create_from_user" # lib/hallon/user.rb
37
+ dynamicly_used << "user_add_ref" # lib/ext/spotify.rb
38
+ dynamicly_used << "user_release" # lib/ext/spotify.rb
39
+ dynamicly_used << "link_as_track" # IGNORE
40
+ dynamicly_used << "link_release" # lib/ext/spotify.rb
41
+
42
+ methods = Spotify.methods(false).map(&:to_s) - dynamicly_used
30
43
  covered = Set.new(methods)
31
- matcher = /Spotify::([\w_]+)[ \(]/
44
+ matcher = /Spotify(?:::|\.)([\w_]+)[ \(]/
32
45
 
33
46
  FileList['lib/**/*.rb'].each do |file|
34
47
  File.read(file).scan(matcher) { |method, _| covered.delete(method) }
@@ -9,18 +9,10 @@ session = Hallon::Session.instance IO.read(ENV['HALLON_APPKEY']) do
9
9
  end
10
10
 
11
11
  session.login ENV['HALLON_USERNAME'], ENV['HALLON_PASSWORD']
12
- logged_in = session.process_events_on(:logged_in) { |error| error }
13
12
 
14
- unless logged_in == :ok
15
- abort "[ERROR] (:logged_in) #{Hallon::Error.explain(logged_in)}"
16
- end
17
-
18
- conn_error = session.process_events_on(:connection_error) do |error|
19
- session.logged_in? or error
20
- end
21
-
22
- unless conn_error == true
23
- abort "[ERROR] (:connection_error) #{Hallon::Error.explain(conn_error)}"
13
+ session.process_events_on(:logged_in) { |error| Hallon::Error.maybe_raise(error) }
14
+ session.process_events_on(:connection_error) do |error|
15
+ session.logged_in? or Hallon::Error.maybe_raise(error)
24
16
  end
25
17
 
26
18
  puts "Successfully logged in!"
data/hallon.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
  gem.required_ruby_version = '~> 1.8'
20
20
 
21
21
  gem.add_dependency 'spotify', '~> 8.0.5'
22
- gem.add_development_dependency 'mockspotify', '~> 0.1.4'
22
+ gem.add_development_dependency 'mockspotify', '~> 0.1.7'
23
23
  gem.add_development_dependency 'rake', '~> 0.8'
24
24
  gem.add_development_dependency 'rspec', '~> 2'
25
25
  gem.add_development_dependency 'autotest-standalone'
data/lib/hallon.rb CHANGED
@@ -14,6 +14,7 @@ require 'hallon/session'
14
14
  require 'hallon/link'
15
15
  require 'hallon/user'
16
16
  require 'hallon/image'
17
+ require 'hallon/track'
17
18
 
18
19
  # The Hallon module wraps around all Hallon objects to avoid polluting
19
20
  # the global namespace. To start using Hallon, you most likely want to
data/lib/hallon/error.rb CHANGED
@@ -5,6 +5,13 @@ module Hallon
5
5
  # @see http://developer.spotify.com/en/libspotify/docs/group__error.html
6
6
  class Error < RuntimeError
7
7
  class << self
8
+ # Hash of error (Symbol) to code (Integer).
9
+ #
10
+ # @return [Hash<Symbol, Integer>]
11
+ def table
12
+ Spotify::enum_type(:error).to_hash
13
+ end
14
+
8
15
  # Given a number or a symbol, find both the symbol and the error
9
16
  # number it represents.
10
17
  #
@@ -1,11 +1,14 @@
1
+ # Extension of Object for Ruby 1.8 compatibility.
1
2
  class Object
2
- unless defined?(singleton_class)
3
+ unless method_defined?(:singleton_class)
4
+ # Singleton class of object.
3
5
  def singleton_class
4
6
  class << self; self; end
5
7
  end
6
8
  end
7
9
 
8
10
  unless method_defined?(:define_singleton_method)
11
+ # Defines a method on the singleton class of object.
9
12
  def define_singleton_method(*args, &b)
10
13
  singleton_class.send(:define_method, *args, &b)
11
14
  end
@@ -44,7 +44,9 @@ module Hallon
44
44
  Hallon::Link.new(link)
45
45
  end
46
46
 
47
- define_method(:to_link) { |*args, &block| self.class.to_link(@pointer, *args, &block) }
47
+ define_method(:to_link) do |*args, &block|
48
+ self.class.to_link(@pointer, *args, &block)
49
+ end
48
50
  end
49
51
  end
50
52
  end
@@ -0,0 +1,120 @@
1
+ # coding: utf-8
2
+ module Hallon
3
+ # Tracks are an essential part to the Spotify service. They
4
+ # are browsable entities that can also be played by streaming.
5
+ #
6
+ # @see http://developer.spotify.com/en/libspotify/docs/group__track.html
7
+ class Track
8
+ extend Linkable
9
+
10
+ from_link(:track) { |link, ptr| Spotify.link_as_track_and_offset(link, ptr) }
11
+ to_link(:track)
12
+
13
+ # Create a Link to the current track and offset in seconds.
14
+ #
15
+ # @param [Float] offset offset into track in seconds
16
+ # @return [Hallon::Link]
17
+ def to_link(offset = offset)
18
+ self.class.to_link(@pointer, (offset * 1000).to_i)
19
+ end
20
+
21
+ # Offset into track in seconds this track was created with.
22
+ #
23
+ # @return [Rational]
24
+ attr_reader :offset
25
+
26
+ # Underlying Spotify pointer.
27
+ #
28
+ # @private
29
+ # @return [FFI::Pointer]
30
+ attr_reader :pointer
31
+
32
+ # Construct a new Track instance.
33
+ #
34
+ # @param [String, Link, FFI::Pointer] link
35
+ def initialize(link)
36
+ FFI::MemoryPointer.new(:int) do |ptr|
37
+ @pointer = Spotify::Pointer.new from_link(link, ptr), :track, true
38
+ @offset = Rational(ptr.read_int, 1000)
39
+ end
40
+ end
41
+
42
+ # Create a new local track.
43
+ #
44
+ # @param [String] title
45
+ # @param [String] artist
46
+ # @param [String] album
47
+ # @param [Integer] length
48
+ # @return [Track]
49
+ def self.local(title, artist, album = nil, length = nil)
50
+ track = Spotify.localtrack_create(artist, title, album || "", length || -1)
51
+ new(track)
52
+ end
53
+
54
+ # @note This’ll be an empty string unless the track is loaded.
55
+ # @return [String]
56
+ def name
57
+ Spotify.track_name(@pointer)
58
+ end
59
+
60
+ # Duration of the track in seconds.
61
+ #
62
+ # @note This’ll be `0` unless the track is loaded.
63
+ # @return [Rational]
64
+ def duration
65
+ Rational(Spotify.track_duration(@pointer), 1000)
66
+ end
67
+
68
+ # Track popularity, between 0 and 1.
69
+ #
70
+ # @note This’ll be `0` unless the track is loaded.
71
+ # @return [Rational]
72
+ def popularity
73
+ Rational(Spotify.track_popularity(@pointer), 100)
74
+ end
75
+
76
+ # Disc number this track appears in.
77
+ #
78
+ # @note This function is a bit special. See libspotify docs for details.
79
+ def disc
80
+ Spotify.track_disc(@pointer)
81
+ end
82
+
83
+ # Position of track on its’ disc.
84
+ #
85
+ # @note This function is a bit special. See libspotify docs for details.
86
+ def index
87
+ Spotify.track_index(@pointer)
88
+ end
89
+
90
+ # Retrieve track error status.
91
+ #
92
+ # @return [Symbol]
93
+ def status
94
+ Spotify.track_error(@pointer)
95
+ end
96
+
97
+ # True if the track is loaded
98
+ #
99
+ # @return [Boolean]
100
+ def loaded?
101
+ Spotify.track_is_loaded(@pointer)
102
+ end
103
+
104
+ # Album this track belongs to.
105
+ #
106
+ # @note This’ll be `nil` unless the track is loaded.
107
+ # @return [Hallon::Album]
108
+ def album
109
+ album = Spotify.track_album(@pointer)
110
+ Hallon::Album.new(album) unless album.null?
111
+ end
112
+
113
+ # TODO: available?(session)
114
+ # TODO: local?(session)
115
+ # TODO: autolinked?(session)
116
+ # TODO: starred?(session)
117
+ # TODO: starred = true (session, array of tracks)
118
+ # TODO: artists (count, by index)
119
+ end
120
+ end
@@ -3,5 +3,5 @@ module Hallon
3
3
  # Current release version of Hallon
4
4
  #
5
5
  # @see http://semver.org/
6
- VERSION = [0, 2, 1].join('.')
6
+ VERSION = [0, 3, 0].join('.')
7
7
  end
@@ -7,5 +7,6 @@ def example_uris
7
7
  "spotify:user:burgestrand:playlist:4nQnbGi4kALbME9csEqdW2" => :playlist,
8
8
  "spotify:user:burgestrand" => :profile,
9
9
  "spotify:user:burgestrand:starred" => :starred,
10
+ "spotify:track:7N2Vc8u56VGA4KUrGbikC2#1:40" => :track
10
11
  }
11
12
  end
@@ -36,4 +36,10 @@ describe Hallon::Error do
36
36
  subject.maybe_raise(0).should eq :ok
37
37
  end
38
38
  end
39
+
40
+ describe "::table" do
41
+ it "should return a hash of symbol to integer" do
42
+ Hallon::Error.table[:ok].should eq 0
43
+ end
44
+ end
39
45
  end
@@ -0,0 +1,89 @@
1
+ # coding: utf-8
2
+ describe Hallon::Track, :session => true do
3
+ let(:artist) { Spotify.mock_artist("Artist", true) }
4
+ let(:album) { Spotify.mock_album("Album", artist, 2011, nil, :unknown, true, true) }
5
+
6
+ subject do
7
+ artists = FFI::MemoryPointer.new(:pointer)
8
+ artists.write_pointer artist
9
+
10
+ track = Spotify.mock_track(
11
+ "Elucteene", # name
12
+ 1, artists, # num_artists, artists
13
+ album, # album
14
+ 123_456, # duration
15
+ 42, # popularity
16
+ 2, 7, # disc, index
17
+ 0, true # error, loaded
18
+ )
19
+
20
+ artists.free
21
+
22
+ Hallon::Track.new(track)
23
+ end
24
+
25
+ its(:name) { should eq "Elucteene" }
26
+ its(:disc) { should be 2 }
27
+ its(:index) { should be 7 }
28
+ its(:status) { should be :ok }
29
+
30
+ its(:duration) { should eq 123.456 }
31
+ its(:popularity) { should eq 0.42 }
32
+
33
+ xit("artist.name") { should eq "Artist" }
34
+ xit("album.name") { should eq "Album" }
35
+
36
+ it { should be_loaded }
37
+
38
+ describe "album" do
39
+ it "should be an album when there is one" do
40
+
41
+ end
42
+
43
+ it "should be nil when there isn’t one" do
44
+ Spotify.should_receive(:track_album).and_return(FFI::Pointer.new(0))
45
+
46
+ subject.album.should be_nil
47
+ end
48
+ end
49
+
50
+ describe "to_link" do
51
+ it "should pass the current offset by default" do
52
+ subject.should_receive(:offset).and_return(10)
53
+ described_class.should_receive(:to_link).with(subject.pointer, 10_000)
54
+
55
+ subject.to_link
56
+ end
57
+
58
+ it "should accept offset as parameter" do
59
+ subject.should_not_receive(:offset)
60
+ described_class.should_receive(:to_link).with(subject.pointer, 1_337_000)
61
+
62
+ subject.to_link(1337)
63
+ end
64
+ end
65
+
66
+ describe "offset" do
67
+ let(:without_offset) { 'spotify:track:7N2Vc8u56VGA4KUrGbikC2' }
68
+ let(:with_offset) { without_offset + '#1:00' }
69
+
70
+ specify "with offset" do
71
+ Hallon::Track.new(with_offset).offset.should eq 60
72
+ end
73
+
74
+ specify "without offset" do
75
+ Hallon::Track.new(without_offset).offset.should eq 0
76
+ end
77
+ end
78
+
79
+ describe "a local track" do
80
+ subject do
81
+ Hallon::Track.local "Title", "Artist", "Coolio", 100
82
+ end
83
+
84
+ its(:name) { should eq "Title" }
85
+ pending("artist.name") { should eq "Artist" }
86
+ pending("album.name") { should eq "Album" }
87
+ its(:duration) { should eq 0.1 }
88
+ end
89
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hallon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-22 00:00:00.000000000Z
12
+ date: 2011-07-25 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: spotify
16
- requirement: &2157240440 !ruby/object:Gem::Requirement
16
+ requirement: &2161051120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,21 +21,21 @@ dependencies:
21
21
  version: 8.0.5
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2157240440
24
+ version_requirements: *2161051120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: mockspotify
27
- requirement: &2157239880 !ruby/object:Gem::Requirement
27
+ requirement: &2161050620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: 0.1.4
32
+ version: 0.1.7
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2157239880
35
+ version_requirements: *2161050620
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &2157239180 !ruby/object:Gem::Requirement
38
+ requirement: &2161050020 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0.8'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2157239180
46
+ version_requirements: *2161050020
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &2157232320 !ruby/object:Gem::Requirement
49
+ requirement: &2161049380 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '2'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2157232320
57
+ version_requirements: *2161049380
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: autotest-standalone
60
- requirement: &2157231660 !ruby/object:Gem::Requirement
60
+ requirement: &2161048660 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2157231660
68
+ version_requirements: *2161048660
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: autotest-growl
71
- requirement: &2157230760 !ruby/object:Gem::Requirement
71
+ requirement: &2161047680 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2157230760
79
+ version_requirements: *2161047680
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: yard
82
- requirement: &2157230240 !ruby/object:Gem::Requirement
82
+ requirement: &2161046920 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2157230240
90
+ version_requirements: *2161046920
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: rdiscount
93
- requirement: &2157229660 !ruby/object:Gem::Requirement
93
+ requirement: &2161044780 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,7 +98,7 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *2157229660
101
+ version_requirements: *2161044780
102
102
  description:
103
103
  email: kim@burgestrand.se
104
104
  executables: []
@@ -131,6 +131,7 @@ files:
131
131
  - lib/hallon/observable.rb
132
132
  - lib/hallon/session.rb
133
133
  - lib/hallon/synchronizable.rb
134
+ - lib/hallon/track.rb
134
135
  - lib/hallon/user.rb
135
136
  - lib/hallon/version.rb
136
137
  - spec/fixtures/example_uris.rb
@@ -144,6 +145,7 @@ files:
144
145
  - spec/hallon/observable_spec.rb
145
146
  - spec/hallon/session_spec.rb
146
147
  - spec/hallon/synchronizable_spec.rb
148
+ - spec/hallon/track_spec.rb
147
149
  - spec/hallon/user_spec.rb
148
150
  - spec/spec_helper.rb
149
151
  - spec/support/.gitkeep
@@ -187,6 +189,7 @@ test_files:
187
189
  - spec/hallon/observable_spec.rb
188
190
  - spec/hallon/session_spec.rb
189
191
  - spec/hallon/synchronizable_spec.rb
192
+ - spec/hallon/track_spec.rb
190
193
  - spec/hallon/user_spec.rb
191
194
  - spec/spec_helper.rb
192
195
  - spec/support/.gitkeep