lastfm-path-finder 1.0.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/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README +15 -0
- data/VERSION +1 -0
- data/config/settings.example.yml +3 -0
- data/lastfm-path-finder.rb +33 -0
- data/lib/lastfm_path_finder/artist.rb +42 -0
- data/lib/lastfm_path_finder/finder.rb +119 -0
- data/lib/lastfm_path_finder/path.rb +22 -0
- data/lib/lastfm_path_finder/settings.rb +14 -0
- data/lib/lastfm_path_finder.rb +12 -0
- data/spec/models/artist.rb +45 -0
- data/spec/models/path.rb +49 -0
- data/spec/spec_helper.rb +69 -0
- metadata +113 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 David J. Brenes
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
This project is the implementation of a conversation between César Alvarez Doval and myself, a program which finds a path between two (apparently) unrelated artists.
|
2
|
+
|
3
|
+
== How to use it?
|
4
|
+
|
5
|
+
First, donwolad this repository and install the dependant gems:
|
6
|
+
|
7
|
+
gem install bundler
|
8
|
+
|
9
|
+
and then:
|
10
|
+
|
11
|
+
bundle install
|
12
|
+
|
13
|
+
and then run the command:
|
14
|
+
|
15
|
+
./lastfm-path-finder.rb "Pink Floyd" "Franz Ferdinand"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'commander/import'
|
5
|
+
require 'lib/lastfm_path_finder'
|
6
|
+
|
7
|
+
program :version, "1.0"
|
8
|
+
program :description, 'Finding paths between artists in Lastfm since 1888'
|
9
|
+
|
10
|
+
default_command :find
|
11
|
+
|
12
|
+
command :find do |c|
|
13
|
+
c.syntax = 'lastfm-path-finder find [options]'
|
14
|
+
c.summary = 'Find paths between two artists'
|
15
|
+
c.description = 'Find paths between two artists in Last.fm'
|
16
|
+
c.example 'Search the path between Pink Floyd and Franz Ferdinand', 'lastfm-path-finder find "Pink Floyd" "Franz Ferdinand"'
|
17
|
+
c.action do |args, options|
|
18
|
+
|
19
|
+
from_name = args.first || ask("One artist: ")
|
20
|
+
to_name = args[1] || ask("Another artist: ")
|
21
|
+
|
22
|
+
from = LastfmPathFinder::Artist.new(:name => from_name)
|
23
|
+
to = LastfmPathFinder::Artist.new(:name => to_name)
|
24
|
+
path = LastfmPathFinder::Finder.find from, to
|
25
|
+
|
26
|
+
if path.found?
|
27
|
+
say "Path found! #{path.artists.values.join(" --> ")}"
|
28
|
+
else
|
29
|
+
say "No path found"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
class LastfmPathFinder::Artist
|
3
|
+
|
4
|
+
include Redis::Objects
|
5
|
+
|
6
|
+
attr_accessor :id
|
7
|
+
|
8
|
+
value :name
|
9
|
+
sorted_set :related_artists
|
10
|
+
|
11
|
+
def initialize params
|
12
|
+
params.symbolize_keys!
|
13
|
+
self.id = params [:id] || (params[:name]).parameterize
|
14
|
+
self.name = params[:name]
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :related_artists_without_lastfm, :related_artists
|
18
|
+
|
19
|
+
def related_artists
|
20
|
+
related = related_artists_without_lastfm
|
21
|
+
related.blank? ? related_artists_in_lastfm : related
|
22
|
+
end
|
23
|
+
|
24
|
+
def related_artists_in_lastfm
|
25
|
+
artists = LastfmPathFinder::Settings.lastfm_api.artist.get_similar(self.name.value)
|
26
|
+
related_artists_without_lastfm.clear
|
27
|
+
artists.each do |artist|
|
28
|
+
related_artists_without_lastfm[artist["name"]] = artist["match"].to_f
|
29
|
+
end
|
30
|
+
related_artists_without_lastfm
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.find_in_lastfm name
|
34
|
+
begin
|
35
|
+
LastfmPathFinder::Artist.new(LastfmPathFinder::Settings.lastfm_api.artist.get_info(name))
|
36
|
+
rescue Lastfm::ApiError => e
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class LastfmPathFinder::Finder
|
2
|
+
|
3
|
+
# This method finds a way between the two artists. How?
|
4
|
+
# 0) We ensure that we don't already have a path for these artists
|
5
|
+
# 1) We get the related to From
|
6
|
+
# 2) We check if To is in this related list
|
7
|
+
# 3) If it's not, we get the related to To
|
8
|
+
# 4) We check if From is in this list
|
9
|
+
# 5) We check if both related lists share (at least, one artist)
|
10
|
+
# 6) We update the score tables and try in a recursive way to find a way between any pair of nodes of the relateds lists
|
11
|
+
def self.find from, to, options = {}
|
12
|
+
|
13
|
+
default_options = {max_length: 7, threshold: 0.4}
|
14
|
+
options.merge! default_options
|
15
|
+
|
16
|
+
currently_included = options[:currently_included] || []
|
17
|
+
currently_included = currently_included.dup
|
18
|
+
|
19
|
+
# 0)
|
20
|
+
|
21
|
+
path = LastfmPathFinder::Path.new from, to
|
22
|
+
return path if path.found?
|
23
|
+
|
24
|
+
currently_included.push from.name.value
|
25
|
+
currently_included.push to.name.value
|
26
|
+
|
27
|
+
# 1)
|
28
|
+
related_from = from.related_artists
|
29
|
+
|
30
|
+
# 2)
|
31
|
+
if related_from.member?(to.name.value)
|
32
|
+
score = related_from.score(to.name.value)
|
33
|
+
path = LastfmPathFinder::Path.new from, to
|
34
|
+
path.score = score
|
35
|
+
path.artists << from.name.value
|
36
|
+
path.artists << to.name.value
|
37
|
+
return path
|
38
|
+
end
|
39
|
+
|
40
|
+
# 3)
|
41
|
+
related_to = to.related_artists
|
42
|
+
|
43
|
+
# 4)
|
44
|
+
if related_from.member?(from.name.value)
|
45
|
+
score = related_to.score(from.name.value)
|
46
|
+
path = LastfmPathFinder::Path.new from, to
|
47
|
+
path.score = score
|
48
|
+
path.artists << from.name.value
|
49
|
+
path.artists << to.name.value
|
50
|
+
return path
|
51
|
+
end
|
52
|
+
|
53
|
+
# 5)
|
54
|
+
related_from_members = related_from.members
|
55
|
+
related_to_members = related_to.members
|
56
|
+
|
57
|
+
# We create an array of shared relates were we are gonna insert those artists present on both lists
|
58
|
+
# with an score wquals to the product of both scores, so we can find the most related to both artists
|
59
|
+
shared_relate = []
|
60
|
+
related_from_members.each do |name|
|
61
|
+
|
62
|
+
if related_to.member? name
|
63
|
+
score = related_from.score(name)
|
64
|
+
other_score = related_to.score(name)
|
65
|
+
shared_relate << {:name => name, :score => score*other_score}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# Now we get the first one and return a path with these three nodes
|
71
|
+
unless shared_relate.blank?
|
72
|
+
shared_contact = shared_relate.sort_by{|r|r[:score]}.last
|
73
|
+
|
74
|
+
path = LastfmPathFinder::Path.new from, to
|
75
|
+
path.score = shared_contact[:score]
|
76
|
+
|
77
|
+
path.artists << from.name.value
|
78
|
+
path.artists << shared_contact[:name]
|
79
|
+
path.artists << to.name.value
|
80
|
+
|
81
|
+
return path
|
82
|
+
end
|
83
|
+
|
84
|
+
#6)
|
85
|
+
|
86
|
+
# If the path is too long we don't look for longer paths
|
87
|
+
|
88
|
+
return path if currently_included.size > (options[:max_length]-2)
|
89
|
+
|
90
|
+
# We store the artists for the score
|
91
|
+
related_from_members.reverse!
|
92
|
+
related_to_members.reverse!
|
93
|
+
|
94
|
+
(related_from_members - currently_included).reject{|from_name|related_from.score(from_name) < options[:threshold] }.each do |from_name|
|
95
|
+
|
96
|
+
from_score = related_from.score(from_name)
|
97
|
+
|
98
|
+
(related_to_members - currently_included).reject{|to_name|related_to.score(to_name) < options[:threshold] }.each do |to_name|
|
99
|
+
|
100
|
+
to_score = related_to.score(to_name)
|
101
|
+
|
102
|
+
path = self.find(LastfmPathFinder::Artist.new(:name => from_name), LastfmPathFinder::Artist.new(:name => to_name), options.merge(currently_included: currently_included))
|
103
|
+
if path.found?
|
104
|
+
new_path = LastfmPathFinder::Path.new from, to
|
105
|
+
new_path.score = path.score * from_score * to_score
|
106
|
+
new_path.artists << from.name.value
|
107
|
+
path.artists.each {|a| new_path.artists << a }
|
108
|
+
new_path.artists << to.name.value
|
109
|
+
return new_path
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
path
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class LastfmPathFinder::Path
|
2
|
+
|
3
|
+
include Redis::Objects
|
4
|
+
|
5
|
+
attr_accessor :id
|
6
|
+
attr_accessor :artist_from, :artist_to
|
7
|
+
|
8
|
+
list :artists
|
9
|
+
value :score
|
10
|
+
|
11
|
+
def initialize from, to
|
12
|
+
self.artist_from = from
|
13
|
+
self.artist_to = to
|
14
|
+
self.id = "#{artist_from.id}_#{artist_to.id}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def found?
|
18
|
+
!artists.values.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'settingslogic'
|
2
|
+
require 'lastfm'
|
3
|
+
|
4
|
+
class LastfmPathFinder::Settings < Settingslogic
|
5
|
+
source "config/settings.yml"
|
6
|
+
|
7
|
+
def self.lastfm_api
|
8
|
+
@@lastfm ||= Lastfm.new(self["lastfm"]["api_key"], self["lastfm"]["api_secret"])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.redis_connection
|
12
|
+
Redis.current ||= Redis.new self["redis"]
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module LastfmPathFinder
|
2
|
+
require 'rubygems'
|
3
|
+
require 'lastfm'
|
4
|
+
require 'redis'
|
5
|
+
require 'redis/objects'
|
6
|
+
|
7
|
+
require_relative 'lastfm_path_finder/settings'
|
8
|
+
Settings.redis_connection
|
9
|
+
require_relative 'lastfm_path_finder/artist'
|
10
|
+
require_relative 'lastfm_path_finder/path'
|
11
|
+
require_relative 'lastfm_path_finder/finder'
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'lib/lastfm_path_finder'
|
2
|
+
|
3
|
+
describe LastfmPathFinder::Artist do
|
4
|
+
|
5
|
+
it "should have no data when not requested to LastFM" do
|
6
|
+
artist = LastfmPathFinder::Artist.new(:id => "pink-floyd")
|
7
|
+
artist.name.should be_nil
|
8
|
+
end
|
9
|
+
it "gets data for existing artists from Last.FM" do
|
10
|
+
|
11
|
+
VCR.use_cassette('lastfm', :record => :new_episodes) do
|
12
|
+
artist = LastfmPathFinder::Artist.find_in_lastfm "pink floyd"
|
13
|
+
artist.should_not be_nil
|
14
|
+
artist.id.should be_eql("pink-floyd")
|
15
|
+
artist.name.should == "Pink Floyd"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
it "handles non-existing artists when retrieving info from Last.FM" do
|
21
|
+
|
22
|
+
VCR.use_cassette('lastfm', :record => :new_episodes) do
|
23
|
+
artist = LastfmPathFinder::Artist.find_in_lastfm "who is pink?"
|
24
|
+
artist.should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
it "gets data about related artists on LastFM" do
|
30
|
+
|
31
|
+
VCR.use_cassette('lastfm', :record => :new_episodes) do
|
32
|
+
artist = LastfmPathFinder::Artist.new :name => "pink floyd"
|
33
|
+
related = artist.related_artists
|
34
|
+
related.should_not be_nil
|
35
|
+
|
36
|
+
related.members.last.should be_eql("David Gilmour")
|
37
|
+
related.score("David Gilmour").should be_eql(1.0)
|
38
|
+
related.members[-2].should be_eql("Roger Waters")
|
39
|
+
related.score("Roger Waters").should be_eql(0.778598)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
data/spec/models/path.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'lib/lastfm_path_finder'
|
2
|
+
|
3
|
+
describe LastfmPathFinder::Path do
|
4
|
+
|
5
|
+
let(:from) {LastfmPathFinder::Artist.new(:name => from_name)}
|
6
|
+
let(:to) {LastfmPathFinder::Artist.new(:name => to_name)}
|
7
|
+
let(:path) do
|
8
|
+
VCR.use_cassette('lastfm', :record => :new_episodes) do
|
9
|
+
LastfmPathFinder::Finder.find from, to
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when there's a direct path between artists" do
|
14
|
+
|
15
|
+
let(:from_name) {"Pink Floyd"}
|
16
|
+
let(:to_name) {"David Gilmour"}
|
17
|
+
|
18
|
+
subject { path }
|
19
|
+
it { should be_found }
|
20
|
+
it { path.artists.count.should be_eql(2) }
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when two artists share a common related artist" do
|
25
|
+
|
26
|
+
let(:from_name) {"Pink Floyd"}
|
27
|
+
let(:to_name) {"B.B. King & Eric Clapton"}
|
28
|
+
|
29
|
+
subject { path }
|
30
|
+
it { should be_found }
|
31
|
+
it { path.artists.count.should be_eql(3) }
|
32
|
+
it { path.artists.values.should == (["Pink Floyd","Eric Clapton","B.B. King & Eric Clapton"]) }
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
context "when two artists not share a common related artist" do
|
38
|
+
|
39
|
+
let(:from_name) {"Pink Floyd"}
|
40
|
+
let(:to_name) {"The Sunday Drivers"}
|
41
|
+
|
42
|
+
subject { path }
|
43
|
+
it { should be_found }
|
44
|
+
it { path.artists.count.should be_eql(7) }
|
45
|
+
it { path.artists.values.should == ( ["Pink Floyd", "David Gilmour", "Nick Mason", "Serú Girán", "Ismael Serrano", "Quique González", "The Sunday Drivers"]) }
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "bundler/setup"
|
3
|
+
require 'spork'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec-redis_helper'
|
6
|
+
#uncomment the following line to use spork with the debugger
|
7
|
+
#require 'spork/ext/ruby-debug'
|
8
|
+
|
9
|
+
Spork.prefork do
|
10
|
+
|
11
|
+
require 'vcr'
|
12
|
+
|
13
|
+
VCR.configure do |c|
|
14
|
+
c.allow_http_connections_when_no_cassette = true
|
15
|
+
c.cassette_library_dir = 'spec/vcr'
|
16
|
+
c.hook_into :webmock # or :fakeweb
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
RSpec.configure do |spec|
|
21
|
+
spec.include RSpec::RedisHelper
|
22
|
+
|
23
|
+
# slightly modified from RSpec::RedisHelper gem site https://github.com/mlanett/rspec-redis_helper
|
24
|
+
spec.around(:each) do |example|
|
25
|
+
with_clean_redis do
|
26
|
+
example.run
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Spork.each_run do
|
34
|
+
# This code will be run each time you run your specs.
|
35
|
+
end
|
36
|
+
|
37
|
+
# --- Instructions ---
|
38
|
+
# Sort the contents of this file into a Spork.prefork and a Spork.each_run
|
39
|
+
# block.
|
40
|
+
#
|
41
|
+
# The Spork.prefork block is run only once when the spork server is started.
|
42
|
+
# You typically want to place most of your (slow) initializer code in here, in
|
43
|
+
# particular, require'ing any 3rd-party gems that you don't normally modify
|
44
|
+
# during development.
|
45
|
+
#
|
46
|
+
# The Spork.each_run block is run each time you run your specs. In case you
|
47
|
+
# need to load files that tend to change during development, require them here.
|
48
|
+
# With Rails, your application modules are loaded automatically, so sometimes
|
49
|
+
# this block can remain empty.
|
50
|
+
#
|
51
|
+
# Note: You can modify files loaded *from* the Spork.each_run block without
|
52
|
+
# restarting the spork server. However, this file itself will not be reloaded,
|
53
|
+
# so if you change any of the code inside the each_run block, you still need to
|
54
|
+
# restart the server. In general, if you have non-trivial code in this file,
|
55
|
+
# it's advisable to move it into a separate file so you can easily edit it
|
56
|
+
# without restarting spork. (For example, with RSpec, you could move
|
57
|
+
# non-trivial code into a file spec/support/my_helper.rb, making sure that the
|
58
|
+
# spec/support/* files are require'd from inside the each_run block.)
|
59
|
+
#
|
60
|
+
# Any code that is left outside the two blocks will be run during preforking
|
61
|
+
# *and* during each_run -- that's probably not what you want.
|
62
|
+
#
|
63
|
+
# These instructions should self-destruct in 10 seconds. If they don't, feel
|
64
|
+
# free to delete them.
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lastfm-path-finder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David J. Brenes
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-07-28 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: lastfm
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis-objects
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: settingslogic
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: commander
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Gem for finding paths between artists in Last.fm
|
61
|
+
email: davidjbrenes@gmail.com
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files:
|
67
|
+
- LICENSE.txt
|
68
|
+
- README
|
69
|
+
files:
|
70
|
+
- Gemfile
|
71
|
+
- README
|
72
|
+
- VERSION
|
73
|
+
- config/settings.example.yml
|
74
|
+
- lastfm-path-finder.rb
|
75
|
+
- lib/lastfm_path_finder.rb
|
76
|
+
- lib/lastfm_path_finder/artist.rb
|
77
|
+
- lib/lastfm_path_finder/finder.rb
|
78
|
+
- lib/lastfm_path_finder/path.rb
|
79
|
+
- lib/lastfm_path_finder/settings.rb
|
80
|
+
- spec/models/artist.rb
|
81
|
+
- spec/models/path.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
- LICENSE.txt
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/brenes/lastfm-path-finder
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.6.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Finding paths between artists in Lastfm since 1888
|
112
|
+
test_files: []
|
113
|
+
|