lastfm-path-finder 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|