Permalink
Browse files

Initial commit.

  • Loading branch information...
1 parent 7ae88c6 commit 8d05ac74bf664e2e7da2573557541bb5d967238d @maxim committed Dec 7, 2009
Showing with 2,764 additions and 19 deletions.
  1. +0 −16 README.rdoc
  2. +4 −1 Rakefile
  3. +7 −0 bin/sogger
  4. +7 −0 lib/sogger.rb
  5. +38 −0 lib/sogger/question.rb
  6. +61 −0 lib/sogger/runner.rb
  7. +45 −0 lib/sogger/sogger.rb
  8. +1,150 −0 test/fixtures/so_feed_sample.xml
  9. +15 −0 test/helper.rb
  10. +60 −0 test/test_question.rb
  11. +9 −0 test/test_runner.rb
  12. +65 −2 test/test_sogger.rb
  13. +1,303 −0 tmp/feed.xml
View
@@ -1,17 +1 @@
-= sogger
-Description goes here.
-
-== Note on Patches/Pull Requests
-
-* Fork the project.
-* Make your feature addition or bug fix.
-* Add tests for it. This is important so I don't break it in a
- future version unintentionally.
-* Commit, do not mess with rakefile, version, or history.
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-* Send me a pull request. Bonus points for topic branches.
-
-== Copyright
-
-Copyright (c) 2009 Maxim Chernyak. See LICENSE for details.
View
@@ -10,7 +10,10 @@ begin
gem.email = "max@bitsonnet.com"
gem.homepage = "http://github.com/maxim/sogger"
gem.authors = ["Maxim Chernyak"]
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
+ gem.add_dependency "meow"
+ gem.add_dependency "nokogiri"
+ gem.add_development_dependency "shoulda"
+ gem.add_development_dependency "fakeweb"
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end
Jeweler::GemcutterTasks.new
View
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+require 'rubygems'
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'sogger'))
+
+sogger = Sogger::Runner.new
+puts "Press CTRL+C to stop..."
+sogger.run
View
@@ -0,0 +1,7 @@
+require 'open-uri'
+require 'nokogiri'
+require 'meow'
+
+require File.dirname(__FILE__) + "/sogger/question.rb"
+require File.dirname(__FILE__) + "/sogger/sogger.rb"
+require File.dirname(__FILE__) + "/sogger/runner.rb"
View
@@ -0,0 +1,38 @@
+module Sogger
+ class Question
+ include Comparable
+ attr_accessor :attributes
+
+ def initialize(attributes = {})
+ @attributes = attributes
+ end
+
+ def self.from_xml(xml_element)
+ attributes = { :title => xml_element.css('title').text,
+ :url => xml_element.css('id').text,
+ :tags => xml_element.css('category').map{|c| c['term']},
+ :published => DateTime.parse(xml_element.css('published').text) }
+ new attributes
+ end
+
+ def title
+ @attributes[:title]
+ end
+
+ def url
+ @attributes[:url]
+ end
+
+ def tags
+ @attributes[:tags]
+ end
+
+ def published
+ @attributes[:published]
+ end
+
+ def <=>(other)
+ other.published <=> published
+ end
+ end
+end
View
@@ -0,0 +1,61 @@
+module Sogger
+ class Runner
+ UPDATE_INTERVAL = 60
+ GROWL_INTERVAL = 20
+
+ def initialize
+ @cached_sogger = Sogger.new
+ @remote_sogger = Sogger.new
+ @questions_buffer = []
+ @growler = Meow.new("Sogger")
+ end
+
+ def run
+ updater = Thread.new("updater") do
+ while true
+ puts "Updater: Downloading feed..."
+ @remote_sogger.download_feed!
+
+ new_questions = []
+ unless @cached_sogger.questions.empty?
+ puts "Updater: Comparing feeds..."
+ new_questions = (@remote_sogger - @cached_sogger).questions
+ end
+
+ unless new_questions.empty?
+ puts "Updater: Adding #{new_questions.size} questions to buffer..."
+ @questions_buffer += new_questions
+ end
+
+ puts "Updater: Caching feed..."
+ @remote_sogger.save!
+
+ puts "Updater: Loading cached feed..."
+ @cached_sogger.load!
+
+ puts "Updater: Sleeping for #{UPDATE_INTERVAL} seconds..."
+ sleep UPDATE_INTERVAL
+ end
+ end
+
+ notifier = Thread.new("notifier") do
+ while true
+ unless @questions_buffer.empty?
+ puts "\nNotifier: Growling..."
+ growl(@questions_buffer.pop)
+ end
+
+ sleep GROWL_INTERVAL
+ end
+ end
+
+ [updater, notifier].map(&:join)
+ end
+
+ def growl(question)
+ @growler.notify(question.tags.join(', '), question.title) do
+ system "open", question.url
+ end
+ end
+ end
+end
View
@@ -0,0 +1,45 @@
+module Sogger
+ class Sogger
+ class << self; attr_accessor :save_path end
+ attr_reader :raw_data
+
+ SO_FEED_URL = "http://stackoverflow.com/feeds"
+ DEFAULT_SAVE_PATH = "tmp/feed.xml"
+ self.save_path = DEFAULT_SAVE_PATH
+
+ def initialize(questions = [])
+ @questions = questions
+ end
+
+ def questions
+ @questions.sort
+ end
+
+ def download_feed!
+ @raw_data = open(SO_FEED_URL).read
+ parse_raw_data
+ end
+
+ def save!
+ File.open(self.class.save_path, "w") do |file|
+ file.write @raw_data
+ end
+ end
+
+ def load!
+ @raw_data = File.read(self.class.save_path)
+ parse_raw_data
+ end
+
+ def -(other)
+ newest_other = other.questions.first
+ Sogger.new(questions.select{|q| q < newest_other })
+ end
+
+ private
+ def parse_raw_data
+ @data = Nokogiri::XML(@raw_data)
+ @questions = @data.css('entry').map{ |e| Question.from_xml(e) }
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 8d05ac7

Please sign in to comment.