phish_dot_net_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .DS_Store
2
+ coverage/*
3
+ data/*
4
+ tmp/*
5
+ .tags
6
+ .tags_sorted_by_file
7
+ .yardoc/*
8
+ doc/*
9
+ .ruby-gemset
10
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rest-client', '~> 1.6.7'
4
+ gem 'json', '~> 1.8.0'
5
+ gem 'nokogiri', '~> 1.6.0'
6
+
7
+ group :development do
8
+ gem 'rake'
9
+ gem 'rspec'
10
+ gem 'yard'
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.2.4)
5
+ json (1.8.0)
6
+ mime-types (1.25)
7
+ mini_portile (0.5.1)
8
+ nokogiri (1.6.0)
9
+ mini_portile (~> 0.5.0)
10
+ rake (10.1.0)
11
+ rest-client (1.6.7)
12
+ mime-types (>= 1.16)
13
+ rspec (2.14.1)
14
+ rspec-core (~> 2.14.0)
15
+ rspec-expectations (~> 2.14.0)
16
+ rspec-mocks (~> 2.14.0)
17
+ rspec-core (2.14.5)
18
+ rspec-expectations (2.14.3)
19
+ diff-lcs (>= 1.1.3, < 2.0)
20
+ rspec-mocks (2.14.3)
21
+ yard (0.8.7.2)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ json (~> 1.8.0)
28
+ nokogiri (~> 1.6.0)
29
+ rake
30
+ rest-client (~> 1.6.7)
31
+ rspec
32
+ yard
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Phish.net API Client
2
+
3
+ A Phish.net API client, with support for parsing the 'setlistdata' field. Inspired by the other Ruby Phish.net API [client](http://api.phish.net/wrappers/ruby.php) written by [Stephen Blackstone](http://phish.net/user/steveb3210).
4
+
5
+ [rdoc](http://rubydoc.info/github/alexebird/phish_dot_net_client/master/frames/file/README.md)
6
+
7
+
8
+ ## Install
9
+
10
+ Gemfile:
11
+
12
+ gem 'phish_dot_net_client'
13
+
14
+ Rubygems:
15
+
16
+ gem install phish_dot_net_client
17
+
18
+
19
+ ## Usage
20
+
21
+ # Setup frequently used parameters
22
+ PhishDotNetClient.apikey = 'private-api-key'
23
+ PhishDotNetClient.authorize 'fluffhead', 'passwurd'
24
+ PhishDotNetClient.clear_auth # clears the stored apikey, username, and authkey
25
+
26
+ # Call API methods by replacing '.' with '_' in the method name
27
+ PhishDotNetClient.pnet_shows_setlists_latest
28
+
29
+ # The 'pnet_' prefix is optional
30
+ PhishDotNetClient.shows_setlists_latest
31
+
32
+ # Pass arguments to the API call with a hash
33
+ PhishDotNetClient.shows_setlists_get :showdate => '2013-07-31'
34
+ PhishDotNetClient.shows_query :month => '7', :country => 'USA', :state => 'CA'
35
+
36
+ # Arguments are not checked for validity before being passed
37
+ PhishDotNetClient.shows_setlists_get :fluff => 'head' # returns {"success" => 0,"reason" => "General API Error"}
38
+
39
+ # All methods return JSON parsed into Ruby Hashes/Arrays
40
+ tweez = PhishDotNetClient.shows_setlists_get :showdate => '2013-07-31'
41
+ # => [
42
+ # {
43
+ # "showdate" => "2013-07-31",
44
+ # "city" => "Stateline",
45
+ # "state" => "NV",
46
+ # "setlistdata" => #<PhishDotNetClient::Setlist ...>
47
+
48
+ See [Phish.net API docs](http://api.phish.net/docu/) for available API methods.
49
+
50
+
51
+ ### setlistdata Parsing
52
+
53
+ JSON objects that have a "setlistdata" will have that field parsed and replaced with
54
+ a [Setlist](http://rubydoc.info/github/alexebird/phish_dot_net_client/master/PhishDotNetClient/Setlist) object. See rdoc for how to use the parsed setlist.
55
+
56
+
57
+ ## Known Issues
58
+
59
+ - Song titles with a ',' character don't get parsed correctly due to the ',' being interpreted as a boundary between song titles. See lib/phish_dot_net_client/setlist.rb#133 for the regex and parsing code.
60
+
61
+
62
+
63
+ ## Testing
64
+
65
+ To run specs:
66
+
67
+ bundle exec rake spec
68
+
69
+ Pull requests are welcome!
70
+
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+
4
+ desc "Parse the documentation from Phish.net to get the current method calls"
5
+ task :parse_method_docs do
6
+ # require 'nokogiri'
7
+ require 'open-uri'
8
+
9
+ doc_url = "http://api.phish.net/docu/navigation.php"
10
+ methods = {}
11
+ longest_method_name = 0
12
+ doc = Nokogiri::HTML(open(doc_url))
13
+
14
+ doc.css("#static-list li.sub span").each_slice(2) do |method,scope|
15
+ method = method.content
16
+ if method.size > longest_method_name
17
+ longest_method_name = method.size
18
+ end
19
+ methods[method] = { :scope => scope.content }
20
+ end
21
+
22
+ puts "{"
23
+ methods.keys.sort.each_with_index do |method,i|
24
+ comma = (i+1) == methods.size ? "" : ","
25
+ puts %[ %-#{longest_method_name + 3}s => { :scope => %s }%s] %
26
+ ['"' + method.to_s + '"', '"' + methods[method][:scope] + '"', comma]
27
+ end
28
+ puts "}"
29
+ end
30
+
31
+
32
+ desc "Default: run specs. Set COVERAGE=true before any spec-related task to generate code coverage."
33
+ task :default => :spec
34
+
35
+ desc "Run all specs"
36
+ RSpec::Core::RakeTask.new(:spec) do |t|
37
+ t.pattern = "./spec/**/*_spec.rb"
38
+ # t.verbose = false
39
+ end
40
+
41
+ desc "Run specs in spec/units"
42
+ RSpec::Core::RakeTask.new('spec:units') do |t|
43
+ t.pattern = "./spec/units/**/*_spec.rb"
44
+ end
45
+
46
+ desc "Run specs in spec/integration"
47
+ RSpec::Core::RakeTask.new('spec:integration') do |t|
48
+ t.pattern = "./spec/integration/**/*_spec.rb"
49
+ end
50
+
51
+ desc "Generate documentation"
52
+ task :doc do
53
+ require 'yard'
54
+ sh "yard"
55
+ end
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+
3
+ require 'restclient'
4
+ require 'json'
5
+ require 'nokogiri'
6
+
7
+ require 'phish_dot_net_client/set'
8
+ require 'phish_dot_net_client/setlist'
9
+ require 'phish_dot_net_client/song'
10
+ require 'phish_dot_net_client/song_transition'
11
+ require 'phish_dot_net_client/version'
12
+
13
+ # @todo write doc
14
+ module PhishDotNetClient
15
+ extend self
16
+
17
+ # The possible API methods. Generated from +rake parse_method_docs+.
18
+ # API_METHODS =
19
+ # {
20
+ # "pnet.api.authkey.get" => { :scope => "protected" },
21
+ # "pnet.api.authorize" => { :scope => "protected" },
22
+ # "pnet.api.authorized.check" => { :scope => "protected" },
23
+ # "pnet.api.isAuthorized" => { :scope => "protected" },
24
+ # "pnet.artists.get" => { :scope => "public" },
25
+ # "pnet.blog.get" => { :scope => "public" },
26
+ # "pnet.blog.item.get" => { :scope => "public" },
27
+ # "pnet.collections.get" => { :scope => "protected" },
28
+ # "pnet.collections.query" => { :scope => "protected" },
29
+ # "pnet.forum.canpost" => { :scope => "protected" },
30
+ # "pnet.forum.get" => { :scope => "public" },
31
+ # "pnet.forum.thread.get" => { :scope => "protected" },
32
+ # "pnet.forum.thread.new" => { :scope => "protected" },
33
+ # "pnet.forum.thread.respond" => { :scope => "protected" },
34
+ # "pnet.news.comments.get" => { :scope => "public" },
35
+ # "pnet.news.get" => { :scope => "public" },
36
+ # "pnet.reviews.query" => { :scope => "protected" },
37
+ # "pnet.reviews.recent" => { :scope => "public" },
38
+ # "pnet.shows.links.get" => { :scope => "protected" },
39
+ # "pnet.shows.query" => { :scope => "protected" },
40
+ # "pnet.shows.setlists.get" => { :scope => "protected" },
41
+ # "pnet.shows.setlists.latest" => { :scope => "public" },
42
+ # "pnet.shows.setlists.random" => { :scope => "public" },
43
+ # "pnet.shows.setlists.recent" => { :scope => "public" },
44
+ # "pnet.shows.setlists.tiph" => { :scope => "public" },
45
+ # "pnet.shows.upcoming" => { :scope => "public" },
46
+ # "pnet.user.get" => { :scope => "protected" },
47
+ # "pnet.user.myshows.add" => { :scope => "protected" },
48
+ # "pnet.user.myshows.get" => { :scope => "protected" },
49
+ # "pnet.user.myshows.remove" => { :scope => "protected" },
50
+ # "pnet.user.register" => { :scope => "protected" },
51
+ # "pnet.user.shows.rate" => { :scope => "protected" },
52
+ # "pnet.user.uid.get" => { :scope => "protected" },
53
+ # "pnet.user.username.check" => { :scope => "protected" }
54
+ # }
55
+
56
+ # The base URL for API calls
57
+ BASE_URL = "https://api.phish.net/api.js"
58
+
59
+ # "https://api.phish.net/api.js?api=2.0&method=pnet.shows.query&format=json&apikey=920FF765772E442F3E22&year=2011"
60
+
61
+ @@default_params = { api: "2.0",
62
+ format: "json" }
63
+
64
+ def apikey=(private_api_key)
65
+ @@default_params.merge!(:apikey => private_api_key)
66
+ end
67
+
68
+ def authorize(username=nil, passwd=nil)
69
+ resp = call_api_method("pnet.api.authorize", :username => username, :passwd => passwd)
70
+
71
+ if resp['success'] == 1 && resp.has_key?('authkey')
72
+ @@default_params.merge!(:username => username, :authkey => resp['authkey'])
73
+ end
74
+ end
75
+
76
+ def clear_auth
77
+ [:apikey, :username, :authkey].each { |key| @@default_params.delete(key) }
78
+ end
79
+
80
+ # @param api_method [String] the method to call
81
+ # @param params [Hash] the url parameters for the api call
82
+ #
83
+ # @return [Hash, Array] the parsed JSON of API response
84
+ # @api private
85
+ def call_api_method(api_method, params={})
86
+ # method_data = API_METHODS[api_method]
87
+ # ensure_api_key if method_data[:scope] == "protected"
88
+
89
+ params.merge!(:method => api_method)
90
+ response = RestClient.get BASE_URL, { :params => @@default_params.merge(params) }
91
+
92
+ if response.code < 200 || response.code > 299
93
+ raise "non 2xx reponse: status=#{response.code}"
94
+ end
95
+
96
+ parsed = JSON.parse(response)
97
+
98
+ if parsed.is_a?(Array)
99
+ parsed.each do |obj|
100
+ obj["setlistdata"] = Setlist.new(obj["setlistdata"]) if obj.has_key?("setlistdata")
101
+ end
102
+ elsif parsed.is_a?(Hash)
103
+ parsed["setlistdata"] = Setlist.new(parsed["setlistdata"]) if parsed.has_key?("setlistdata")
104
+ end
105
+
106
+ return parsed
107
+ end
108
+
109
+ # @api private
110
+ def method_missing(name, *args)
111
+ api_method = get_api_method(name)
112
+
113
+ if api_method
114
+ call_api_method(api_method, *args)
115
+ else
116
+ super(name, *args)
117
+ end
118
+ end
119
+
120
+ # private
121
+
122
+ # @api private
123
+ def get_api_method(rb_method_name)
124
+ api_method_name = rb_method_name.to_s.gsub("_", ".")
125
+
126
+ unless api_method_name.match(/\Apnet\./)
127
+ api_method_name = 'pnet.' + api_method_name
128
+ end
129
+
130
+ return api_method_name
131
+
132
+ # if API_METHODS.has_key?(api_method_name)
133
+ # return api_method_name
134
+ # else
135
+ # return nil
136
+ # end
137
+ end
138
+
139
+ # def ensure_api_key
140
+ # raise "api key is required" if @@default_params[:apikey].nil?
141
+ # end
142
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ # @api private
4
+ module PhishDotNetClient
5
+ class Set
6
+
7
+ attr_reader :position
8
+ attr_reader :name
9
+ attr_reader :songs
10
+
11
+ def initialize(attrs={})
12
+ @songs = []
13
+ @position = attrs[:position]
14
+ @name = attrs[:name]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,182 @@
1
+ # encoding: utf-8
2
+
3
+ # @api private
4
+ module PhishDotNetClient
5
+ class Setlist
6
+
7
+ attr_reader :sets
8
+ attr_reader :footnotes
9
+
10
+ def initialize(setlistdata)
11
+ @sets, @footnotes = self.class.parse_setlistdata(setlistdata)
12
+ end
13
+
14
+ def songs
15
+ return @sets.map(&:songs).reduce([]){|memo,songs| memo += songs }
16
+ end
17
+
18
+ # @api private
19
+ def self.parse_setlistdata(setlistdata)
20
+ doc = Nokogiri::HTML(setlistdata)
21
+
22
+ footnotes = {}
23
+ footnotes_text = doc.css('.pnetfootnotes').first
24
+ if footnotes_text
25
+ footnotes_text = footnotes_text.content.split(/(?=\[\d+\])/)
26
+ footnotes_text.each do |note|
27
+ note.sub!(/\[(?<num>\d+)\]/, '').strip!
28
+ num = $~[:num]
29
+ footnotes[num.to_i] = { text: note }
30
+ end
31
+ end
32
+
33
+ sets = []
34
+ all_songs = []
35
+ transitions_tokens = []
36
+ slug_instances = {}
37
+
38
+ setnodes = doc.css(".pnetset")
39
+ position_in_show = 1
40
+
41
+ setnodes.each_with_index do |n,set_index|
42
+ setname = n.css(".pnetsetlabel").first.content
43
+ set = Set.new(:name => setname.sub!(/:\z/, ''), :position => set_index + 1)
44
+ songs_doc = n.css("a")
45
+ songs_doc.each_with_index do |song,song_index|
46
+ position_in_set = song_index + 1
47
+ title = song.content
48
+ url = song.attr('href')
49
+ slug = URI.parse(url).path
50
+ slug.sub!(/\A\/song\//, '')
51
+
52
+ slug_instances[slug] ||= 0
53
+ slug_instances[slug] += 1
54
+
55
+ song = Song.new(:title => title,
56
+ :url => url,
57
+ :slug => slug,
58
+ :instance => slug_instances[slug],
59
+ :position_in_set => position_in_set,
60
+ :position_in_show => position_in_show)
61
+ set.songs << song
62
+ all_songs << song
63
+ position_in_show += 1
64
+ end
65
+
66
+ transitions_text = n.content
67
+ transitions_tokens += tokenize_transitions_text(transitions_text, transitions_tokens)
68
+
69
+ sets << set
70
+ end
71
+
72
+ augment_songs(transitions_tokens, all_songs, footnotes)
73
+
74
+ return sets, footnotes
75
+ end
76
+
77
+ # @api private
78
+ def self.augment_songs(tokens, songs, footnotes)
79
+ songs = Array.new(songs) # make a copy
80
+
81
+ get_song_by_title = lambda do |title,instance|
82
+ songs.each do |song|
83
+ if song.title == title && song.instance == instance
84
+ return song
85
+ end
86
+ end
87
+ raise "no song with that title ('#{title}')"
88
+ end
89
+
90
+ tokens.each_index do |i|
91
+ prev_i = i >= 1 ? i - 1 : nil
92
+ next_i = i < tokens.size - 1 ? i + 1 : nil
93
+
94
+ token = tokens[i]
95
+ case token[:type]
96
+ when :note_ref
97
+ prev_token = tokens[prev_i]
98
+ song = get_song_by_title.call(prev_token[:title], prev_token[:instance])
99
+ num = token[:number]
100
+ footnotes[num][:song] = song
101
+ song.footnotes << num
102
+ when :transition
103
+ prev_token = tokens[prev_i]
104
+ if prev_token[:type] == :note_ref
105
+ prev_token = tokens[prev_i-1]
106
+ end
107
+ next_token = tokens[next_i]
108
+ from_song = get_song_by_title.call(prev_token[:title], prev_token[:instance])
109
+ to_song = get_song_by_title.call(next_token[:title], next_token[:instance])
110
+ PhishDotNetClient::SongTransition.new(token[:transition_type], from_song, to_song)
111
+ end
112
+ end
113
+ end
114
+
115
+ # @api private
116
+ def self.tokenize_transitions_text(transitions_text, existing_tokens=[])
117
+ tokens = []
118
+
119
+ # Account for multiple songs with the same title in a song list
120
+ song_title_counts = {}
121
+ existing_tokens.each do |token|
122
+ if token[:type] == :song
123
+ title = token[:title]
124
+ song_title_counts[title] ||= 0
125
+ song_title_counts[title] += 1
126
+ end
127
+ end
128
+
129
+ tokens.push(/\A[a-z0-9\s]+:/i => lambda do |match|
130
+ return { type: :set_name, name: match.to_s.strip.sub(':', '') }
131
+ end)
132
+
133
+ tokens.push(/\A[äa-z0-9\-?\.!:\/'\s\(\)]+[a-z0-9\)?!']/i => lambda do |match|
134
+ title = match.to_s.strip
135
+ song_title_counts[title] ||= 0
136
+ song_title_counts[title] += 1
137
+ return { type: :song, title: title, instance: song_title_counts[title] }
138
+ end)
139
+
140
+ tokens.push(/\A\[(?<num>\d+)\]/ => lambda do |match|
141
+ return { type: :note_ref, number: match[:num].to_i }
142
+ end)
143
+
144
+ tokens.push(/\A,/ => lambda do |match|
145
+ return { type: :transition, transition_type: :no_segue }
146
+ end)
147
+
148
+ tokens.push(/\A>/ => lambda do |match|
149
+ return { type: :transition, transition_type: :segue }
150
+ end)
151
+
152
+ tokens.push(/\A->/ => lambda do |match|
153
+ return { type: :transition, transition_type: :deep_segue }
154
+ end)
155
+
156
+ tokens.push(/\A\s+/ => lambda do |match|
157
+ return nil
158
+ end)
159
+
160
+ parsed_set = []
161
+
162
+ until transitions_text.empty?
163
+ matched = false
164
+
165
+ tokens.each do |matcher|
166
+ token_regex, processor = matcher.keys.first, matcher.values.first
167
+
168
+ if transitions_text.slice!(token_regex)
169
+ matched = true
170
+ token = processor.call($~)
171
+ parsed_set.push(token) if token
172
+ break
173
+ end
174
+ end
175
+
176
+ raise "could not parse: '#{transitions_text}'" unless matched
177
+ end
178
+
179
+ return parsed_set
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ # @api private
4
+ module PhishDotNetClient
5
+ class Song
6
+ attr_reader :title
7
+ attr_reader :url
8
+ attr_reader :slug
9
+ attr_reader :instance
10
+ attr_reader :position_in_set
11
+ attr_reader :position_in_show
12
+ attr_reader :footnotes
13
+ attr_accessor :pre_transition
14
+ attr_accessor :post_transition
15
+
16
+ def initialize(attrs={})
17
+ @title = attrs[:title]
18
+ @url = attrs[:url]
19
+ @slug = attrs[:slug]
20
+ @instance = attrs[:instance]
21
+ @position_in_set = attrs[:position_in_set]
22
+ @position_in_show = attrs[:position_in_show]
23
+ @footnotes = []
24
+ end
25
+
26
+ def to_s
27
+ s = StringIO.new
28
+
29
+ if pre_transition
30
+ s.print "#{pre_transition.from_song.title}(#{pre_transition.from_song.instance})..."
31
+ else
32
+ s.print "x..."
33
+ end
34
+
35
+ s.print "#{@title}(#{@instance})"
36
+
37
+ if post_transition
38
+ s.puts "...#{post_transition.to_song.title}(#{post_transition.to_song.instance})"
39
+ else
40
+ s.puts "...x"
41
+ end
42
+
43
+ s.puts
44
+ return s.string
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ # @api private
4
+ module PhishDotNetClient
5
+ class SongTransition
6
+
7
+ attr_reader :type
8
+ attr_reader :from_song
9
+ attr_reader :to_song
10
+
11
+ def initialize(type, from_song, to_song)
12
+ @type = type
13
+ @from_song = from_song
14
+ @to_song = to_song
15
+ @from_song.post_transition = @to_song.pre_transition = self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module PhishDotNetClient
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+ require 'phish_dot_net_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'phish_dot_net_client'
8
+ spec.version = PhishDotNetClient::VERSION
9
+ spec.date = Date.today.to_s
10
+ spec.licenses = ['MIT']
11
+ spec.authors = ["Alexander Bird"]
12
+ spec.email = ["alexebird@gmail.com"]
13
+ spec.homepage = "https://github.com/alexebird/phish_dot_net_client"
14
+ spec.summary = %q{Phish.net API client with setlist parsing}
15
+ spec.description = %q{Calls any Phish.net API method. Supports authentication. Parses 'setlistdata' fields.}
16
+
17
+ spec.required_ruby_version = '>= 1.9.1'
18
+ spec.required_rubygems_version = '>= 1.3.6'
19
+
20
+ spec.add_dependency 'rest-client', '~> 1.6.7'
21
+ spec.add_dependency 'json', '~> 1.8.0'
22
+ spec.add_dependency 'nokogiri', '~> 1.6.0'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec', '~> 2.14'
25
+ spec.add_development_dependency 'yard', '~> 0.8'
26
+
27
+ spec.files = `git ls-files`.split($/)
28
+ spec.test_files = spec.files.grep(%r{^spec/})
29
+
30
+ spec.require_paths = ["lib"]
31
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe PhishDotNetClient::Set do
4
+ let(:set) { PhishDotNetClient::Set }
5
+ let(:set_instance) { set.new }
6
+
7
+ %w[position name songs].each do |attr|
8
+ it "has a #{attr} attribute" do
9
+ expect(set_instance.respond_to? attr.to_sym).to be_true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe PhishDotNetClient::Setlist do
4
+ # Set 1: Runaway Jim[1], Foam, Punch You In the Eye[2] > Bathtub Gin, Cavern
5
+ # Set 2: Also Sprach Zarathustra -> Cavern
6
+ # Encore: Sleeping Monkey
7
+ let(:setlistdata) { "<p class='pnetset pnetset1'><span class='pnetsetlabel'>Set 1:<\/span> <a href=\"http:\/\/phish.net\/song\/runaway-jim\">Runaway Jim<\/a><sup>[1]<\/sup>, <a href=\"http:\/\/phish.net\/song\/foam\">Foam<\/a>, <a href=\"http:\/\/phish.net\/song\/punch-you-in-the-eye\">Punch You In the Eye<\/a><sup>[2]<\/sup> > <a href=\"http:\/\/phish.net\/song\/bathtub-gin\">Bathtub Gin<\/a>, <a href=\"http:\/\/phish.net\/song\/cavern\">Cavern<\/a><\/p><p class='pnetset pnetset2'><span class='pnetsetlabel'>Set 2:<\/span> <a href=\"http:\/\/phish.net\/song\/also-sprach-zarathustra\">Also Sprach Zarathustra<\/a> -> <a href=\"http:\/\/phish.net\/song\/cavern\">Cavern<\/a> <p class='pnetset pnetsete'><span class='pnetsetlabel'>Encore:<\/span> <a href=\"http:\/\/phish.net\/song\/sleeping-monkey\">Sleeping Monkey<\/a> <p class='pnetfootnotes'>[1] O.J. reference.<br \/>[2] \"Anti-drum solo.\"<br \/><\/p>" }
8
+ let(:setlist) { PhishDotNetClient::Setlist }
9
+ let(:setlist_instance) { setlist.new(setlistdata) }
10
+ let(:setlist_dumb_instance) { setlist.new('') }
11
+
12
+ it "has a sets attribute" do
13
+ expect(setlist_dumb_instance.respond_to? :sets).to be_true
14
+ end
15
+
16
+ it "has a footnotes attribute" do
17
+ expect(setlist_dumb_instance.respond_to? :footnotes).to be_true
18
+ end
19
+
20
+ describe "#initialize" do
21
+ it "sets the sets" do
22
+ expect(setlist_instance.sets).to be_instance_of(Array)
23
+ end
24
+ end
25
+
26
+ describe ".parse_setlistdata" do
27
+ let(:parsed_setlist) { setlist.parse_setlistdata(setlistdata) }
28
+
29
+ it "returns Array(sets), Hash(footnotes)" do
30
+ expect(parsed_setlist).to be_instance_of(Array)
31
+ sets = parsed_setlist.first
32
+ expect(sets).to be_instance_of(Array)
33
+ notes = parsed_setlist.last
34
+ expect(notes).to eq({ 1 => { text: %[O.J. reference.], song: sets[0].songs[0] },
35
+ 2 => { text: %["Anti-drum solo."], song: sets[0].songs[2] } })
36
+
37
+ set1 = sets[0]
38
+ expect(set1).to be_instance_of(PhishDotNetClient::Set)
39
+ set1.songs.each do |song|
40
+ expect(song).to be_instance_of(PhishDotNetClient::Song)
41
+ end
42
+
43
+ set2 = sets[1]
44
+ expect(set2).to be_instance_of(PhishDotNetClient::Set)
45
+ set2.songs.each do |song|
46
+ expect(song).to be_instance_of(PhishDotNetClient::Song)
47
+ end
48
+
49
+ encore = sets[2]
50
+ expect(encore).to be_instance_of(PhishDotNetClient::Set)
51
+ encore.songs.each do |song|
52
+ expect(song).to be_instance_of(PhishDotNetClient::Song)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "#songs" do
58
+ it "returns all songs" do
59
+ expect(setlist_instance.songs.size).to eq(8)
60
+ end
61
+ end
62
+
63
+ describe ".augment_songs" do
64
+ let(:song1) { PhishDotNetClient::Song.new(:title => "Song 1", :instance => 1) }
65
+ let(:song2) { PhishDotNetClient::Song.new(:title => "Song 2", :instance => 1) }
66
+ let(:song1again) { PhishDotNetClient::Song.new(:title => "Song 1", :instance => 2) }
67
+ let(:transitions_tokens) { setlist.tokenize_transitions_text("Set 1: Song 1[1] > Song 2 -> Song 1") }
68
+ let(:footnotes) { { 1 => { text: %[O.J. reference.] },
69
+ 2 => { text: %["Anti-drum solo."] } } }
70
+
71
+ it "adds transitions to the songs" do
72
+ setlist.augment_songs(transitions_tokens, [song1, song2, song1again], footnotes)
73
+ expect(song1.post_transition).to be_instance_of(PhishDotNetClient::SongTransition)
74
+ expect(song1.post_transition).to eq(song2.pre_transition)
75
+ expect(song1.post_transition.type).to eq(:segue)
76
+ end
77
+
78
+ it "accounts for songs with the same title" do
79
+ setlist.augment_songs(transitions_tokens, [song1, song2, song1again], footnotes)
80
+ expect(song2.post_transition).to eq(song1again.pre_transition)
81
+ expect(song2.post_transition.type).to eq(:deep_segue)
82
+ end
83
+
84
+ it "adds notes to the songs" do
85
+ setlist.augment_songs(transitions_tokens, [song1, song2, song1again], footnotes)
86
+ expect(song1.footnotes).to eq([1])
87
+ end
88
+ end
89
+
90
+ describe ".tokenize_transitions_text" do
91
+ let(:setlist_str) { "Set 1: Runaway Jim[1] -> Foam, Punch You In the Eye[2] > Bathtub Gin, Foam" }
92
+ let(:parsed_text) { setlist.tokenize_transitions_text(setlist_str) }
93
+
94
+ it "parses the set's text" do
95
+ expect(parsed_text).to eq([
96
+ { type: :set_name, name: "Set 1" },
97
+ { type: :song, title: "Runaway Jim", instance: 1 },
98
+ { type: :note_ref, number: 1 },
99
+ { type: :transition, transition_type: :deep_segue },
100
+ { type: :song, title: "Foam", instance: 1 },
101
+ { type: :transition, transition_type: :no_segue },
102
+ { type: :song, title: "Punch You In the Eye", instance: 1 },
103
+ { type: :note_ref, number: 2 },
104
+ { type: :transition, transition_type: :segue },
105
+ { type: :song, title: "Bathtub Gin", instance: 1 },
106
+ { type: :transition, transition_type: :no_segue },
107
+ { type: :song, title: "Foam", instance: 2 }
108
+ ])
109
+ end
110
+
111
+ it "raises an error for unparsable text" do
112
+ expect{ setlist.tokenize_transitions_text("asdf !@###") }.to raise_error
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe PhishDotNetClient::Song do
4
+ let(:song) { PhishDotNetClient::Song }
5
+ let(:song_instance) { song.new }
6
+
7
+ %w[title url slug instance position_in_set position_in_show footnotes pre_transition post_transition].each do |attr|
8
+ it "has a #{attr} attribute" do
9
+ expect(song_instance.respond_to? attr.to_sym).to be_true
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "sets the attributes" do
15
+ s = song.new(:title => 'title', :url => 'http://url', :slug => "slug", :instance => "inst",
16
+ :position_in_set => 3, :position_in_show => 5)
17
+ expect(s.title).to eq('title')
18
+ expect(s.url).to eq('http://url')
19
+ expect(s.slug).to eq('slug')
20
+ expect(s.instance).to eq('inst')
21
+ expect(s.position_in_set).to eq(3)
22
+ expect(s.position_in_show).to eq(5)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe PhishDotNetClient::SongTransition do
4
+ let(:from_song) { PhishDotNetClient::Song.new(:title => "Song 1") }
5
+ let(:to_song) { PhishDotNetClient::Song.new(:title => "Song 2") }
6
+ let(:song_trans) { PhishDotNetClient::SongTransition }
7
+ let!(:song_trans_instance) { PhishDotNetClient::SongTransition.new(:deep_segue, from_song, to_song) }
8
+
9
+ %w[type from_song to_song].each do |attr|
10
+ it "has a #{attr} attribute" do
11
+ expect(song_trans_instance.respond_to? attr.to_sym).to be_true
12
+ end
13
+ end
14
+
15
+ describe "#initialize" do
16
+ it "sets the attributes" do
17
+ expect(song_trans_instance.type).to eq(:deep_segue)
18
+ expect(song_trans_instance.from_song).to eq(from_song)
19
+ expect(song_trans_instance.to_song).to eq(to_song)
20
+ end
21
+
22
+ it "sets the songs transitions" do
23
+ expect(from_song.post_transition).to eq(song_trans_instance)
24
+ expect(to_song.pre_transition).to eq(song_trans_instance)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe PhishDotNetClient do
4
+ let(:pnet) { PhishDotNetClient }
5
+ let(:fake_api_key) { "fake-apikey" }
6
+
7
+ def access_default_params(key)
8
+ return pnet.class_variable_get(:@@default_params)[key]
9
+ end
10
+
11
+ it "pre-sets the api version to '2.0'" do
12
+ expect(access_default_params(:api)).to eq('2.0')
13
+ end
14
+
15
+ it "pre-sets the response format to json" do
16
+ expect(access_default_params(:format)).to eq('json')
17
+ end
18
+
19
+ describe "#apikey=" do
20
+ it "sets the api key" do
21
+ pnet.apikey = fake_api_key
22
+ expect(access_default_params(:apikey)).to eq(fake_api_key)
23
+ end
24
+ end
25
+
26
+ describe "#authorize" do
27
+ it "sets the username" do
28
+ # pnet.authorize('uzer', 'asdfasdf')
29
+ # expect(access_default_params(:username)).to eq('uzer')
30
+ end
31
+ end
32
+
33
+ describe "#clear_auth" do
34
+ before(:each) do
35
+ pnet.clear_auth
36
+ end
37
+
38
+ it "clears the apikey" do
39
+ expect(access_default_params(:apikey)).to be_nil
40
+ end
41
+
42
+ it "clears the authkey" do
43
+ expect(access_default_params(:authkey)).to be_nil
44
+ end
45
+
46
+ it "clears the username" do
47
+ expect(access_default_params(:username)).to be_nil
48
+ end
49
+ end
50
+
51
+ describe "#call_api_method" do
52
+ # context "when the api_method is protected" do
53
+ # it "raises an error if no apikey is specified" do
54
+ # pnet.clear_auth
55
+ # expect{ pnet.call_api_method("pnet.shows.query") }.to raise_error
56
+ # end
57
+ # end
58
+
59
+ it "calls the api" do
60
+ rv = pnet.call_api_method("pnet.shows.setlists.latest")
61
+ expect(rv[0]["setlistdata"]).to be_instance_of(PhishDotNetClient::Setlist)
62
+ end
63
+ end
64
+
65
+ describe "#method_missing" do
66
+ context "when the api method is a valid" do
67
+ it "delegates to call_api_method" do
68
+ expect{ pnet.method_missing :"pnet_shows_setlists_latest" }.not_to raise_error
69
+ end
70
+ end
71
+
72
+ # context "when the method name is not a valid api method" do
73
+ # it "passes the method_missing call on to super" do
74
+ # expect{ pnet.method_missing :"asdf_jkl" }.to raise_error(NoMethodError)
75
+ # end
76
+ # end
77
+ end
78
+
79
+ describe "#get_api_method" do
80
+ # it "returns nil when the api_method doesn't exist" do
81
+ # expect(pnet.get_api_method('doesnt_exist')).to be_nil
82
+ # end
83
+
84
+ it "returns the api method" do
85
+ expect(pnet.get_api_method("pnet_api_authorize")).to eq("pnet.api.authorize")
86
+ end
87
+
88
+ it "adds the 'pnet.' prefix if it's not there" do
89
+ expect(pnet.get_api_method("api_authorize")).to eq("pnet.api.authorize")
90
+ end
91
+ end
92
+
93
+ # describe "#ensure_api_key" do
94
+ # it "raises an error if api_key isn't set" do
95
+ # expect{ pnet.ensure_api_key }.to raise_error
96
+ # end
97
+ # end
98
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler.require :development
3
+
4
+ require './lib/phish_dot_net_client'
5
+
6
+ RSpec.configure do |config|
7
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phish_dot_net_client
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Alexander Bird
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-10-09 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rest-client
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 6
31
+ - 7
32
+ version: 1.6.7
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 8
46
+ - 0
47
+ version: 1.8.0
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: nokogiri
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 1
60
+ - 6
61
+ - 0
62
+ version: 1.6.0
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rake
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 2
88
+ - 14
89
+ version: "2.14"
90
+ type: :development
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ name: yard
94
+ prerelease: false
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ~>
99
+ - !ruby/object:Gem::Version
100
+ segments:
101
+ - 0
102
+ - 8
103
+ version: "0.8"
104
+ type: :development
105
+ version_requirements: *id006
106
+ description: Calls any Phish.net API method. Supports authentication. Parses 'setlistdata' fields.
107
+ email:
108
+ - alexebird@gmail.com
109
+ executables: []
110
+
111
+ extensions: []
112
+
113
+ extra_rdoc_files: []
114
+
115
+ files:
116
+ - .gitignore
117
+ - .rspec
118
+ - Gemfile
119
+ - Gemfile.lock
120
+ - README.md
121
+ - Rakefile
122
+ - lib/phish_dot_net_client.rb
123
+ - lib/phish_dot_net_client/set.rb
124
+ - lib/phish_dot_net_client/setlist.rb
125
+ - lib/phish_dot_net_client/song.rb
126
+ - lib/phish_dot_net_client/song_transition.rb
127
+ - lib/phish_dot_net_client/version.rb
128
+ - phish_dot_net_client.gemspec
129
+ - spec/phish_dot_net_client/set_spec.rb
130
+ - spec/phish_dot_net_client/setlist_spec.rb
131
+ - spec/phish_dot_net_client/song_spec.rb
132
+ - spec/phish_dot_net_client/song_transition_spec.rb
133
+ - spec/phish_dot_net_client_spec.rb
134
+ - spec/spec_helper.rb
135
+ has_rdoc: true
136
+ homepage: https://github.com/alexebird/phish_dot_net_client
137
+ licenses:
138
+ - MIT
139
+ post_install_message:
140
+ rdoc_options: []
141
+
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ segments:
150
+ - 1
151
+ - 9
152
+ - 1
153
+ version: 1.9.1
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ segments:
160
+ - 1
161
+ - 3
162
+ - 6
163
+ version: 1.3.6
164
+ requirements: []
165
+
166
+ rubyforge_project:
167
+ rubygems_version: 1.3.7.1
168
+ signing_key:
169
+ specification_version: 3
170
+ summary: Phish.net API client with setlist parsing
171
+ test_files:
172
+ - spec/phish_dot_net_client/set_spec.rb
173
+ - spec/phish_dot_net_client/setlist_spec.rb
174
+ - spec/phish_dot_net_client/song_spec.rb
175
+ - spec/phish_dot_net_client/song_transition_spec.rb
176
+ - spec/phish_dot_net_client_spec.rb
177
+ - spec/spec_helper.rb