sandro-metamuse 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 (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