hallon 0.0.0 → 0.1.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 (46) hide show
  1. data/.autotest +6 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +29 -0
  4. data/.rspec +7 -0
  5. data/.yardopts +8 -0
  6. data/CHANGELOG +20 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE.txt +21 -0
  9. data/QUIRKS +11 -0
  10. data/README.markdown +58 -0
  11. data/Rakefile +75 -0
  12. data/Termfile +7 -0
  13. data/examples/logging_in.rb +26 -0
  14. data/examples/printing_link_information.rb +27 -0
  15. data/hallon.gemspec +31 -0
  16. data/lib/hallon.rb +34 -0
  17. data/lib/hallon/error.rb +54 -0
  18. data/lib/hallon/ext/ffi.rb +26 -0
  19. data/lib/hallon/ext/spotify.rb +101 -0
  20. data/lib/hallon/image.rb +70 -0
  21. data/lib/hallon/link.rb +101 -0
  22. data/lib/hallon/linkable.rb +50 -0
  23. data/lib/hallon/observable.rb +91 -0
  24. data/lib/hallon/session.rb +189 -0
  25. data/lib/hallon/synchronizable.rb +32 -0
  26. data/lib/hallon/user.rb +69 -0
  27. data/lib/hallon/version.rb +7 -0
  28. data/spec/fixtures/example_uris.rb +11 -0
  29. data/spec/fixtures/pink_cover.jpg +0 -0
  30. data/spec/hallon/error_spec.rb +30 -0
  31. data/spec/hallon/ffi_spec.rb +5 -0
  32. data/spec/hallon/hallon_spec.rb +16 -0
  33. data/spec/hallon/image_spec.rb +41 -0
  34. data/spec/hallon/link_spec.rb +84 -0
  35. data/spec/hallon/linkable_spec.rb +43 -0
  36. data/spec/hallon/observable_spec.rb +103 -0
  37. data/spec/hallon/session_spec.rb +61 -0
  38. data/spec/hallon/synchronizable_spec.rb +19 -0
  39. data/spec/hallon/user_spec.rb +73 -0
  40. data/spec/spec_helper.rb +71 -0
  41. data/spec/support/.gitkeep +0 -0
  42. data/spec/support/context_initialized_session.rb +3 -0
  43. data/spec/support/context_logged_in.rb +16 -0
  44. data/spec/support/cover_me.rb +5 -0
  45. data/spec/support/shared_for_loadable_objects.rb +7 -0
  46. metadata +271 -96
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ require 'monitor'
3
+ require 'forwardable'
4
+
5
+ module Hallon
6
+ # Adds synchronization primitives to target when included.
7
+ module Synchronizable
8
+ # Creates a `Monitor` for the target instance and adds `monitor` class method for access.
9
+ #
10
+ # Also adds several other methods:
11
+ #
12
+ # - `#synchronize`
13
+ # - `#new_cond`
14
+ #
15
+ # These all delegate to `#monitor`.
16
+ #
17
+ # @note This module is part of Hallons private API
18
+ # @private
19
+ def self.included(o)
20
+ o.instance_exec do
21
+ @monitor = Monitor.new
22
+ class << self
23
+ attr_reader :monitor
24
+ end
25
+ end
26
+ end
27
+
28
+ extend Forwardable
29
+ def_delegators :monitor, :synchronize, :new_cond
30
+ def_delegators 'self.class', :monitor
31
+ end
32
+ end
@@ -0,0 +1,69 @@
1
+ # coding: utf-8
2
+ module Hallon
3
+ # Users are the entities that interact with the Spotify service.
4
+ #
5
+ # Methods are available for retrieving metadata and relationship
6
+ # status between users.
7
+ #
8
+ # @see http://developer.spotify.com/en/libspotify/docs/group__user.html
9
+ class User
10
+ extend Linkable
11
+
12
+ # @macro [attach] from_link
13
+ # Given a Link, get its’ underlying pointer.
14
+ #
15
+ # @method to_link
16
+ # @scope class
17
+ # @param [String, Hallon::Link, FFI::Pointer] link
18
+ # @return [FFI::Pointer]
19
+ from_link(:profile) { |link| Spotify::link_as_user(link) }
20
+
21
+ # @macro [attach] to_link
22
+ # Create a Link to the current object.
23
+ #
24
+ # @method to_link
25
+ # @scope instance
26
+ # @return [Hallon::Link]
27
+ to_link(:user)
28
+
29
+ # Used by {Session#relation_type?}
30
+ attr_reader :pointer
31
+
32
+ # Construct a new instance of User.
33
+ #
34
+ # @param [String, Link, FFI::Pointer] link
35
+ def initialize(link)
36
+ @pointer = Spotify::Pointer.new from_link(link), :user, true
37
+ end
38
+
39
+ # @return [Boolean] true if the user is loaded
40
+ def loaded?
41
+ Spotify::user_is_loaded(@pointer)
42
+ end
43
+
44
+ # Retrieve the name of the current user.
45
+ #
46
+ # @note Unless the user is {User#loaded?} only the canonical name is accessible
47
+ # @param [Symbol] type one of :canonical, :display, :full
48
+ # @return [String]
49
+ def name(type = :canonical)
50
+ case type
51
+ when :display
52
+ Spotify::user_display_name(@pointer)
53
+ when :full
54
+ Spotify::user_full_name(@pointer)
55
+ when :canonical
56
+ Spotify::user_canonical_name(@pointer)
57
+ else
58
+ raise ArgumentError, "expected type to be :display, :full or :canonical, but was #{type}"
59
+ end.to_s
60
+ end
61
+
62
+ # Retrieve the URL to the users’ profile picture.
63
+ #
64
+ # @return [String]
65
+ def picture
66
+ Spotify::user_picture(@pointer).to_s
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,7 @@
1
+ # coding: utf-8
2
+ module Hallon
3
+ # Current release version of Hallon
4
+ #
5
+ # @see http://semver.org/
6
+ VERSION = [0, 1, 0].join('.')
7
+ end
@@ -0,0 +1,11 @@
1
+ def example_uris
2
+ {
3
+ "spotify:search:omg%2bwtf%2b%ef%a3%bf%c3%9f%e2%88%82%2bbbq" => :search,
4
+ "spotify:track:3oN2Kq1h07LSSBSLYQp0Ns" => :track,
5
+ "spotify:album:6I58XCEkOnfUVsfpDehzlQ" => :album,
6
+ "spotify:artist:6MF9fzBmfXghAz953czmBC" => :artist,
7
+ "spotify:user:burgestrand:playlist:4nQnbGi4kALbME9csEqdW2" => :playlist,
8
+ "spotify:user:burgestrand" => :profile,
9
+ "spotify:user:burgestrand:starred" => :starred,
10
+ }
11
+ end
@@ -0,0 +1,30 @@
1
+ describe Hallon::Error do
2
+ subject { described_class }
3
+
4
+ it { should <= RuntimeError }
5
+
6
+ describe "::disambiguate" do
7
+ it "should not fail on invalid numbers" do
8
+ subject.disambiguate(10000).should eq [-1, nil]
9
+ end
10
+
11
+ it "should not fail on invalid symbols" do
12
+ subject.disambiguate(:fail).should eq [-1, nil]
13
+ end
14
+ end
15
+
16
+ describe "::explain" do
17
+ it { subject.explain(0).should eq 'No error' }
18
+ it { subject.explain(-1).should eq 'invalid error code' }
19
+ end
20
+
21
+ describe "::maybe_raise" do
22
+ it "should not raise error when given 0 as error code" do
23
+ expect { subject.maybe_raise(0) }.to_not raise_error
24
+ end
25
+
26
+ it "should raise error when given non-0 as error code" do
27
+ expect { subject.maybe_raise(1) }.to raise_error(Hallon::Error)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ describe FFI::Pointer do
2
+ it "should have a #read_size_t" do
3
+ described_class.instance_methods.should include :read_size_t
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ describe Hallon do
2
+ describe "VERSION" do
3
+ specify { Hallon::VERSION.should == "0.0.0" }
4
+ end
5
+
6
+ describe "API_VERSION" do
7
+ specify { Hallon::API_VERSION.should == 8 }
8
+ end
9
+
10
+ describe "URI" do
11
+ subject { Hallon::URI }
12
+ example_uris.keys.each do |uri|
13
+ it { should match uri }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+ describe Hallon::Image, :logged_in => true do
3
+ context "created from a Spotify URI" do
4
+ before(:all) do
5
+ @uri = "spotify:image:3ad93423add99766e02d563605c6e76ed2b0e450"
6
+ @image = Hallon::Image.new @uri
7
+ @image.status.should eq :is_loading
8
+ session.process_events_on { @image.loaded? }
9
+ end
10
+
11
+ subject { @image }
12
+
13
+ its(:status) { should be :ok }
14
+ its(:format) { should be :jpeg }
15
+
16
+ specify "its #id should match its’ spotify uri" do
17
+ @uri.should match @image.id
18
+ end
19
+
20
+ describe "#data" do
21
+ it "should correspond to the fixture image" do
22
+ @image.data.should eq File.read(fixture_image_path, :encoding => 'binary')
23
+ end
24
+
25
+ it "should have a binary encoding" do
26
+ @image.data.encoding.name.should eq 'ASCII-8BIT'
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "callbacks" do
32
+ it "should trigger :load when loaded" do
33
+ uri = "spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663"
34
+ image = Hallon::Image.new(uri)
35
+ image.should_not be_loaded
36
+ image.should_receive(:trigger).with(:load).once
37
+
38
+ session.process_events_on { image.loaded? }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,84 @@
1
+ describe Hallon::Link, :session => true do
2
+ subject { Hallon::Link.new("spotify:user:burgestrand") }
3
+
4
+ context "class methods" do
5
+ subject { described_class }
6
+
7
+ describe "::new" do
8
+ it "should raise an ArgumentError on an invalid link" do
9
+ expect { subject.new("omgwtfbbq") }.to raise_error(ArgumentError, /omgwtfbbq/)
10
+ end
11
+
12
+ it "should not raise error on valid links" do
13
+ expect { subject.new("spotify:user:burgestrand") }.to_not raise_error
14
+ end
15
+
16
+ it "should accept an FFI pointer" do
17
+ expect { subject.new FFI::Pointer.new(0) }.to raise_error(ArgumentError, /is not a valid Spotify link/)
18
+ end
19
+
20
+ it "should not initialize when given a Link" do
21
+ link = subject.new('spotify:user:burgestrand')
22
+ link.should_not_receive :to_str
23
+ subject.new link
24
+ end
25
+ end
26
+
27
+ describe "::valid?" do
28
+ it "should be true for a valid link" do
29
+ subject.valid?("spotify:user:burgestrand").should be_true
30
+ end
31
+
32
+ it "should be false for an invalid link" do
33
+ subject.valid?("omgwtfbbq").should be_false
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "#to_str" do
39
+ it "should return the Spotify URI as a string" do
40
+ subject.to_str.should == "spotify:user:burgestrand"
41
+ end
42
+
43
+ it "should truncate if given a small maximum length" do
44
+ subject.to_str(7).should == "spotify"
45
+ end
46
+ end
47
+
48
+ describe "#to_url" do
49
+ it "should return the correct http URL" do
50
+ subject.to_url.should == "http://open.spotify.com/user/burgestrand"
51
+ end
52
+ end
53
+
54
+ describe "#length" do
55
+ it { subject.length.should == "spotify:user:burgestrand".length }
56
+ end
57
+
58
+ describe "#type" do
59
+ example_uris.each_pair do |uri, type|
60
+ specify "#{uri} should equal #{type}" do
61
+ Hallon::Link.new(uri).type.should equal type
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "#to_s" do
67
+ it("should include the Spotify URI") do
68
+ subject.to_s.should include subject.to_str
69
+ end
70
+ end
71
+
72
+ describe "#==" do
73
+ it "should compare using #to_str" do
74
+ obj = Object.new
75
+ obj.should_receive(:to_str).and_return(subject.to_str)
76
+
77
+ subject.should eq obj
78
+ end
79
+
80
+ it "should not fail when #to_str is unavailable" do
81
+ subject.should_not eq Object.new
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ describe Hallon::Linkable do
2
+ it "should define the #from_link method" do
3
+ klass = Class.new
4
+ klass.should_not respond_to :from_link
5
+
6
+ klass.instance_exec do
7
+ extend Hallon::Linkable
8
+ from_link :foobar
9
+ end
10
+
11
+ klass.should respond_to :from_link
12
+ end
13
+
14
+ describe "#from_link" do
15
+ it "should call the given block if necessary" do
16
+ called = false
17
+ klass = Class.new
18
+
19
+ klass.instance_exec do
20
+ extend Hallon::Linkable
21
+ from_link(nil) { called = true }
22
+ end
23
+
24
+ klass.from_link("spotify:search:whatever")
25
+ called.should eq true
26
+ end
27
+
28
+ it "should pass extra parameters to the defining block" do
29
+ klass = Class.new
30
+
31
+ link = mock
32
+ link.stub(:pointer)
33
+ Hallon::Link.stub(:new => link)
34
+
35
+ klass.instance_exec do
36
+ extend Hallon::Linkable
37
+ from_link(nil) { |link, *args| args }
38
+ end
39
+
40
+ klass.from_link("spotify:user:burgestrand", :cool, 5).should eq [:cool, 5]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,103 @@
1
+ describe Hallon::Observable do
2
+ subject do
3
+ Class.new { include Hallon::Observable }.new
4
+ end
5
+
6
+ describe "instance methods" do
7
+ subject { described_class.instance_methods }
8
+
9
+ it { should include :on }
10
+ it { should include :trigger }
11
+ end
12
+
13
+ describe "#on" do
14
+ it "should allow defining one handler for multiple events" do
15
+ subject.on(:a, :b, :c) do |event, *args|
16
+ "yay"
17
+ end
18
+
19
+ subject.trigger(:a).should eq "yay"
20
+ subject.trigger(:b).should eq "yay"
21
+ subject.trigger(:c).should eq "yay"
22
+ end
23
+
24
+ specify "a multi-declared handler should know its name" do
25
+ subject.on(:a, :b) { |event, *args| event }
26
+ subject.trigger(:a).should eq :a
27
+ subject.trigger(:b).should eq :b
28
+ end
29
+
30
+ specify "a single-declared handler should not know its name" do
31
+ subject.on(:a) { |event, *args| event }
32
+ subject.trigger(:a).should eq nil
33
+ end
34
+ end
35
+
36
+ describe "#trigger and #on" do
37
+ it "should define and call event handlers" do
38
+ called = false
39
+ subject.on(:a) { called = true }
40
+ subject.trigger(:a)
41
+ called.should be_true
42
+ end
43
+
44
+ it "should pass any arguments to handlers" do
45
+ passed_args = []
46
+ subject.on(:a) { |*args| passed_args = args }
47
+ subject.trigger(:a, :b, :c)
48
+ passed_args.should eq [:b, :c]
49
+ end
50
+
51
+ it "should do nothing when there are no handlers" do
52
+ subject.trigger(:this_event_does_not_exist).should be_nil
53
+ end
54
+
55
+ context "multiple handlers" do
56
+ it "should call all handlers in order" do
57
+ triggered = []
58
+ subject.on(:a) { triggered << :a }
59
+ subject.on(:a) { triggered << :b }
60
+ subject.trigger(:a)
61
+ triggered.should eq [:a, :b]
62
+ end
63
+
64
+ it "should return the last-returned value" do
65
+ subject.on(:a) { :first }
66
+ subject.on(:a) { :second }
67
+ subject.trigger(:a).should eq :second
68
+ end
69
+
70
+ it "should allow execution to be aborted" do
71
+ subject.on(:a) { throw :return, :first }
72
+ subject.on(:b) { :second }
73
+ subject.trigger(:a).should eq :first
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#protecting_handlers" do
79
+ it "should call the given block, returning the result" do
80
+ was_called = false
81
+ subject.protecting_handlers { was_called = true }.should be_true
82
+ was_called.should be_true
83
+ end
84
+
85
+ it "should restore previous handlers on return" do
86
+ subject.on(:protected) { "before" }
87
+
88
+ subject.protecting_handlers do
89
+ subject.trigger(:protected).should eq "before"
90
+ subject.on(:protected) { "after" }
91
+ subject.trigger(:protected).should eq "after"
92
+ end
93
+
94
+ subject.trigger(:protected).should eq "before"
95
+ end
96
+
97
+ it "should still allow #trigger to work on non-defined events" do
98
+ subject.protecting_handlers {}
99
+ expect { subject.trigger(:does_not_exist) }.to_not raise_error
100
+ end
101
+ end
102
+
103
+ end