sandro-metamuse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.document +5 -0
  2. data/.gitignore +6 -0
  3. data/MIT_LICENSE +20 -0
  4. data/README.rdoc +7 -0
  5. data/Rakefile +48 -0
  6. data/VERSION +1 -0
  7. data/lib/array_ext.rb +5 -0
  8. data/lib/arrayish.rb +23 -0
  9. data/lib/httparty_ext.rb +16 -0
  10. data/lib/metamuse.rb +71 -0
  11. data/lib/metamuse/album.rb +19 -0
  12. data/lib/metamuse/artist.rb +43 -0
  13. data/lib/metamuse/association.rb +48 -0
  14. data/lib/metamuse/collection.rb +25 -0
  15. data/lib/metamuse/services.rb +16 -0
  16. data/lib/metamuse/services/echonest.rb +26 -0
  17. data/lib/metamuse/services/freebase.rb +43 -0
  18. data/lib/metamuse/services/lastfm.rb +27 -0
  19. data/lib/metamuse/services/lastfm/image.rb +41 -0
  20. data/lib/metamuse/services/music_brainz.rb +27 -0
  21. data/lib/metamuse/track.rb +13 -0
  22. data/lib/object_ext.rb +11 -0
  23. data/metamuse.gemspec +89 -0
  24. data/script/console +8 -0
  25. data/spec/fake_object.rb +38 -0
  26. data/spec/fake_object_spec.rb +66 -0
  27. data/spec/metamuse/album_spec.rb +4 -0
  28. data/spec/metamuse/artist_spec.rb +99 -0
  29. data/spec/metamuse/association_spec.rb +74 -0
  30. data/spec/metamuse/collection_spec.rb +4 -0
  31. data/spec/metamuse/services/echonest_spec.rb +28 -0
  32. data/spec/metamuse/services/freebase_spec.rb +24 -0
  33. data/spec/metamuse/services/lastfm/image_spec.rb +71 -0
  34. data/spec/metamuse/services/lastfm_spec.rb +30 -0
  35. data/spec/metamuse/services/music_brainz_spec.rb +12 -0
  36. data/spec/metamuse_spec.rb +21 -0
  37. data/spec/spec_helper.rb +27 -0
  38. data/spec/web_fixtures/GET_developer.echonest.com-api-search_artists_17a9da1.fixture +36 -0
  39. data/spec/web_fixtures/GET_developer.echonest.com-api-search_artists_350c352.fixture +36 -0
  40. data/spec/web_fixtures/GET_musicbrainz.org-ws-1-release-bb32aa1d-f37b-4134-8c0e-b43b7a6dab85_b976ba7.fixture +26 -0
  41. data/spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_60ee2bd.fixture +938 -0
  42. data/spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_b8b2565.fixture +107 -0
  43. metadata +106 -0
