phish_dot_net_client 0.0.1

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/.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