woot 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,14 +8,14 @@ Scrapes woot.com sites
8
8
 
9
9
  == Usage
10
10
 
11
- Simply call <tt>Woot.scrape</tt> and optionally specify the woot subdomain (e.g. www, shirt, kids, wine, etc).
12
- Defaults to www. Returns a Struct object.
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.scrape
14
+ woot = Woot.new
15
15
  puts woot.title
16
16
  puts woot.price
17
17
 
18
- woot = Woot.scrape(:shirt)
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 'scrapi'
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.7
1
+ 0.1.0
@@ -1,9 +1,10 @@
1
- require 'scrapi'
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
- def self.scrape(subdomain = :www)
21
- url = "http://#{subdomain}.#{DOMAIN}/"
22
- if subdomain.to_s == 'sellout'
23
- url = Scraper.define do
24
- process_first "div.bd>div.img>a", :url => '@href'
25
- result :url
26
- end.scrape(Net::HTTP.get(SELLOUT_DOMAIN, '/')).gsub('&amp;', '&')
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.selectors(subdomain = :www)
40
- @selectors = {
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 scrape(subdomain) unless subdomain.nil?
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('&amp;', '&') : "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
@@ -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.scrape(subdomain) }
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 => "\n+ $5 shipping\n",
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 => "\nMy mother-in-law thought we were crazy. &ldquo;You two are going to end up pulverized by a giant world-eating alien just like your Aunt Regina!&rdquo; We&rsquo;ve 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&rsquo;s cyborg velociraptors. And around the fourth time some villain&rsquo;s robot henchman gets plowed through your wall and into your living room without so much as a &ldquo;Sorry, Citizens&rdquo; from whatever hero happens to be on duty that day, you start to get a little annoyed.\nI&rsquo;m not complaining, really. That&rsquo;s what cape insurance is for, after all. That&rsquo;s 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&rsquo;re 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&rsquo;d love to hear it. I know they&rsquo;re just doing their inner-monologue thing, but it gets kinda creepy.\n",
67
- :specs => "\n\nWarranty: 90 Day Woot Limited Warranty\nFeatures:\n\nPatented triple-flange ear bud design provides optimal noise protection for an excellent fit that radically reduces noise\nIntegrated volume control places fingertip adjustment of any iPod or MP3 player easily within reach\n24 decibel Noise Reduction Rating brings an end to dangerously over-cranked tunes struggling to compete with loud external sounds\nUse 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\nUltra comfortable design from the same company who brought you the industry leading WorkTunes\niPod and MP3 compatible (any device with a 3.5mm plug)\nMeets ANSI/OSHA Standards\n\nAdditional Photos:\n\nBlockade Noise Isolating Earbuds\nVolume Control\nTriple-flange ear bud design\n\nIn the box:\n\n2 AO Safety 99014 Blockade Noise Isolating Earbuds\n6 Extra Pairs of Ear Tips\n2 Carrying Bags\n\n",
68
- :details => "\nCondition:\nNew\nProduct:\n2 AO Safety 99014 Blockade Noise Isolating Earbuds\n",
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 => "\n+ $5 shipping\n",
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 => 'This is nice, isn&rsquo;t 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.',
85
- :writeup => "\nI 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&rsquo;d heard about this place, but it&rsquo;s even nicer than I imagined. Well, except for the floors. It&rsquo;s like they think they can slap down a brown carpet and forget about ever vacuuming again. Well, they can&rsquo;t 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&rsquo;d really bear down with my Spot Clean mode, and I&rsquo;d have that crud lifted up before you could say &ldquo;new, improved filter&rdquo;. See, that&rsquo;s 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&rsquo;t get out much.\nSo, uh, have you seen any interesting movies lately? I haven&rsquo;t. I&rsquo;ve 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&rsquo;t resist getting down there and sweeping it all up. Well, you can imagine the noise. I&rsquo;m loud, I know. What can I say? I grew up in a loud family. You get us together, it&rsquo;s VRRRR-VRRRR-VRRRR the whole time.\nWhat? Go back to your place? Sure. Yeah, that would be fun. I&rsquo;m 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&rsquo;s like that. I guess I should&rsquo;ve known.\n",
86
- :specs => "Authorized for SquareTrade Extended Warranty\n\n\n\n//<![CDATA[\n$(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'}); });\n//\n\n\nWarranty: 90 Day iRobot\nFeatures:\n\nEfficiently vacuums dirt, debris, pet hair, dust, allergens and more from carpets and hard floors\nCounter rotating Bristle Brush and Beater Brush work together like a dustpan and broom\nSturdy Bristle Brush digs deep into carpet fibers to grab dirt, debris, pet hair and more\nPowerful vacuum sucks large and small debris into the large, bag-less bin\nFine filter traps dust, pollen and tiny particulate inside the bin\nCleans the whole floor, under and around furniture, into corners and along wall edges\nDetects dirtier areas and spends more time cleaning them\nSpot Clean provides quick clean-up of spills and concentrated messes\nAutomatically senses and avoids stairs and other drop-offs\nSimple operation&mdash;just press the Clean button and Roomba does the rest\nAutomatically returns to its self-charging Home Base&reg; to dock and recharge between cleanings\nFaster counter-rotating brushes with improved design pick up more hair and debris and are easier to remove and clean\nImproved filter captures more dust and allergens while a larger bin holds more debris\nImproved anti-tangle technology keeps Roomba from getting stuck on cords, carpet fringe and tassels\nImproved sidebrush makes Roomba even more efficient at cleaning edges and corners\n\nAdditional Photo:\n\nUnderbelly\nIn Home Base with Virtual Wall\nVirtual Wall\n\nIn the box:\n\n1 iRobot Roomba 530\n1 Virtual Wall&nbsp;\n1 Self-charging Home Base\n1 Power Supply (3 hour charge time)\n1 Rechargeable Battery\n1 Filter\n\n&nbsp;\n",
87
- :details => "\nCondition:\nRefurbished\nProduct:\n1 iRobot Roomba 530 Robotic Vacuum with Virtual Wall and Self-Charging Home Base\n",
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
  }
@@ -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.members.include?(attribute.to_s)
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
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{woot}
8
- s.version = "0.0.7"
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<scrapi>, [">= 0"])
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<scrapi>, [">= 0"])
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<scrapi>, [">= 0"])
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.7
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: scrapi
16
+ name: nokogiri
17
17
  type: :runtime
18
18
  version_requirement:
19
19
  version_requirements: !ruby/object:Gem::Requirement