@@ -0,0 +1,27 @@
1
+ module Metamuse
2
+ module Services
3
+ module Lastfm
4
+ extend Service
5
+ include HTTParty
6
+ base_uri 'http://ws.audioscrobbler.com/2.0/'
7
+ has_api_key
8
+
9
+ def self.top_albums(name)
10
+ build_albums get('', :query => {:method => "artist.gettopalbums", :artist => name})
11
+ end
12
+
13
+ def self.artist(name)
14
+ end
15
+
16
+ private
17
+
18
+ def self.build_albums(response)
19
+ album_data = response['lfm']['topalbums']['album']
20
+ album_data.map do |data|
21
+ images = data['image'].map {|location| Image.new location}
22
+ Album.new :name => data['name'], :mbid => data['mbid'], :rank => data['rank'], :images => images
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ class Metamuse::Services::Lastfm::Image
2
+ attr_reader :location, :size
3
+
4
+ DIMENSIONS = {
5
+ :small => '34x34',
6
+ :medium => '64x64',
7
+ :large => '126x126',
8
+ :extra_large => '300x300'
9
+ }
10
+
11
+ def initialize(location)
12
+ @location = location
13
+ set_size
14
+ end
15
+
16
+ def dimensions
17
+ DIMENSIONS[size]
18
+ end
19
+
20
+ def height
21
+ dimensions.split("x").last
22
+ end
23
+
24
+ def width
25
+ dimensions.split("x").first
26
+ end
27
+
28
+ private
29
+
30
+ def set_size
31
+ @size = DIMENSIONS.index(dimension_value_from_location)
32
+ end
33
+
34
+ def dimension_value_from_location
35
+ size_regexp = %r(/serve/(\d+).*/)
36
+ match = location.match size_regexp
37
+ if match && width = match.captures.first
38
+ "#{width}x#{width}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module Metamuse
2
+ module Services
3
+ class MusicBrainz
4
+ include HTTParty
5
+ base_uri 'http://musicbrainz.org/ws/1'
6
+ default_params :type => :xml
7
+
8
+ def self.album(id)
9
+ build_album get("/release/#{id}/", :query => {:inc => :tracks})
10
+ end
11
+
12
+ private
13
+
14
+ def self.build_album(response)
15
+ release = response['metadata']['release']
16
+ album_data = {:name => release['title'], :mbid => release['id']}
17
+
18
+ track_list = release['track_list']['track']
19
+ tracks = track_list.map do |track|
20
+ Track.new :name => track['title'], :index => track_list.index(track), :mbid => track['id']
21
+ end
22
+
23
+ Album.new album_data.merge(:tracks => tracks)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Metamuse
2
+ class Track
3
+ include InstanceInitialize
4
+ extend Association
5
+ belongs_to :album, Album
6
+ attr_accessor :name, :index, :freebase_id, :mbid
7
+ alias number index
8
+
9
+ def <=>(other)
10
+ name <=> other.name
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class Object
2
+ def try(method)
3
+ send(method) if respond_to? method
4
+ end
5
+
6
+ def attempt
7
+ yield self
8
+ rescue StandardError, RuntimeError
9
+ self
10
+ end
11
+ end
@@ -0,0 +1,89 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{metamuse}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sandro Turriate"]
9
+ s.date = %q{2009-07-19}
10
+ s.email = %q{sandro.turriate@gmail.com}
11
+ s.extra_rdoc_files = [
12
+ "README.rdoc"
13
+ ]
14
+ s.files = [
15
+ ".document",
16
+ ".gitignore",
17
+ "MIT_LICENSE",
18
+ "README.rdoc",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "lib/array_ext.rb",
22
+ "lib/arrayish.rb",
23
+ "lib/httparty_ext.rb",
24
+ "lib/metamuse.rb",
25
+ "lib/metamuse/album.rb",
26
+ "lib/metamuse/artist.rb",
27
+ "lib/metamuse/association.rb",
28
+ "lib/metamuse/collection.rb",
29
+ "lib/metamuse/services.rb",
30
+ "lib/metamuse/services/echonest.rb",
31
+ "lib/metamuse/services/freebase.rb",
32
+ "lib/metamuse/services/lastfm.rb",
33
+ "lib/metamuse/services/lastfm/image.rb",
34
+ "lib/metamuse/services/music_brainz.rb",
35
+ "lib/metamuse/track.rb",
36
+ "lib/object_ext.rb",
37
+ "metamuse.gemspec",
38
+ "script/console",
39
+ "spec/fake_object.rb",
40
+ "spec/fake_object_spec.rb",
41
+ "spec/metamuse/album_spec.rb",
42
+ "spec/metamuse/artist_spec.rb",
43
+ "spec/metamuse/association_spec.rb",
44
+ "spec/metamuse/collection_spec.rb",
45
+ "spec/metamuse/services/echonest_spec.rb",
46
+ "spec/metamuse/services/freebase_spec.rb",
47
+ "spec/metamuse/services/lastfm/image_spec.rb",
48
+ "spec/metamuse/services/lastfm_spec.rb",
49
+ "spec/metamuse/services/music_brainz_spec.rb",
50
+ "spec/metamuse_spec.rb",
51
+ "spec/spec_helper.rb",
52
+ "spec/web_fixtures/GET_developer.echonest.com-api-search_artists_17a9da1.fixture",
53
+ "spec/web_fixtures/GET_developer.echonest.com-api-search_artists_350c352.fixture",
54
+ "spec/web_fixtures/GET_musicbrainz.org-ws-1-release-bb32aa1d-f37b-4134-8c0e-b43b7a6dab85_b976ba7.fixture",
55
+ "spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_60ee2bd.fixture",
56
+ "spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_b8b2565.fixture"
57
+ ]
58
+ s.has_rdoc = true
59
+ s.homepage = %q{http://github.com/sandro/metamuse}
60
+ s.rdoc_options = ["--charset=UTF-8"]
61
+ s.require_paths = ["lib"]
62
+ s.rubygems_version = %q{1.3.1}
63
+ s.summary = %q{Search for music metadata}
64
+ s.test_files = [
65
+ "spec/fake_object.rb",
66
+ "spec/fake_object_spec.rb",
67
+ "spec/metamuse/album_spec.rb",
68
+ "spec/metamuse/artist_spec.rb",
69
+ "spec/metamuse/association_spec.rb",
70
+ "spec/metamuse/collection_spec.rb",
71
+ "spec/metamuse/services/echonest_spec.rb",
72
+ "spec/metamuse/services/freebase_spec.rb",
73
+ "spec/metamuse/services/lastfm/image_spec.rb",
74
+ "spec/metamuse/services/lastfm_spec.rb",
75
+ "spec/metamuse/services/music_brainz_spec.rb",
76
+ "spec/metamuse_spec.rb",
77
+ "spec/spec_helper.rb"
78
+ ]
79
+
80
+ if s.respond_to? :specification_version then
81
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
82
+ s.specification_version = 2
83
+
84
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
85
+ else
86
+ end
87
+ else
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ libs << " -r lib/metamuse.rb"
7
+
8
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,38 @@
1
+ module FakeObject
2
+ def fake(*args)
3
+ Fake.new *args
4
+ end
5
+
6
+ class Fake
7
+ attr_reader :name, :strategy, :expectations
8
+
9
+ def initialize(*args)
10
+ @strategy = extract_strategy(args.last) || :stub
11
+ setup args
12
+ end
13
+
14
+ def setup(args)
15
+ case arg = args.shift
16
+ when Hash
17
+ set_expectations arg
18
+ when String
19
+ @name = arg
20
+ setup args
21
+ when NilClass
22
+ else
23
+ raise ArgumentError, "Hash excepted, got: #{args.inspect}"
24
+ end
25
+ end
26
+
27
+ def set_expectations(expectations)
28
+ expectations.each do |method_name, return_value|
29
+ ::RR.send(strategy, self).__send__(method_name).returns(return_value) # stub(object).method.returns(value)
30
+ end
31
+ end
32
+
33
+ def extract_strategy(arg)
34
+ arg[:strategy] if Hash === arg
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class FakeTestContext
4
+ include FakeObject
5
+ def stub(*args)
6
+ end
7
+ end
8
+
9
+ describe FakeObject do
10
+
11
+ context "#fake" do
12
+ before do
13
+ @context = FakeTestContext.new
14
+ end
15
+
16
+ it "sets the context when setting expectations" do
17
+ mock(FakeObject::Fake).new(:greeting => 'hi')
18
+ @context.fake(:greeting => 'hi')
19
+ end
20
+ end
21
+
22
+ describe Fake do
23
+ context "#new with no expectations" do
24
+ it "returns a fake object" do
25
+ FakeObject::Fake.new.should be_instance_of(Fake)
26
+ end
27
+
28
+ it "has a name" do
29
+ f = FakeObject::Fake.new 'fake'
30
+ f.name.should == 'fake'
31
+ end
32
+ end
33
+
34
+ context "#new with expectations" do
35
+ it "responds to fake method 'greeting'" do
36
+ f = FakeObject::Fake.new :greeting => 'hi'
37
+ f.greeting.should == 'hi'
38
+ end
39
+
40
+ it "responds has a name" do
41
+ f = FakeObject::Fake.new 'Greeter', :greeting => 'hi'
42
+ f.name.should == 'Greeter'
43
+ end
44
+
45
+ it "uses the mock build strategy" do
46
+ f = FakeObject::Fake.new({:greeting => 'hi'}, :strategy => :mock)
47
+ f.greeting
48
+ f.strategy.should == :mock
49
+ end
50
+ end
51
+
52
+ context "Hash not provided" do
53
+ it "raises ArgumentError for an array" do
54
+ expect {
55
+ FakeObject::Fake.new []
56
+ }.to raise_error(ArgumentError)
57
+ end
58
+
59
+ it "raises ArgumentError for a name and array" do
60
+ expect {
61
+ FakeObject::Fake.new 'name', []
62
+ }.to raise_error(ArgumentError)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Metamuse::Album do
4
+ end
@@ -0,0 +1,99 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Metamuse::Artist do
4
+ subject { Metamuse::Artist.new :name => 'Mozart' }
5
+
6
+ describe ".build_artist" do
7
+ subject { Metamuse::Artist }
8
+ it "gets and enhances freebase artist information" do
9
+ mock.proxy(Metamuse::Services::Freebase).artist('Mozart') do |artist|
10
+ mock(artist).enhance_albums!
11
+ artist
12
+ end
13
+ subject.build('Mozart')
14
+ end
15
+ end
16
+
17
+ describe "#tracks" do
18
+ it "collects all of the tracks from the every album" do
19
+ album1 = fake(:tracks => [1,2])
20
+ album2 = fake(:tracks => [3,4])
21
+ stub(subject).albums { [album1, album2] }
22
+ subject.tracks.should == [1,2,3,4]
23
+ end
24
+ end
25
+
26
+ describe "#enhance_albums!" do
27
+ context "when albums exist" do
28
+ before do
29
+ @image = fake('image')
30
+ @lastfm_albums = [
31
+ Metamuse::Album.new(:name => 'Mild', :rank => 3, :mbid => 1),
32
+ Metamuse::Album.new(:name => 'Hot', :rank => 1, :mbid => 2, :images => [@image]),
33
+ Metamuse::Album.new(:name => 'Medium', :rank => 2, :mbid => 3)
34
+ ]
35
+ stub(subject).lastfm_albums { @lastfm_albums }
36
+ subject.albums = [{:name => "Mild"}, {:name => 'Hot'}, {:name => 'Medium'}]
37
+ end
38
+
39
+ it "uses albums from Last.fm" do
40
+ subject.enhance_albums!
41
+ subject.should have_received(:lastfm_albums).any_times
42
+ end
43
+
44
+ it "sets the album's mbid" do
45
+ subject.enhance_albums!.first.mbid.should == 2
46
+ end
47
+
48
+ it "sets the album's images collection" do
49
+ subject.enhance_albums!.first.images.should include(@image)
50
+ end
51
+
52
+ it "sets the album's rank" do
53
+ subject.enhance_albums!.first.rank.should == 1
54
+ end
55
+
56
+ context "rankings" do
57
+ it "sets rank to nil when the lastfm album is unranked" do
58
+ ranked = Metamuse::Album.new
59
+ stub(subject).lastfm_albums { [ranked] }
60
+ subject.enhance_albums!.first.rank.should be_nil
61
+ end
62
+
63
+ it "sorts based on rank" do
64
+ ranked = [Metamuse::Album.new(:name => 'Mild', :rank => 3), Metamuse::Album.new(:name => 'Hot', :rank => 1), Metamuse::Album.new(:name => 'Medium', :rank => 2)]
65
+ stub(subject).ranked_albums { ranked }
66
+ subject.enhance_albums!
67
+ subject.albums.map{|a| a.name}.should == %w(Hot Medium Mild)
68
+ end
69
+ end
70
+ end
71
+
72
+ context "artist has no albums" do
73
+ it "returns albums if the artist has no albums" do
74
+ albums = []
75
+ mock(subject).albums.any_times { albums }
76
+ subject.enhance_albums!.should == albums
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#fetch_albums_and_tracks!" do
82
+ it "fetches artist information from Freebase" do
83
+ mock.proxy(Metamuse::Services::Freebase).artist("Mozart") do |freebase_artist|
84
+ mock(subject).albums=(freebase_artist.albums)
85
+ freebase_artist
86
+ end
87
+ subject.fetch_albums_and_tracks!
88
+ end
89
+
90
+ it "assigns the tracks that were retrieved from Freebase" do
91
+ freebase_artist = nil
92
+ stub.proxy(Metamuse::Services::Freebase).artist('Mozart') do |fba|
93
+ freebase_artist = fba
94
+ end
95
+ subject.fetch_albums_and_tracks!
96
+ subject.tracks.should == freebase_artist.tracks
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class TestShoe
4
+ extend Metamuse::Association
5
+ attr_accessor :color
6
+ def initialize(attrs={})
7
+ attrs.each {|k,v| send(:"#{k}=", v)}
8
+ end
9
+ end
10
+
11
+ class TestPerson
12
+ extend Metamuse::Association
13
+ has_many :shoes, TestShoe
14
+ end
15
+ TestShoe.class_eval { belongs_to :person, TestPerson }
16
+
17
+ describe Metamuse::Association do
18
+ it "creates a shoes accessor which returns an array" do
19
+ TestPerson.new.shoes.should be_kind_of(Array)
20
+ end
21
+
22
+ describe "adding shoes to the test person" do
23
+ before do
24
+ @person = TestPerson.new
25
+ end
26
+
27
+ it "adds a single red shoe" do
28
+ expect {
29
+ @person.shoes << {:color => "red"}
30
+ }.to change{@person.shoes.size}.by(1)
31
+ end
32
+
33
+ it "creates a new shoe when not appending a TestShoe" do
34
+ mock.proxy(TestShoe).new({:color => 'red'})
35
+ @person.shoes << {:color => 'red'}
36
+ @person.shoes.last.color == 'red'
37
+ end
38
+
39
+ it "appends a TestShoe" do
40
+ @person.shoes << TestShoe.new(:color => "red")
41
+ @person.shoes.last.should be_instance_of(TestShoe)
42
+ end
43
+
44
+ it "appends many shoes" do
45
+ @person.shoes << [TestShoe.new(:color => "red"), {:color => "blue"}]
46
+ @person.shoes.size.should == 2
47
+ @person.shoes.map {|s| s.class}.uniq.should == [TestShoe]
48
+ end
49
+
50
+ it "clears old shoes when setting the collection" do
51
+ @person.shoes << {:color => 'blue'}
52
+ expect {
53
+ @person.shoes = {:color => 'red'}
54
+ }.to change{@person.shoes.first.color}.from('blue').to('red')
55
+ @person.shoes.size.should == 1
56
+ end
57
+ end
58
+
59
+ describe "TestShoe belongs to a TestPerson" do
60
+ before do
61
+ @person = TestPerson.new
62
+ end
63
+
64
+ it "sets the shoe's person equal to the person that added the shoe" do
65
+ @person.shoes << {:color => 'red', :person => @person}
66
+ @person.shoes.last.person.should == @person
67
+ end
68
+
69
+ it "sets the shoe's person when not explicitly available in the hash" do
70
+ @person.shoes << {:color => 'red'}
71
+ @person.shoes.last.person.should == @person
72
+ end
73
+ end
74
+ end