woot 0.0.7 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -4
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/woot.rb +70 -40
- data/test/test_helper.rb +10 -14
- data/test/woot_test.rb +4 -4
- data/woot.gemspec +4 -4
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -8,14 +8,14 @@ Scrapes woot.com sites
|
|
8
8
|
|
9
9
|
== Usage
|
10
10
|
|
11
|
-
Simply call <tt>Woot.
|
12
|
-
Defaults to www.
|
11
|
+
Simply call <tt>Woot.new</tt> and optionally specify the woot subdomain (e.g. www, shirt, kids, wine, etc).
|
12
|
+
Defaults to www.
|
13
13
|
|
14
|
-
woot = Woot.
|
14
|
+
woot = Woot.new
|
15
15
|
puts woot.title
|
16
16
|
puts woot.price
|
17
17
|
|
18
|
-
woot = Woot.
|
18
|
+
woot = Woot.new(:shirt)
|
19
19
|
puts woot.title
|
20
20
|
|
21
21
|
You can also receive live Woot updates using Twitter's Streaming API (See http://apiwiki.twitter.com/Streaming-API-Documentation).
|
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ begin
|
|
10
10
|
gem.email = 'shuber@huberry.com'
|
11
11
|
gem.homepage = 'http://github.com/shuber/woot'
|
12
12
|
gem.authors = ['Sean Huber']
|
13
|
-
gem.add_dependency '
|
13
|
+
gem.add_dependency 'nokogiri'
|
14
14
|
gem.add_dependency 'tweetstream'
|
15
15
|
gem.add_development_dependency 'fakeweb'
|
16
16
|
gem.add_development_dependency 'shoulda'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/woot.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
require '
|
1
|
+
require 'rubygems'
|
2
|
+
require 'nokogiri'
|
2
3
|
require 'tweetstream'
|
3
|
-
|
4
|
-
Tidy.path = ENV['TIDY_PATH'] if ENV['TIDY_PATH']
|
4
|
+
require 'net/http'
|
5
5
|
|
6
6
|
class Woot
|
7
|
+
|
7
8
|
DOMAIN = 'woot.com'
|
8
9
|
SELLOUT_DOMAIN = 'shopping.yahoo.com'
|
9
10
|
WOOT_OFF = 'woot-off'
|
@@ -17,51 +18,37 @@ class Woot
|
|
17
18
|
}
|
18
19
|
SUBDOMAINS = TWITTER_IDS.keys - [WOOT_OFF]
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
attr_reader :subdomain
|
22
|
+
|
23
|
+
def initialize(subdomain = 'www')
|
24
|
+
@subdomain = subdomain.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def document
|
28
|
+
@document ||= Nokogiri::HTML(html)
|
29
|
+
end
|
30
|
+
|
31
|
+
def html
|
32
|
+
@html ||= Net::HTTP.get(URI.parse(scrape_url))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.attribute(name, selector, result = nil, &block)
|
36
|
+
attributes << name unless attributes.include?(name)
|
37
|
+
instance_variable_name = "@#{name}"
|
38
|
+
define_method name do
|
39
|
+
instance_variable_set(instance_variable_name, parse(selector, block_given? ? block : result)) unless instance_variable_defined?(instance_variable_name)
|
40
|
+
instance_variable_get(instance_variable_name)
|
27
41
|
end
|
28
|
-
response = Net::HTTP.get(URI.parse(url))
|
29
|
-
|
30
|
-
selectors = self.selectors(subdomain)
|
31
|
-
Scraper.define do
|
32
|
-
result *(selectors.inject([]) do |array, (pattern, results)|
|
33
|
-
process_first pattern, results
|
34
|
-
array += results.keys
|
35
|
-
end)
|
36
|
-
end.scrape(response)
|
37
42
|
end
|
38
43
|
|
39
|
-
def self.
|
40
|
-
@
|
41
|
-
'*' => { :subdomain => proc { |element| subdomain.to_s } },
|
42
|
-
'h2.fn' => { :title => :text },
|
43
|
-
'span.amount' => { :price => :text },
|
44
|
-
'abbr.currency' => { :currency => '@title', :currency_symbol => :text },
|
45
|
-
'ul#shippingOptions' => { :shipping => :text },
|
46
|
-
'img.photo' => { :image => '@src' },
|
47
|
-
'div.hproduct>a' => { :alternate_image => proc { |element| $1 if element.attributes['href'] =~ /\('([^']+)'\);/ } },
|
48
|
-
'a.url' => { :url => '@href' },
|
49
|
-
'li.comments>a' => { :comments_url => '@href', :comments_count => proc { |element| element.children[0].content.gsub(/\D/, '') } },
|
50
|
-
'div.story>h2' => { :header => :text },
|
51
|
-
'div.story>h3' => { :subheader => :text },
|
52
|
-
'div.writeUp' => { :writeup => :text },
|
53
|
-
'div.specs' => { :specs => :text },
|
54
|
-
'div.productDescription>dl' => { :details => :text },
|
55
|
-
'a#ctl00_ctl00_ContentPlaceHolderLeadIn_ContentPlaceHolderLeadIn_SaleControl_HyperLinkWantOne' => { :purchase_url => proc do |element|
|
56
|
-
"http://#{subdomain}.#{DOMAIN}#{element.attributes['href'].gsub(/^https?:\/\/[^\/]+/, '')}" if element.attributes.has_key?('href')
|
57
|
-
end }
|
58
|
-
}
|
44
|
+
def self.attributes
|
45
|
+
@attributes ||= []
|
59
46
|
end
|
60
47
|
|
61
48
|
def self.stream(twitter_username, twitter_password)
|
62
49
|
TweetStream::Client.new(twitter_username, twitter_password).follow(*TWITTER_IDS.values) do |status|
|
63
50
|
subdomain = subdomain_from_twitter_status(status)
|
64
|
-
yield
|
51
|
+
yield new(subdomain) unless subdomain.nil?
|
65
52
|
end
|
66
53
|
end
|
67
54
|
|
@@ -71,9 +58,52 @@ class Woot
|
|
71
58
|
|
72
59
|
protected
|
73
60
|
|
61
|
+
def evaluate_result(result, element)
|
62
|
+
case result
|
63
|
+
when Symbol
|
64
|
+
element.send(result).to_s.strip
|
65
|
+
when Proc
|
66
|
+
result.bind(self).call(element)
|
67
|
+
when String
|
68
|
+
element.attributes[result].to_s.strip
|
69
|
+
else
|
70
|
+
result
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse(selector, result)
|
75
|
+
evaluate_result result, document.css(selector).first
|
76
|
+
end
|
77
|
+
|
78
|
+
def scrape_url
|
79
|
+
subdomain.to_s == 'sellout' ? Nokogiri::HTML(Net::HTTP.get(SELLOUT_DOMAIN, '/')).css('div.bd div.img a').first.attributes['href'].to_s.gsub('&', '&') : "http://#{subdomain}.#{DOMAIN}/"
|
80
|
+
end
|
81
|
+
|
74
82
|
def self.subdomain_from_twitter_status(status)
|
75
83
|
subdomain = TWITTER_IDS.index(status.user.id)
|
76
84
|
subdomain = (status.text =~ /(\w+)\.#{DOMAIN}/ ? $1 : nil) if subdomain == WOOT_OFF
|
77
85
|
subdomain
|
78
86
|
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
class Woot
|
91
|
+
|
92
|
+
attribute :alternate_image, 'div.hproduct a', proc { |element| $1 if element.attributes['href'].to_s =~ /\('([^']+)'\);/ }
|
93
|
+
attribute :comments_count, 'li.comments a', proc { |element| element.content.gsub(/\D/, '') }
|
94
|
+
attribute :comments_url, 'li.comments a', 'href'
|
95
|
+
attribute :currency, 'abbr.currency', 'title'
|
96
|
+
attribute :currency_symbol, 'abbr.currency', :content
|
97
|
+
attribute :details, 'div.productDescription dl', :content
|
98
|
+
attribute :header, 'div.story h2', :content
|
99
|
+
attribute :image, 'img.photo', 'src'
|
100
|
+
attribute :price, 'span.amount', :content
|
101
|
+
attribute :purchase_url, 'a#ctl00_ctl00_ContentPlaceHolderLeadIn_ContentPlaceHolderLeadIn_SaleControl_HyperLinkWantOne', proc { |element| "http://#{subdomain}.#{DOMAIN}#{element.attributes['href'].to_s.gsub(/^https?:\/\/[^\/]+/, '')}" if element.attributes.has_key?('href') }
|
102
|
+
attribute :shipping, 'ul#shippingOptions', :content
|
103
|
+
attribute :specs, 'div.specs', :content
|
104
|
+
attribute :subheader, 'div.story h3', :content
|
105
|
+
attribute :title, 'h2.fn', :content
|
106
|
+
attribute :url, 'a.url', 'href'
|
107
|
+
attribute :writeup, 'div.writeUp', :content
|
108
|
+
|
79
109
|
end
|
data/test/test_helper.rb
CHANGED
@@ -8,10 +8,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
8
8
|
require 'woot'
|
9
9
|
|
10
10
|
class Test::Unit::TestCase
|
11
|
-
|
12
|
-
def self.attributes
|
13
|
-
@attributes ||= Woot.selectors.map { |selector, results| results.keys }.flatten
|
14
|
-
end
|
15
11
|
|
16
12
|
def self.possible_blank_attributes
|
17
13
|
@possible_blank_attributes ||= [:purchase_url]
|
@@ -26,7 +22,7 @@ class Test::Unit::TestCase
|
|
26
22
|
end
|
27
23
|
|
28
24
|
def self.woots
|
29
|
-
@woots ||= (Woot::SUBDOMAINS + fixtures.keys).inject({}) { |hash, subdomain| hash.merge! subdomain => Woot.
|
25
|
+
@woots ||= (Woot::SUBDOMAINS + fixtures.keys).inject({}) { |hash, subdomain| hash.merge! subdomain => Woot.new(subdomain) }
|
30
26
|
end
|
31
27
|
|
32
28
|
register_fakeweb_uris
|
@@ -55,7 +51,7 @@ WOOT_FIXTURE_VALUES = {
|
|
55
51
|
:price => '14.99',
|
56
52
|
:currency => 'USD',
|
57
53
|
:currency_symbol => '$',
|
58
|
-
:shipping => "
|
54
|
+
:shipping => "+ $5 shipping",
|
59
55
|
:image => 'http://sale.images.woot.com/Blockade_Noise_Isolating_EarbudsmqlStandard.jpg',
|
60
56
|
:alternate_image => 'http://sale.images.woot.com/Blockade_Noise_Isolating_Earbudsqx4Detail.jpg',
|
61
57
|
:url => 'http://www.woot.com/sale/10457',
|
@@ -63,9 +59,9 @@ WOOT_FIXTURE_VALUES = {
|
|
63
59
|
:comments_count => '71',
|
64
60
|
:header => 'I think the little hairy guy with the claws is popping my bike tires on purpose',
|
65
61
|
:subheader => 'Head down, earbuds in, try not to get hit by a laser',
|
66
|
-
:writeup => "
|
67
|
-
:specs => "
|
68
|
-
:details => "\
|
62
|
+
:writeup => "My mother-in-law thought we were crazy. \342\200\234You two are going to end up pulverized by a giant world-eating alien just like your Aunt Regina!\342\200\235 We\342\200\231ve always wanted to live in Metro City, though, and when Jessica got that job offer at the Major Daily Newspaper, we were excited to move to where all the costumed action is.\nFor about the first three weeks, anyway.\nWhen the errant meteorite hit the Volvo, we shrugged it off and started using public transportation. Then, our dog Scamp was eaten by the Crimson Cowl\342\200\231s cyborg velociraptors. And around the fourth time some villain\342\200\231s robot henchman gets plowed through your wall and into your living room without so much as a \342\200\234Sorry, Citizens\342\200\235 from whatever hero happens to be on duty that day, you start to get a little annoyed.\nI\342\200\231m not complaining, really. That\342\200\231s what cape insurance is for, after all. That\342\200\231s the price you pay for living in the superhero capital of the world, I guess. You either pack your things and move to Wyoming, or you learn to adapt. It just takes a little adjusting.\nSo on the nights when the sky burns red with crisis or some silver-plated intergalactic herald is preaching doom and gloom all over the city, my wife and I plug the Blockade Noise Isolating Earbuds into our MP3 players. The triple flange design provides optimal noise protection from all the laser blasts and explosions that flood the city soundscape, allowing us to concentrate on the music or audiobook we\342\200\231re listening to. When I need to take a trip down to the corner market and have to jump out of the way of an automobile being thrown by some hulking gamma-irradiated scientist, the comfortable fit of these earbuds means the music stays in my ears. The integrated volume control in the cord is great for when I want to drown out any superhero bellowing witty banter, too.\nIf anyone knows a good way of keeping some of these heroes from perching on the gargoyle outside our bedroom window at night, though, I\342\200\231d love to hear it. I know they\342\200\231re just doing their inner-monologue thing, but it gets kinda creepy.",
|
63
|
+
:specs => "Warranty: 90 Day Woot Limited Warranty \nFeatures:\nPatented triple-flange ear bud design provides optimal noise protection for an excellent fit that radically reduces noise\n Integrated volume control places fingertip adjustment of any iPod or MP3 player easily within reach\n 24 decibel Noise Reduction Rating brings an end to dangerously over-cranked tunes struggling to compete with loud external sounds\n Use in the yard while mowing and gardening, in the workshop, on the job, in the gym or traveling on a plane to safely isolate background noise\n Ultra comfortable design from the same company who brought you the industry leading WorkTunes\n iPod and MP3 compatible (any device with a 3.5mm plug)\n Meets ANSI/OSHA Standards\nAdditional Photos:\nBlockade Noise Isolating Earbuds\n Volume Control\n Triple-flange ear bud design\nIn the box:\n2 AO Safety 99014 Blockade Noise Isolating Earbuds\n 6 Extra Pairs of Ear Tips\n 2 Carrying Bags",
|
64
|
+
:details => "Condition:\n New\n Product:\n \n\t\t\t\t\t\n\t\t\t\t\t 2 AO Safety 99014 Blockade Noise Isolating Earbuds",
|
69
65
|
:purchase_url => 'http://available.woot.com/WantOne.aspx?id=983db0c6-5c69-4737-926b-3697d4af542e'
|
70
66
|
},
|
71
67
|
'soldout' => {
|
@@ -74,17 +70,17 @@ WOOT_FIXTURE_VALUES = {
|
|
74
70
|
:price => '129.99',
|
75
71
|
:currency => 'USD',
|
76
72
|
:currency_symbol => '$',
|
77
|
-
:shipping => "
|
73
|
+
:shipping => "+ $5 shipping",
|
78
74
|
:image => 'http://sale.images.woot.com/iRobot_Roomba_530_Robotic_Vacuum_with_Virtual_WalluquStandard.jpg',
|
79
75
|
:alternate_image => 'http://sale.images.woot.com/iRobot_Roomba_530_Robotic_Vacuum_with_Virtual_WallyxlDetail.jpg',
|
80
76
|
:url => 'http://www.woot.com/sale/10244',
|
81
77
|
:comments_url => 'http://www.woot.com/Forums/ViewPost.aspx?PostID=3553042',
|
82
78
|
:comments_count => '209',
|
83
79
|
:header => 'Looking For Mr. Roombar',
|
84
|
-
:subheader =>
|
85
|
-
:writeup => "
|
86
|
-
:specs => "Authorized for SquareTrade Extended Warranty
|
87
|
-
:details => "\
|
80
|
+
:subheader => "This is nice, isn\342\200\231t it? I have to say, I was surprised when you called me. Most people who date robots prefer the kind that, you know, look like people.",
|
81
|
+
:writeup => "I just keep thinking about all those times you came over to play cards with Len and Sherry. Of course I noticed you, but I never imagined you noticed me, way down on the floor in my self-charging home base. Well, there was this one time when I heard you ask Sherry about me. It made my heart leap. But then Sherry just started talking about how convenient I was, and how my counter-rotating Bristle Brush and Beater Brush work together like a broom and dustpan to lift hair, dust, and debris out of carpet. I just assumed you were interested in my cleaning abilities, not me personally. Glad I was wrong!\nWow, everything on the menu looks so good. I\342\200\231d heard about this place, but it\342\200\231s even nicer than I imagined. Well, except for the floors. It\342\200\231s like they think they can slap down a brown carpet and forget about ever vacuuming again. Well, they can\342\200\231t fool me and my Dirt Detect capability. Like that smear of, like, dried holladaise sauce or whatever it is, over by that booth. See, if it was up to me, I\342\200\231d really bear down with my Spot Clean mode, and I\342\200\231d have that crud lifted up before you could say \342\200\234new, improved filter\342\200\235. See, that\342\200\231s the difference between me and a regular old-\nListen to me. Talking shop in the middle of a lovely first date. Sorry. I just don\342\200\231t get out much.\nSo, uh, have you seen any interesting movies lately? I haven\342\200\231t. I\342\200\231ve actually never seen a movie in a theater. Every time I try, I see all that popcorn and all those crumbs all over the floor, and I just can\342\200\231t resist getting down there and sweeping it all up. Well, you can imagine the noise. I\342\200\231m loud, I know. What can I say? I grew up in a loud family. You get us together, it\342\200\231s VRRRR-VRRRR-VRRRR the whole time.\nWhat? Go back to your place? Sure. Yeah, that would be fun. I\342\200\231m not that hungry anyway. Um, yes, I did bring my Virtual Wall. Yeah, I emptied my Debris Bin right before you picked me up. Why do you ask?\nOh. Oh, I see. So it\342\200\231s like that. I guess I should\342\200\231ve known.",
|
82
|
+
:specs => "Authorized for SquareTrade Extended Warranty$(document).ready(function() {st_widget.create({itemCondition:'Refurbished',itemDescription:'iRobot Roomba 530 Robotic Vacuum with Virtual Wall',itemPrice:'129.99',bannerStyle:'wide',widgetType:'quote',merchantID:'subscrip_014793207843'}); });Warranty: 90 Day iRobot\nFeatures:\nEfficiently vacuums dirt, debris, pet hair, dust, allergens and more from carpets and hard floors\n Counter rotating Bristle Brush and Beater Brush work together like a dustpan and broom\n Sturdy Bristle Brush digs deep into carpet fibers to grab dirt, debris, pet hair and more\n Powerful vacuum sucks large and small debris into the large, bag-less bin\n Fine filter traps dust, pollen and tiny particulate inside the bin\n Cleans the whole floor, under and around furniture, into corners and along wall edges\n Detects dirtier areas and spends more time cleaning them\n Spot Clean provides quick clean-up of spills and concentrated messes\n Automatically senses and avoids stairs and other drop-offs\n Simple operation\342\200\224just press the Clean button and Roomba does the rest\n Automatically returns to its self-charging Home Base\302\256 to dock and recharge between cleanings\n Faster counter-rotating brushes with improved design pick up more hair and debris and are easier to remove and clean\n Improved filter captures more dust and allergens while a larger bin holds more debris\n Improved anti-tangle technology keeps Roomba from getting stuck on cords, carpet fringe and tassels\n Improved sidebrush makes Roomba even more efficient at cleaning edges and corners\nAdditional Photo:\nUnderbelly\n In Home Base with Virtual Wall\n Virtual Wall\nIn the box:\n1 iRobot Roomba 530\n 1 Virtual Wall\302\240\n 1 Self-charging Home Base\n 1 Power Supply (3 hour charge time)\n 1 Rechargeable Battery\n 1 Filter \n\302\240",
|
83
|
+
:details => "Condition:\n Refurbished\n Product:\n \n\t\t\t\t\t\n\t\t\t\t\t 1 iRobot Roomba 530 Robotic Vacuum with Virtual Wall and Self-Charging Home Base",
|
88
84
|
:purchase_url => nil
|
89
85
|
}
|
90
86
|
}
|
data/test/woot_test.rb
CHANGED
@@ -4,15 +4,15 @@ class WootTest < Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
woots.each do |subdomain, woot|
|
6
6
|
context "When parsing http://#{subdomain}.#{Woot::DOMAIN}/ it" do
|
7
|
-
attributes.each do |attribute|
|
7
|
+
Woot.attributes.each do |attribute|
|
8
8
|
should "have a key for #{attribute}" do
|
9
|
-
assert woot.
|
9
|
+
assert woot.respond_to?(attribute)
|
10
10
|
end
|
11
11
|
|
12
12
|
unless possible_blank_attributes.include?(attribute)
|
13
13
|
should "have a value for #{attribute}" do
|
14
14
|
assert !woot.send(attribute).nil?
|
15
|
-
assert !woot.send(attribute).empty?
|
15
|
+
assert !woot.send(attribute).to_s.empty?
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -22,7 +22,7 @@ class WootTest < Test::Unit::TestCase
|
|
22
22
|
context 'When parsing the fixture' do
|
23
23
|
fixtures.each do |subdomain, file|
|
24
24
|
context "for http://#{subdomain}.#{Woot::DOMAIN} it" do
|
25
|
-
attributes.each do |attribute|
|
25
|
+
Woot.attributes.each do |attribute|
|
26
26
|
should "parse the correct value for #{attribute}" do
|
27
27
|
assert_equal WOOT_FIXTURE_VALUES[subdomain][attribute], self.class.woots[subdomain].send(attribute)
|
28
28
|
end
|
data/woot.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{woot}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Sean Huber"]
|
@@ -43,18 +43,18 @@ Gem::Specification.new do |s|
|
|
43
43
|
s.specification_version = 3
|
44
44
|
|
45
45
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
46
|
-
s.add_runtime_dependency(%q<
|
46
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
|
47
47
|
s.add_runtime_dependency(%q<tweetstream>, [">= 0"])
|
48
48
|
s.add_development_dependency(%q<fakeweb>, [">= 0"])
|
49
49
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
50
50
|
else
|
51
|
-
s.add_dependency(%q<
|
51
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
52
52
|
s.add_dependency(%q<tweetstream>, [">= 0"])
|
53
53
|
s.add_dependency(%q<fakeweb>, [">= 0"])
|
54
54
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
55
55
|
end
|
56
56
|
else
|
57
|
-
s.add_dependency(%q<
|
57
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
58
58
|
s.add_dependency(%q<tweetstream>, [">= 0"])
|
59
59
|
s.add_dependency(%q<fakeweb>, [">= 0"])
|
60
60
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: woot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Huber
|
@@ -13,7 +13,7 @@ date: 2009-11-03 00:00:00 -08:00
|
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: nokogiri
|
17
17
|
type: :runtime
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|