rapgenius 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ spec/support/cassettes
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ __v0.0.1__ (17th August 2013)
4
+
5
+ * Initial version
6
+
7
+ __v0.0.2__ (17th August 2013)
8
+
9
+ * Adds `RapGenius::Song.find` to replicate behaviour in `RapGenius::Annotation`
10
+
11
+ __v0.0.3__ (22nd August 2013, *contributed by [tsigo](https://github.com/tsigo)*)
12
+
13
+ * Improves implementation of HTTParty
14
+ * Reorganises specs to use VCR
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # rapgenius
2
+
3
+ ![Rap Genius logo](http://f.cl.ly/items/303W0c1i2r100j2u3Y0y/Screen%20Shot%202013-08-17%20at%2016.01.19.png)
4
+
5
+ ## What does this do?
6
+
7
+ It's a Ruby gem for accessing lyrics and explanations on
8
+ [Rap Genius](http://rapgenius.com).
9
+
10
+ They very sadly [don't have an API](https://twitter.com/RapGenius/status/245057326321655808) so I decided to replicate one for myself
11
+ with a nice bit of screen scraping with [Nokogiri](https://github.com/sparklemotion/nokogiri), much like my [amex](https://github.com/timrogers/amex), [ucas](https://github.com/timrogers/ucas) and [lloydstsb](https://github.com/timrogers/lloydstsb) gems.
12
+
13
+ ## Installation
14
+
15
+ Install the gem, and you're ready to go. Simply add the following to your
16
+ Gemfile:
17
+
18
+ `gem "rapgenius", "~> 0.0.2"`
19
+
20
+ ## Usage
21
+
22
+ Songs on Rap Genius don't have numeric identifiers as far as I can tell - they're identified by a URL slug featuring the artist and song name, for instance "Big-sean-control-lyrics". We use this to fetch a particular track, like so:
23
+
24
+ ```ruby
25
+ require 'rapgenius'
26
+ song = RapGenius::Song.find("Big-sean-control-lyrics")
27
+ ```
28
+
29
+ Once you've got the song, you can easily load details about it. This uses
30
+ Nokogiri to fetch the song's page and then parse it:
31
+
32
+ ```ruby
33
+ song.title
34
+ # => "Control"
35
+
36
+ song.artist
37
+ # => "Big Sean"
38
+
39
+ song.full_artist
40
+ # => "Big Sean (Ft. Jay Electronica & Kendrick Lamar)"
41
+
42
+ song.images
43
+ # => ["http://s3.amazonaws.com/rapgenius/1376434983_jay-electronica.jpg", "http://s3.amazonaws.com/rapgenius/1375029260_Big%20Sean.png", "http://s3.amazonaws.com/rapgenius/Kendrick-Lamar-1024x680.jpg"]
44
+
45
+ song.description
46
+ # => "The non-album cut from Sean that basically blew up the Internet due to a world-beating verse by Kendrick Lamar...
47
+ ```
48
+
49
+ The `#annotations` accessor on a Song returns an array of RapGenius::Annotation
50
+ objects corresponding to different annotated lines of the song, identified by
51
+ their `id`.
52
+
53
+ You can look these up manually using `RapGenius::Annotation.find("id")`. You
54
+ can grab the ID for a lyric from a RapGenius page by right clicking on an annotation, copying the shortcut and then finding the number after "http://rapgenius.com".
55
+
56
+ ```ruby
57
+ song.annotations
58
+ # => [<RapGenius::Annotation>, <RapGenius::Annotation>...]
59
+
60
+ annotation = song.annotations[99]
61
+
62
+ annotation.lyric
63
+ # => "And that goes for Jermaine Cole, Big KRIT, Wale\nPusha T, Meek Millz, A$AP Rocky, Drake\nBig Sean, Jay Electron', Tyler, Mac Miller"
64
+
65
+ annotation.explanation
66
+ # => "Kendrick calls out some of the biggest names in present day Hip-hop...""
67
+
68
+ annotation.song == song # You can get back to the song from the annotation...
69
+ # => true
70
+
71
+ annotation.id
72
+ # => "2093001"
73
+
74
+ annotation2 = RapGenius::Annotation.find("2093001") # Fetching directly...
75
+
76
+ annotation == annotations2
77
+ # => true
78
+ ```
79
+
80
+ ## Contributing
81
+
82
+ There are a few things I'd love to see added to this gem:
83
+
84
+ * __Searching__ - having to know the path to a particular track's lyrics isn't super intuitive
85
+ * __Support for *\*Genius*__ - RapG enius also have other sites on subdomains like [News Genius](http://news.rapgenius.com) and [Poetry Genius](http://poetry.rapgenius.com). These could very easily be supported, since theyre identical in terms of markup.
86
+
87
+ This gem is open source, so feel free to add anything you want, then make a pull request. A few quick tips:
88
+
89
+ * Don't update the version numbers before your pull request - I'll sort that part out for you!
90
+ * Make sure you write specs, then run them with `$ bundle exec rake`
91
+ * Update this README.md file so I, and users, know how your changes work
92
+
93
+ ## Get in touch
94
+
95
+ Any questions, thoughts or comments? Email me at <me@timrogers.co.uk>.
@@ -26,11 +26,12 @@ module RapGenius
26
26
  end
27
27
 
28
28
  def song
29
- entry_path = document.css('meta[property="rap_genius:song"]').
30
- attr('content').to_s
31
-
32
- @song ||= Song.new(entry_path)
29
+ @song ||= Song.new(song_url)
33
30
  end
34
31
 
32
+ def song_url
33
+ @song_url ||= document.css('meta[property="rap_genius:song"]').
34
+ attr('content').to_s
35
+ end
35
36
  end
36
- end
37
+ end
@@ -3,33 +3,55 @@ require 'httparty'
3
3
 
4
4
  module RapGenius
5
5
  module Scraper
6
- BASE_URL = "http://rapgenius.com/".freeze
6
+ # Custom HTTParty parser that parses the returned body with Nokogiri
7
+ class NokogiriParser < HTTParty::Parser
8
+ SupportedFormats.merge!('text/html' => :html)
7
9
 
8
- attr_reader :url
10
+ def html
11
+ Nokogiri::HTML(body)
12
+ end
13
+ end
14
+
15
+ # HTTParty client
16
+ #
17
+ # Sets some useful defaults for all of our requests.
18
+ #
19
+ # See Scraper#fetch
20
+ class Client
21
+ include HTTParty
22
+
23
+ format :html
24
+ parser NokogiriParser
25
+ base_uri 'http://rapgenius.com'
26
+ headers 'User-Agent' => "rapgenius.rb v#{RapGenius::VERSION}"
27
+ end
9
28
 
29
+ BASE_URL = Client.base_uri + "/".freeze
30
+
31
+ attr_reader :url
10
32
 
11
33
  def url=(url)
12
- if !(url =~ /^https?:\/\//)
13
- @url = "#{BASE_URL}#{url}"
34
+ unless url =~ /^https?:\/\//
35
+ @url = BASE_URL + url
14
36
  else
15
37
  @url = url
16
38
  end
17
39
  end
18
40
 
19
41
  def document
20
- @document ||= Nokogiri::HTML(fetch(@url))
42
+ @document ||= fetch(@url)
21
43
  end
22
44
 
23
45
  private
46
+
24
47
  def fetch(url)
25
- response = HTTParty.get(url)
48
+ response = Client.get(url)
26
49
 
27
50
  if response.code != 200
28
51
  raise ScraperError, "Received a #{response.code} HTTP response"
29
52
  end
30
53
 
31
- response.body
54
+ response.parsed_response
32
55
  end
33
-
34
56
  end
35
- end
57
+ end
@@ -11,7 +11,6 @@ module RapGenius
11
11
  self.url = path
12
12
  end
13
13
 
14
-
15
14
  def artist
16
15
  document.css('.song_title a').text
17
16
  end
@@ -43,7 +42,5 @@ module RapGenius
43
42
  )
44
43
  end
45
44
  end
46
-
47
-
48
45
  end
49
- end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module RapGenius
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
Binary file
data/rapgenius.gemspec CHANGED
@@ -14,13 +14,15 @@ Gem::Specification.new do |s|
14
14
  "working at Rap Genius is the API". With this magical screen-scraping gem,
15
15
  you can access the wealth of data on the internet Talmud in Ruby.}
16
16
 
17
- s.add_runtime_dependency "nokogiri", "~>1.6.0"
18
- s.add_runtime_dependency "httparty", "~>0.11.0"
19
- s.add_development_dependency "rspec", "~>2.14.1"
20
- s.add_development_dependency "mocha", "~>0.14.0"
17
+ s.add_runtime_dependency "nokogiri", "~>1.6.0"
18
+ s.add_runtime_dependency "httparty", "~>0.11.0"
19
+ s.add_development_dependency "rspec", "~>2.14.1"
20
+ s.add_development_dependency "mocha", "~>0.14.0"
21
+ s.add_development_dependency "webmock", "~>1.11.0"
22
+ s.add_development_dependency "vcr", "~>2.5.0"
21
23
 
22
24
  s.files = `git ls-files`.split("\n")
23
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
26
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
27
  s.require_paths = ["lib"]
26
- end
28
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ module RapGenius
4
+ describe Annotation, vcr: {cassette_name: "big-sean-annotation"} do
5
+
6
+ let(:annotation) { described_class.new(id: "2092393") }
7
+ subject { annotation }
8
+
9
+ its(:id) { should eq "2092393" }
10
+ its(:url) { should eq "http://rapgenius.com/2092393" }
11
+ its(:song) { should be_a Song }
12
+ its(:song_url) { should eq "http://rapgenius.com/Big-sean-control-lyrics" }
13
+
14
+ describe "#lyric" do
15
+ it "should have the correct lyric" do
16
+ annotation.lyric.should eq "You gon' get this rain like it's May weather,"
17
+ end
18
+ end
19
+
20
+ describe "#explanation" do
21
+ it "should have the correct explanation" do
22
+ annotation.explanation.should include "making it rain"
23
+ end
24
+ end
25
+
26
+ describe '.find' do
27
+ it "returns a new instance at the specified path" do
28
+ i = described_class.find("foobar")
29
+ i.should be_an Annotation
30
+ i.id.should eq "foobar"
31
+ end
32
+ end
33
+
34
+ context "with additional parameters passed into the constructor" do
35
+ let(:annotation) { described_class.new(id: "5678", lyric: "foo") }
36
+
37
+ its(:id) { should eq "5678" }
38
+ its(:lyric) { should eq "foo" }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ class ScraperTester
4
+ include RapGenius::Scraper
5
+ end
6
+
7
+ module RapGenius
8
+ describe Scraper do
9
+
10
+ let(:scraper) { ScraperTester.new }
11
+
12
+ describe "#url=" do
13
+ it "forms the URL with the base URL, if the current path is relative" do
14
+ scraper.url = "foobar"
15
+ scraper.url.should include RapGenius::Scraper::BASE_URL
16
+ end
17
+
18
+ it "leaves the URL as it is if already complete" do
19
+ scraper.url = "http://foobar.com/baz"
20
+ scraper.url.should eq "http://foobar.com/baz"
21
+ end
22
+ end
23
+
24
+ describe "#document" do
25
+ before do
26
+ scraper.url = "http://foo.bar/"
27
+ end
28
+
29
+ context "with a successful request" do
30
+ before do
31
+ stub_request(:get, "http://foo.bar").to_return({body: 'ok', status: 200})
32
+ end
33
+
34
+ it "returns a Nokogiri document object" do
35
+ scraper.document.should be_a Nokogiri::HTML::Document
36
+ end
37
+
38
+ it "contains the tags in page received back from the HTTP request" do
39
+ scraper.document.css('body').length.should eq 1
40
+ end
41
+ end
42
+
43
+ context "with a failed request" do
44
+ before do
45
+ stub_request(:get, "http://foo.bar").to_return({body: '', status: 404})
46
+ end
47
+
48
+ it "raises a ScraperError" do
49
+ expect { scraper.document }.to raise_error(RapGenius::ScraperError)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ module RapGenius
4
+ describe Song do
5
+ context "given Big Sean's Control", vcr: {cassette_name: "big-sean-control-lyrics"} do
6
+ subject { described_class.new("Big-sean-control-lyrics") }
7
+
8
+ its(:url) { should eq "http://rapgenius.com/Big-sean-control-lyrics" }
9
+ its(:title) { should eq "Control" }
10
+ its(:artist) { should eq "Big Sean" }
11
+ its(:description) { should include "blew up the Internet" }
12
+ its(:full_artist) { should include "(Ft. Jay Electronica & Kendrick Lamar)"}
13
+
14
+ describe "#images" do
15
+ it "should be an Array" do
16
+ subject.images.should be_an Array
17
+ end
18
+
19
+ it "should include Big Sean's picture" do
20
+ subject.images.should include "http://s3.amazonaws.com/rapgenius/1375029260_Big%20Sean.png"
21
+ end
22
+ end
23
+
24
+ describe "#annotations" do
25
+ it "should be an Array of Annotation objects" do
26
+ subject.annotations.should be_an Array
27
+ subject.annotations.first.should be_a Annotation
28
+ end
29
+
30
+ it "should be of a valid length" do
31
+ # Annotations get added and removed from the live site; we want our
32
+ # count to be somewhat accurate, within reason.
33
+ subject.annotations.length.should be_within(15).of(130)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '.find' do
39
+ it "returns a new instance at the specified path" do
40
+ i = described_class.find("foobar")
41
+ i.should be_a Song
42
+ i.url.should eq 'http://rapgenius.com/foobar'
43
+ end
44
+ end
45
+ end
46
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'rapgenius'
2
2
  require 'mocha/api'
3
+ require 'webmock/rspec'
4
+
5
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
3
6
 
4
7
  RSpec.configure do |config|
5
8
  config.mock_framework = :mocha
6
- end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'vcr'
2
+
3
+ VCR.configure do |c|
4
+ c.default_cassette_options = {
5
+ record: :new_episodes,
6
+ re_record_interval: 24 * 60 * 60
7
+ }
8
+ c.cassette_library_dir = File.expand_path('../cassettes/', __FILE__)
9
+ c.hook_into :webmock
10
+ c.configure_rspec_metadata!
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapgenius
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-17 00:00:00.000000000 Z
12
+ date: 2013-08-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -75,6 +75,38 @@ dependencies:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
77
  version: 0.14.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: webmock
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.11.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.11.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: vcr
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 2.5.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 2.5.0
78
110
  description: ! "Up until until now, to quote RapGenius themselves,\n \"working
79
111
  at Rap Genius is the API\". With this magical screen-scraping gem,\n you can
80
112
  access the wealth of data on the internet Talmud in Ruby."
@@ -84,9 +116,11 @@ executables: []
84
116
  extensions: []
85
117
  extra_rdoc_files: []
86
118
  files:
119
+ - .gitignore
120
+ - CHANGELOG.md
87
121
  - Gemfile
88
- - Gemfile.lock
89
122
  - LICENSE
123
+ - README.md
90
124
  - Rakefile
91
125
  - lib/rapgenius.rb
92
126
  - lib/rapgenius/annotation.rb
@@ -95,13 +129,13 @@ files:
95
129
  - lib/rapgenius/song.rb
96
130
  - lib/rapgenius/version.rb
97
131
  - pkg/rapgenius-0.0.1.gem
132
+ - pkg/rapgenius-0.0.2.gem
98
133
  - rapgenius.gemspec
99
- - spec/annotation_spec.rb
100
- - spec/scraper_spec.rb
101
- - spec/song_spec.rb
134
+ - spec/rapgenius/annotation_spec.rb
135
+ - spec/rapgenius/scraper_spec.rb
136
+ - spec/rapgenius/song_spec.rb
102
137
  - spec/spec_helper.rb
103
- - spec/support/annotation.html
104
- - spec/support/song.html
138
+ - spec/support/vcr.rb
105
139
  homepage: http://timrogers.co.uk
106
140
  licenses: []
107
141
  post_install_message:
@@ -127,9 +161,8 @@ signing_key:
127
161
  specification_version: 3
128
162
  summary: A gem for accessing lyrics and explanations on RapGenius.com
129
163
  test_files:
130
- - spec/annotation_spec.rb
131
- - spec/scraper_spec.rb
132
- - spec/song_spec.rb
164
+ - spec/rapgenius/annotation_spec.rb
165
+ - spec/rapgenius/scraper_spec.rb
166
+ - spec/rapgenius/song_spec.rb
133
167
  - spec/spec_helper.rb
134
- - spec/support/annotation.html
135
- - spec/support/song.html
168
+ - spec/support/vcr.rb