ruby-hackernews 1.3.3 → 1.3.5

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDk5ZmI0Zjc1YzU2M2Y5NTA1NTM4YmEwZTFjYWJjODMyMWViMjVlYQ==
5
+ data.tar.gz: !binary |-
6
+ NGMyOTJiN2U3ZGJlNjEzNzUzZGNlOTIzZjQ2ZDRiNWNmZWRhN2MwYw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NTk4NTUzMWVkYmRhYzdmZDA3OGRjZTY2MDE3MmFhNTZkYmUxM2FjMWVmMDlh
10
+ MTJiYTZlNWU5YmE3ZmI2MTY3NzBkNGMzODA2ODczYTViYzQ0YWNhMzZmOTNi
11
+ NDllMWRmN2RhMGM4NTQ5NzViZjQyZjFiNjhmM2I0NTYxNTRkOTM=
12
+ data.tar.gz: !binary |-
13
+ ZmRjM2E4Y2Q5MDFiODliY2JmMzA1YzVlYTUzM2FhMzBhYWIyMTRhMWM1MjRh
14
+ ZGQzYmIwNDBiNzViZmQ2YTk3MjczZTE0YzhkMDEzZWRjNjAxOWQ4NDVkMDg5
15
+ NTliZDI2Y2UwMmU3MDBhOTUzNmUzYzJjMmVhMGRlNWU5MDY1YzA=
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ nbproject
20
+ *~
21
+ *.save*
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ # rspec is here and not in gemspec, because :require is not supported there
5
+ # put every other gem in gemspec
6
+ gem 'rspec', :require => "spec"
7
+ end
8
+
9
+ # Specify your gem's dependencies in ruby-hackernews.gemspec
10
+ gemspec
data/README.rdoc CHANGED
@@ -38,6 +38,8 @@ There are methods for getting specific entry types:
38
38
  Entry.questions # gets the first page of questions (ask NH)
39
39
  Entry.newest # gets the first page of new links (new)
40
40
  Entry.jobs # gets the first page of job offerts (jobs)
41
+ Entry.shows # gets the first page of shows (show HN)
42
+ Entry.new_shows # gets the first page of new shows (show HN new)
41
43
 
42
44
  You can also get a single entry by its ID:
43
45
 
@@ -160,6 +162,7 @@ Will return the HN comment url of the last submitted story of that user
160
162
 
161
163
  == THANKS TO
162
164
 
165
+ - Anton Katunin ( https://github.com/antulik ) for adding the gemfile and putting all specs into modules
163
166
  - Mike Kelly ( https://github.com/mikekelly ) for fixing a bug in TimeInfo.time
164
167
  - Marc Köhlbrugge ( http://github.com/marckohlbrugge ) fox fixing and formatting the documentation
165
168
  - Peter Cooper ( http://github.com/peterc ) for bug reports
@@ -168,3 +171,4 @@ Will return the HN comment url of the last submitted story of that user
168
171
  - Wayne ( http://github.com/BlissOfBeing ) for adding User.submissions, Entry.comments_url and cleaning up the Rakefile
169
172
  - Daniel Da Cunha ( http://github.com/ddacunha ) for a fix on Entry#comments_count
170
173
  - Nathan Campos ( http://github.com/nathanpc) for adding #text to Entry
174
+ - Girish Sonawane ( https://github.com/girishso) for adding Entry#shows and Entry#new_shows methods
data/Rakefile CHANGED
@@ -1,28 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
1
3
 
2
- require 'rubygems'
3
- require 'rake'
4
- require 'rake/clean'
5
- require 'rubygems/package_task'
6
- require 'rdoc/task'
7
- require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
8
5
 
9
- spec = Gem::Specification.new do |s|
10
- s.name = 'ruby-hackernews'
11
- s.version = '1.3.3'
12
- s.add_dependency('require_all', '>= 1.1.0')
13
- s.add_dependency('mechanize', '>= 1.0.0')
14
- s.has_rdoc = false
15
- s.homepage = "http://github.com/bolthar/ruby-hackernews"
16
- s.summary = 'An interface to Hacker News'
17
- s.description = s.summary
18
- s.author = 'Andrea Dallera'
19
- s.email = 'andrea@andreadallera.com'
20
- s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib}/**/*")
21
- s.require_path = "lib"
22
- end
6
+ RSpec::Core::RakeTask.new('spec')
23
7
 
24
- Gem::PackageTask.new(spec) do |p|
25
- p.gem_spec = spec
26
- p.need_tar = true
27
- p.need_zip = true
28
- end
8
+ task :default => :spec
@@ -1,4 +1,6 @@
1
1
 
2
+ require "ruby-hackernews/version"
3
+
2
4
  require 'rubygems'
3
5
  require 'mechanize'
4
6
 
@@ -3,18 +3,19 @@ module RubyHackernews
3
3
  class Comment
4
4
  include Enumerable
5
5
 
6
- attr_reader :text
6
+ attr_reader :text_html
7
7
  attr_reader :voting
8
8
  attr_reader :user
9
9
 
10
10
  attr_accessor :parent
11
11
 
12
- def initialize(text, voting, user_info, reply_link, absolute_link)
13
- @text = text
12
+ def initialize(text_html, voting, user_info, reply_link, absolute_link, parent_link)
13
+ @text_html = text_html
14
14
  @voting = voting
15
15
  @user = user_info
16
16
  @reply_link = reply_link
17
17
  @absolute_link = absolute_link
18
+ @parent_link = parent_link
18
19
  @children = []
19
20
  end
20
21
 
@@ -22,6 +23,18 @@ module RubyHackernews
22
23
  return @absolute_link.split("=")[1].to_i
23
24
  end
24
25
 
26
+ def parent_id
27
+ if parent
28
+ parent.id
29
+ elsif @parent_link
30
+ @parent_link[/\d+/].to_i
31
+ end
32
+ end
33
+
34
+ def text
35
+ @text ||= text_html.gsub(/<.{1,2}>/, "")
36
+ end
37
+
25
38
  def <<(comment)
26
39
  comment.parent = self
27
40
  @children << comment
@@ -43,6 +56,12 @@ module RubyHackernews
43
56
  return CommentService.new.get_new_comments(pages)
44
57
  end
45
58
 
59
+ def self.newest_with_extra(pages = 1, url = nil)
60
+ args = [pages]
61
+ args << url unless url.nil?
62
+ return CommentService.new.get_new_comments_with_extra *args
63
+ end
64
+
46
65
  def self.find(id)
47
66
  return CommentService.new.find_by_id(id)
48
67
  end
@@ -39,6 +39,12 @@ module RubyHackernews
39
39
  return EntryService.new.get_new_entries(pages)
40
40
  end
41
41
 
42
+ def self.newest_with_extra(pages = 1, url = nil)
43
+ args = [pages]
44
+ args << url unless url.nil?
45
+ return EntryService.new.get_entries_with_extra *args
46
+ end
47
+
42
48
  def self.questions(pages = 1)
43
49
  return EntryService.new.get_questions(pages)
44
50
  end
@@ -47,6 +53,14 @@ module RubyHackernews
47
53
  return EntryService.new.get_jobs(pages)
48
54
  end
49
55
 
56
+ def self.shows(pages = 1)
57
+ return EntryService.new.get_shows(pages)
58
+ end
59
+
60
+ def self.new_shows(pages = 1)
61
+ return EntryService.new.get_new_shows(pages)
62
+ end
63
+
50
64
  def self.find(id)
51
65
  return EntryService.new.find_by_id(id)
52
66
  end
@@ -11,10 +11,11 @@ module RubyHackernews
11
11
  return Time.now - @unit_of_measure * @value
12
12
  end
13
13
 
14
- def initialize(value, unit_of_measure)
14
+ def initialize(value, unit_of_measure)
15
15
  @value = value
16
16
  if(unit_of_measure)
17
- descriptor = unit_of_measure[unit_of_measure.length - 1].chr == "s" ? unit_of_measure[0..unit_of_measure.length - 2] : unit_of_measure
17
+ descriptor = unit_of_measure
18
+ descriptor = unit_of_measure[0..-2] if unit_of_measure[-1].chr == 's'
18
19
  @unit_of_measure = self.class.const_get(descriptor.upcase)
19
20
  end
20
21
  end
@@ -28,11 +28,12 @@ module RubyHackernews
28
28
  comments = []
29
29
  target = comments
30
30
  current_level = 0
31
- table.search("table/tr").select do |tr|
31
+ trs = table.search("table/tr").select do |tr|
32
32
  tr.search("span.comment").inner_html != "[deleted]"
33
- end.each do |tr|
33
+ end
34
+ trs.each do |tr|
34
35
  comment = parse_comment(tr)
35
- level = tr.search("img[@src='http://ycombinator.com/images/s.gif']").first['width'].to_i / 40
36
+ level = tr.search("img[@src='s.gif']").first['width'].to_i / 40
36
37
  difference = current_level - level
37
38
  (difference + 1).times do
38
39
  target = target.kind_of?(Comment) && target.parent ? target.parent : comments
@@ -45,8 +46,13 @@ module RubyHackernews
45
46
  end
46
47
 
47
48
  def get_new_comments(pages = 1, url = ConfigurationService.comments_url)
49
+ get_new_comments_with_extra(pages,url)[:comments]
50
+ end
51
+
52
+ def get_new_comments_with_extra(pages = 1, url = ConfigurationService.comments_url)
48
53
  parser = EntryPageParser.new(agent.get(url))
49
54
  comments = []
55
+ next_url = nil
50
56
  pages.times do
51
57
  lines = parser.get_lines
52
58
  lines.each do |line|
@@ -55,22 +61,24 @@ module RubyHackernews
55
61
  next_url = parser.get_next_url || break
56
62
  parser = EntryPageParser.new(agent.get(next_url))
57
63
  end
58
- return comments
64
+ return {:comments => comments, :next_url => next_url}
59
65
  end
60
66
 
61
67
  def parse_comment(element)
62
- text = ""
68
+ text_html = ""
63
69
  element.search("span.comment").first.children.each do |ch|
64
- text = ch.inner_html.gsub(/<.{1,2}>/,"")
70
+ text_html = ch.inner_html
65
71
  end
66
72
  header = element.search("span.comhead").first
67
73
  voting = VotingInfoParser.new(element.search("td/center/a"), header).parse
68
74
  user_info = UserInfoParser.new(header).parse
69
75
  reply_link = element.search("td[@class='default']/p//u//a").first
70
- reply_url = reply_link['href'] if reply_link
76
+ reply_url = reply_link['href'] if reply_link
71
77
  absolute_link_group = header.search("a")
72
78
  absolute_url = absolute_link_group.count == 2 ? absolute_link_group[1]['href'] : nil
73
- return Comment.new(text, voting, user_info, reply_url, absolute_url)
79
+ parent_link = header.search("a[text()*='parent']").first
80
+ parent_url = parent_link['href'] if parent_link
81
+ return Comment.new(text_html, voting, user_info, reply_url, absolute_url, parent_url)
74
82
  end
75
83
 
76
84
  def write_comment(page_url, comment)
@@ -22,6 +22,14 @@ module RubyHackernews
22
22
  return File.join(self.base_url, "jobs")
23
23
  end
24
24
 
25
+ def self.show_url
26
+ return File.join(self.base_url, "show")
27
+ end
28
+
29
+ def self.new_shows_url
30
+ return File.join(self.base_url, "shownew")
31
+ end
32
+
25
33
  def self.comments_url
26
34
  return File.join(self.base_url, "newcomments")
27
35
  end
@@ -4,8 +4,13 @@ module RubyHackernews
4
4
  include MechanizeContext
5
5
 
6
6
  def get_entries(pages = 1, url = ConfigurationService.base_url)
7
+ get_entries_with_extra(pages, url)[:entries]
8
+ end
9
+
10
+ def get_entries_with_extra(pages = 1, url = ConfigurationService.base_url)
7
11
  parser = EntryPageParser.new(agent.get(url))
8
12
  entry_infos = []
13
+ next_url = nil
9
14
  pages.times do
10
15
  lines = parser.get_lines
11
16
  (lines.length / 2).times do
@@ -14,7 +19,7 @@ module RubyHackernews
14
19
  next_url = parser.get_next_url || break
15
20
  parser = EntryPageParser.new(agent.get(next_url))
16
21
  end
17
- return entry_infos
22
+ return {:entries => entry_infos, :next_url => next_url}
18
23
  end
19
24
 
20
25
  def find_by_id(id)
@@ -34,7 +39,15 @@ module RubyHackernews
34
39
  def get_jobs(pages = 1)
35
40
  return get_entries(pages, ConfigurationService.jobs_url)
36
41
  end
37
-
42
+
43
+ def get_shows(pages = 1)
44
+ return get_entries(pages, ConfigurationService.show_url)
45
+ end
46
+
47
+ def get_new_shows(pages = 1)
48
+ return get_entries(pages, ConfigurationService.new_shows_url)
49
+ end
50
+
38
51
  def submit(title, url)
39
52
  require_authentication
40
53
  form = agent.get(ConfigurationService.submit_url).forms.first
@@ -61,7 +74,7 @@ module RubyHackernews
61
74
  form.x = text
62
75
  form.submit
63
76
  end
64
-
77
+
65
78
  end
66
79
 
67
80
  end
@@ -0,0 +1,3 @@
1
+ module RubyHackernews
2
+ VERSION = "1.3.5"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruby-hackernews/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby-hackernews"
8
+ spec.version = RubyHackernews::VERSION
9
+ spec.authors = ["Andrea Dallera"]
10
+ spec.email = ["andrea@andreadallera.com"]
11
+ spec.description = %q{An interface to Hacker News}
12
+ spec.summary = %q{An interface to Hacker News}
13
+ spec.homepage = "http://github.com/blissofbeing/ruby-hackernews"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.has_rdoc = false
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_dependency('require_all', '>= 1.1.0')
26
+ spec.add_dependency('mechanize', '>= 1.0.0')
27
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyHackernews
4
+ describe TimeInfo do
5
+
6
+ before :all do
7
+ class RubyHackernews::TimeInfo
8
+ DUMMY = 10
9
+ end
10
+ end
11
+
12
+ describe :initialize do
13
+
14
+ it "should set unit_of_measure as uppercase descriptor const if singular" do
15
+ timeinfo = TimeInfo.new(5, "dummy")
16
+ timeinfo.instance_variable_get(:@unit_of_measure).should == 10
17
+ end
18
+
19
+ it "should set unit_of_measure as uppercase minus last s descriptor const if plural" do
20
+ timeinfo = TimeInfo.new(5, "dummys")
21
+ timeinfo.instance_variable_get(:@unit_of_measure).should == 10
22
+ end
23
+
24
+ end
25
+
26
+ describe :time do
27
+
28
+ it "should return right amount when passed seconds" do
29
+ TimeInfo.new(15, "seconds").time.round.should == (Time.now - 15).round
30
+ end
31
+
32
+ it "should return right amount when passed minutes" do
33
+ TimeInfo.new(24, "minutes").time.round.should == (Time.now - 24*60).round
34
+ end
35
+
36
+ it "should return right amount when passed hours" do
37
+ TimeInfo.new(3, "hours").time.round.should == (Time.now - 3*3600).round
38
+ end
39
+
40
+ it "should return right amount when passed days" do
41
+ TimeInfo.new(16, "days").time.round.should == (Time.now - 16*86400).round
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ #useless purely integration tests... remove?
4
+ module RubyHackernews
5
+ describe EntryService do
6
+
7
+ describe :get_entries do
8
+
9
+ it "should always call EntryPageParser with current agent" do
10
+ parser = stub()
11
+ parser.should_receive(:get_lines).and_return([])
12
+ parser.should_receive(:get_next_url).and_return(nil)
13
+ agent = stub()
14
+ agent.should_receive(:get).any_number_of_times.and_return(:page)
15
+ MechanizeContext.create(:default, agent)
16
+ service = EntryService.new
17
+ EntryPageParser.should_receive(:new).any_number_of_times.with(:page).and_return(parser)
18
+ service.get_entries
19
+ end
20
+
21
+ it "should always call get_lines 'pages' number of times" do
22
+ pages = 5
23
+ parser = stub()
24
+ parser.should_receive(:get_lines).exactly(pages).times.and_return([])
25
+ parser.should_receive(:get_next_url).any_number_of_times.and_return(nil)
26
+ agent = stub()
27
+ agent.should_receive(:get).any_number_of_times.and_return(:page)
28
+ MechanizeContext.create(:default, agent)
29
+ service = EntryService.new
30
+ EntryPageParser.should_receive(:new).any_number_of_times.with(:page).and_return(parser)
31
+ service.get_entries(pages)
32
+ end
33
+
34
+ it "should always call get_next_url 'pages' number of times" do
35
+ pages = 5
36
+ parser = stub()
37
+ parser.should_receive(:get_lines).any_number_of_times.and_return([])
38
+ parser.should_receive(:get_next_url).exactly(pages).times.and_return(nil)
39
+ agent = stub()
40
+ agent.should_receive(:get).any_number_of_times.and_return(:page)
41
+ MechanizeContext.create(:default, agent)
42
+ service = EntryService.new
43
+ EntryPageParser.should_receive(:new).any_number_of_times.with(:page).and_return(parser)
44
+ service.get_entries(pages)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe CommentsInfoParser do
6
+
7
+ before(:each) do
8
+ @line = ParserHelper.second_line
9
+ @comment_link = "item?id=1645686"
10
+ end
11
+
12
+ describe :parse do
13
+
14
+ it "should not call CommentsInfo.new if comment tag is missing" do
15
+ parser = CommentsInfoParser.new(ParserHelper.second_line_comments_tag_missing)
16
+ CommentsInfo.should_not_receive(:new)
17
+ parser.parse
18
+ end
19
+
20
+ it "should call CommentsInfo.new with comments count if not 'discuss'" do
21
+ parser = CommentsInfoParser.new(@line)
22
+ CommentsInfo.should_receive(:new).with(2, anything)
23
+ parser.parse
24
+ end
25
+
26
+ it "should call CommentsInfo.new with 0 if is 'discuss'" do
27
+ parser = CommentsInfoParser.new(ParserHelper.second_line_no_comments_yet)
28
+ CommentsInfo.should_receive(:new).with(0, anything)
29
+ parser.parse
30
+ end
31
+
32
+ it "should call CommentsInfo.new with comment link" do
33
+ parser = CommentsInfoParser.new(@line)
34
+ CommentsInfo.should_receive(:new).with(anything, @comment_link)
35
+ parser.parse
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe EntryPageParser do
6
+
7
+ before :each do
8
+ @page = ParserHelper.full_page
9
+ end
10
+
11
+ describe :get_lines do
12
+ it "should always return 60 elements" do
13
+ lines = EntryPageParser.new(@page).get_lines
14
+ lines.length.should == 60
15
+ end
16
+ end
17
+
18
+ describe :get_next_url do
19
+ it "should get the url in 'More' element" do
20
+ url = EntryPageParser.new(@page).get_next_url
21
+ url.should == "/x?fnid=9LO1ba1swy"
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe EntryParser do
6
+
7
+ before(:each) do
8
+ @first_line = ParserHelper.first_line_full
9
+ @second_line = ParserHelper.second_line_full
10
+ end
11
+
12
+ describe :parse do
13
+
14
+ it "should call Entry.new with the right number" do
15
+ parser = EntryParser.new(@first_line, @second_line)
16
+ Entry.should_receive(:new).with(2, anything, anything, anything, anything, anything)
17
+ parser.parse
18
+ end
19
+
20
+ it "should return an entry with correct link" do
21
+ parser = EntryParser.new(@first_line, @second_line)
22
+ entry = parser.parse
23
+ entry.link.title.should == "The Tragic Death of Practically Everything"
24
+ entry.link.href.should == "http://technologizer.com/2010/08/18/the-tragic-death-of-practically-everything/"
25
+ entry.link.site.should == "technologizer.com"
26
+ end
27
+
28
+ it "should return an entry with correct voting" do
29
+ parser = EntryParser.new(@first_line, @second_line)
30
+ entry = parser.parse
31
+ entry.voting.upvote.should == "vote?for=1645745&dir=up&whence=%6e%65%77%73"
32
+ entry.voting.downvote.should be_nil
33
+ entry.voting.score.should == 59
34
+ end
35
+
36
+ it "should return an entry with correct user" do
37
+ parser = EntryParser.new(@first_line, @second_line)
38
+ entry = parser.parse
39
+ entry.user.name.should == "dhotson"
40
+ entry.user.page.should == "user?id=dhotson"
41
+ end
42
+
43
+ it "should return an entry with correct time" do
44
+ parser = EntryParser.new(@first_line, @second_line)
45
+ entry = parser.parse
46
+ entry.time.round.should == (Time.now - 3600*4).round
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+ end
53
+
@@ -0,0 +1,36 @@
1
+ <html><head><link rel="stylesheet" type="text/css" href="http://ycombinator.com/news.css">
2
+ <link rel="shortcut icon" href="http://ycombinator.com/favicon.ico">
3
+ <script>
4
+ function byId(id) {
5
+ return document.getElementById(id);
6
+ }
7
+
8
+ function vote(node) {
9
+ var v = node.id.split(/_/); // {'up', '123'}
10
+ var item = v[1];
11
+
12
+ // adjust score
13
+ var score = byId('score_' + item);
14
+ var newscore = parseInt(score.innerHTML) + (v[0] == 'up' ? 1 : -1);
15
+ score.innerHTML = newscore + (newscore == 1 ? ' point' : ' points');
16
+
17
+ // hide arrows
18
+ byId('up_' + item).style.visibility = 'hidden';
19
+ byId('down_' + item).style.visibility = 'hidden';
20
+
21
+ // ping server
22
+ var ping = new Image();
23
+ ping.src = node.href;
24
+
25
+ return false; // cancel browser nav
26
+ } </script><title>Hacker News</title></head><body><center><table border=0 cellpadding=0 cellspacing=0 width="85%" bgcolor=#f6f6ef><tr><td bgcolor=#ff6600><table border=0 cellpadding=0 cellspacing=0 width="100%" style="padding:2px"><tr><td style="width:18px;padding-right:4px"><a href="http://ycombinator.com"><img src="http://ycombinator.com/images/y18.gif" width=18 height=18 style="border:1px #ffffff solid;"></img></a></td><td style="line-height:12pt; height:10px;"><span class="pagetop"><b><a href="news">Hacker News</a></b><img src="http://ycombinator.com/images/s.gif" height=1 width=10><a href="newest">new</a> | <a href="newcomments">comments</a> | <a href="ask">ask</a> | <a href="jobs">jobs</a> | <a href="submit">submit</a></span></td><td style="text-align:right;padding-right:4px;"><span class="pagetop"><a href="/x?fnid=aGDlLvxj45">login</a></span></td></tr></table></td></tr><tr style="height:10px"></tr><tr><td><table border=0 cellpadding=0 cellspacing=0><tr><td align=right valign=top class="title">1.</td><td><center><a id=up_1645934 href="vote?for=1645934&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645934></span></center></td><td class="title"><a href="http://hackaday.com/2010/08/29/seaswarm-we-can-clean-up-the-gulf-in-a-month/">Seaswarm: we can clean up the Gulf in a month</a><span class="comhead"> (hackaday.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645934>30 points</span> by <a href="user?id=IgorPartola">IgorPartola</a> 1 hour ago | <a href="item?id=1645934">10 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">2.</td><td><center><a id=up_1645745 href="vote?for=1645745&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645745></span></center></td><td class="title"><a href="http://technologizer.com/2010/08/18/the-tragic-death-of-practically-everything/">The Tragic Death of Practically Everything</a><span class="comhead"> (technologizer.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645745>59 points</span> by <a href="user?id=dhotson">dhotson</a> 4 hours ago | <a href="item?id=1645745">5 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">3.</td><td><center><a id=up_1645820 href="vote?for=1645820&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645820></span></center></td><td class="title"><a href="http://github.com/resistor/BrainFTracing">I wrote an LLVM-powered trace-based JIT for Brainf**k</a><span class="comhead"> (github.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645820>31 points</span> by <a href="user?id=Halienja">Halienja</a> 3 hours ago | <a href="item?id=1645820">21 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">4.</td><td><center><a id=up_1646087 href="vote?for=1646087&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1646087></span></center></td><td class="title"><a href="http://www.inc.com/magazine/20100901/the-truth-about-real-estate.html">Jason Fried: The Truth About Real Estate</a><span class="comhead"> (inc.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1646087>6 points</span> by <a href="user?id=slater">slater</a> 27 minutes ago | <a href="item?id=1646087">discuss</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">5.</td><td><center><a id=up_1644876 href="vote?for=1644876&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644876></span></center></td><td class="title"><a href="http://weblog.rubyonrails.org/2010/8/29/rails-3-0-it-s-done">Rails 3.0: It's ready</a><span class="comhead"> (rubyonrails.org) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644876>313 points</span> by <a href="user?id=steveklabnik">steveklabnik</a> 14 hours ago | <a href="item?id=1644876">51 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">6.</td><td><center><a id=up_1646096 href="vote?for=1646096&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1646096></span></center></td><td class="title"><a href="http://people.csail.mit.edu/pgbovine/python/" rel="nofollow">Python Visual Tutorial</a><span class="comhead"> (mit.edu) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1646096>5 points</span> by <a href="user?id=prog">prog</a> 23 minutes ago | <a href="item?id=1646096">discuss</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">7.</td><td><center><a id=up_1645911 href="vote?for=1645911&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645911></span></center></td><td class="title"><a href="http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/">Flow chart: How to find out which things to throw out</a><span class="comhead"> (mortenjust.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645911>14 points</span> by <a href="user?id=mortenjust">mortenjust</a> 2 hours ago | <a href="item?id=1645911">6 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">8.</td><td><center><a id=up_1645929 href="vote?for=1645929&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645929></span></center></td><td class="title"><a href="http://www.nytimes.com/2010/08/30/business/media/30link.html?_r=1&src=twr">A Hit Song on YouTube, Unnameable on the Radio</a><span class="comhead"> (nytimes.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645929>17 points</span> by <a href="user?id=donohoe">donohoe</a> 2 hours ago | <a href="item?id=1645929">10 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">9.</td><td><center><a id=up_1644915 href="vote?for=1644915&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644915></span></center></td><td class="title"><a href="http://swarmation.com/">Swarmation: like musical chairs for pixels</a><span class="comhead"> (swarmation.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644915>162 points</span> by <a href="user?id=mcantelon">mcantelon</a> 13 hours ago | <a href="item?id=1644915">59 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">10.</td><td><center><a id=up_1645686 href="vote?for=1645686&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645686></span></center></td><td class="title"><a href="http://djangodash.com/judging/results/">Django Dash 2010 Results</a><span class="comhead"> (djangodash.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645686>21 points</span> by <a href="user?id=johnthedebs">johnthedebs</a> 5 hours ago | <a href="item?id=1645686">5 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">11.</td><td><center><a id=up_1645417 href="vote?for=1645417&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645417></span></center></td><td class="title"><a href="http://www.anandtech.com/show/3878/farewell-to-ati-amd-to-retire-the-ati-brand-later-this-year">AMD to Retire the ATI Brand Later this Year</a><span class="comhead"> (anandtech.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645417>50 points</span> by <a href="user?id=spcmnspff">spcmnspff</a> 8 hours ago | <a href="item?id=1645417">9 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">12.</td><td><center><a id=up_1645291 href="vote?for=1645291&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645291></span></center></td><td class="title"><a href="http://www.nycresistor.com/2010/08/29/finally-finished/">Hacker builds working 1/10th scale Cray 1</a><span class="comhead"> (nycresistor.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645291>61 points</span> by <a href="user?id=henning">henning</a> 10 hours ago | <a href="item?id=1645291">13 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">13.</td><td><center><a id=up_1645885 href="vote?for=1645885&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645885></span></center></td><td class="title"><a href="http://online.wsj.com/article_email/SB10001424052748703418004575455911922562120-lMyQjAxMTAwMDIwNzEyNDcyWj.html">Spreading Hayek, Spurning Keynes</a><span class="comhead"> (wsj.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645885>7 points</span> by <a href="user?id=DanielBMarkham">DanielBMarkham</a> 2 hours ago | <a href="item?id=1645885">8 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">14.</td><td><center><a id=up_1645799 href="vote?for=1645799&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645799></span></center></td><td class="title"><a href="http://blog.ianbicking.org/2008/05/06/the-gpl-and-principles/">The GPL and Principles</a><span class="comhead"> (ianbicking.org) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645799>10 points</span> by <a href="user?id=dhotson">dhotson</a> 3 hours ago | <a href="item?id=1645799">discuss</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">15.</td><td><center><a id=up_1645129 href="vote?for=1645129&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645129></span></center></td><td class="title"><a href="http://karaten.posterous.com/ageism-in-silicon-valley-a-foreign-perspectiv">Ageism in silicon valley: a foreign perspective</a><span class="comhead"> (karaten.posterous.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645129>73 points</span> by <a href="user?id=karaten">karaten</a> 11 hours ago | <a href="item?id=1645129">20 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">16.</td><td><center><a id=up_1645376 href="vote?for=1645376&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645376></span></center></td><td class="title"><a href="http://www.ryanallis.com/most-important-business-lessons-learned/">The Most Important Business Lessons I’ve Learned </a><span class="comhead"> (ryanallis.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645376>42 points</span> by <a href="user?id=zaidf">zaidf</a> 9 hours ago | <a href="item?id=1645376">8 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">17.</td><td><center><a id=up_1645258 href="vote?for=1645258&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645258></span></center></td><td class="title"><a href="http://rubydoc.info">RubyDoc.info: Auto-generated RubyGems and GitHub library docs</a><span class="comhead"> (rubydoc.info) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645258>41 points</span> by <a href="user?id=zapnap">zapnap</a> 10 hours ago | <a href="item?id=1645258">3 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">18.</td><td><center><a id=up_1643318 href="vote?for=1643318&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1643318></span></center></td><td class="title"><a href="http://www.thestar.com/news/gta/article/854018--how-panhandlers-use-free-credit-cards?bn=1">What happens when you give homeless people a prepaid credit card. </a><span class="comhead"> (thestar.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1643318>330 points</span> by <a href="user?id=pavs">pavs</a> 1 day ago | <a href="item?id=1643318">219 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">19.</td><td><center><a id=up_1645202 href="vote?for=1645202&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645202></span></center></td><td class="title"><a href="http://academic.research.microsoft.com/">Microsoft Academic Search</a><span class="comhead"> (microsoft.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645202>40 points</span> by <a href="user?id=brisance">brisance</a> 11 hours ago | <a href="item?id=1645202">12 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">20.</td><td><center><a id=up_1645520 href="vote?for=1645520&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645520></span></center></td><td class="title"><a href="http://nodeknockout.com/teams/anansi">MapRejuice - Distributed Client Side Computing (Node Knockout Entry)</a><span class="comhead"> (nodeknockout.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645520>19 points</span> by <a href="user?id=Klonoar">Klonoar</a> 7 hours ago | <a href="item?id=1645520">14 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">21.</td><td><center><a id=up_1643239 href="vote?for=1643239&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1643239></span></center></td><td class="title"><a href="http://www.inspirationandchai.com/Regrets-of-the-Dying.html">Regrets of the Dying</a><span class="comhead"> (inspirationandchai.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1643239>304 points</span> by <a href="user?id=vijaydev">vijaydev</a> 1 day ago | <a href="item?id=1643239">78 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">22.</td><td><center><a id=up_1645115 href="vote?for=1645115&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645115></span></center></td><td class="title"><a href="http://archiver.mailfighter.net/nnsquad/2010/August/29/0002.html">Net Neutrality is now law in Chile</a><span class="comhead"> (mailfighter.net) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645115>42 points</span> by <a href="user?id=nfriedly">nfriedly</a> 12 hours ago | <a href="item?id=1645115">6 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">23.</td><td><center><a id=up_1644481 href="vote?for=1644481&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644481></span></center></td><td class="title"><a href="http://nodelay.no.de/">Realtime monitoring of Wikipedia edits via node.js</a><span class="comhead"> (no.de) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644481>83 points</span> by <a href="user?id=mcantelon">mcantelon</a> 17 hours ago | <a href="item?id=1644481">11 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">24.</td><td><center><a id=up_1644094 href="vote?for=1644094&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644094></span></center></td><td class="title"><a href="http://www.economist.com/node/16881791">Obesity: Drink till you drop</a><span class="comhead"> (economist.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644094>110 points</span> by <a href="user?id=jsyedidia">jsyedidia</a> 20 hours ago | <a href="item?id=1644094">81 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">25.</td><td><center><a id=up_1643936 href="vote?for=1643936&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1643936></span></center></td><td class="title"><a href="http://lessframework.com/">Less Framework 2.0 Released</a><span class="comhead"> (lessframework.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1643936>141 points</span> by <a href="user?id=alexkiwi">alexkiwi</a> 22 hours ago | <a href="item?id=1643936">26 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">26.</td><td><center><a id=up_1644859 href="vote?for=1644859&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644859></span></center></td><td class="title"><a href="http://symkat.com/1/five-text-processing-tools-you-should-know/">Basic Command-Line Data Processing in Linux</a><span class="comhead"> (symkat.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644859>55 points</span> by <a href="user?id=symkat">symkat</a> 14 hours ago | <a href="item?id=1644859">17 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">27.</td><td><center><a id=up_1645200 href="vote?for=1645200&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645200></span></center></td><td class="title"><a href="http://techcrunch.com/2010/08/29/cisco-may-be-making-a-run-for-skype/">Cisco May Be Making A Run For Skype</a><span class="comhead"> (techcrunch.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645200>27 points</span> by <a href="user?id=mjfern">mjfern</a> 11 hours ago | <a href="item?id=1645200">13 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">28.</td><td><center><a id=up_1645004 href="vote?for=1645004&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645004></span></center></td><td class="title"><a href="http://bhorowitz.com/2010/08/29/the-right-kind-of-ambition-2/">The Right Kind of Ambition</a><span class="comhead"> (bhorowitz.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1645004>39 points</span> by <a href="user?id=dwynings">dwynings</a> 12 hours ago | <a href="item?id=1645004">20 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">29.</td><td><center><a id=up_1644772 href="vote?for=1644772&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1644772></span></center></td><td class="title"><a href="http://www.scribd.com/doc/36582231/CrunchPad-Interserve-v-Fusion-Garage-Denial-of-Preliminary-Injunction">Arrington Is Denied Injunction Against Fusion Garage Over CrunchPad</a><span class="comhead"> (scribd.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1644772>40 points</span> by <a href="user?id=tptacek">tptacek</a> 14 hours ago | <a href="item?id=1644772">12 comments</a></td></tr><tr style="height:5px"></tr><tr><td align=right valign=top class="title">30.</td><td><center><a id=up_1643920 href="vote?for=1643920&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1643920></span></center></td><td class="title"><a href="http://www.makikoitoh.com/journal/satoshi-kons-last-words">Satoshi Kon's last words</a><span class="comhead"> (makikoitoh.com) </span></td></tr><tr><td colspan=2></td><td class="subtext"><span id=score_1643920>79 points</span> by <a href="user?id=wallflower">wallflower</a> 22 hours ago | <a href="item?id=1643920">10 comments</a></td></tr><tr style="height:5px"></tr><tr style="height:10px"></tr><tr><td colspan=2></td><td class="title"><a href="/x?fnid=9LO1ba1swy" rel="nofollow">More</a></td></tr></table></td></tr><tr><td><img src="http://ycombinator.com/images/s.gif" height=10 width=0><table width="100%" cellspacing=0 cellpadding=1><tr><td bgcolor=#ff6600></td></tr></table><br>
27
+ <center><span class="yclinks"><a href="lists">Lists</a> | <a href="rss">RSS</a> | <a href="http://google.com/search?q=site%3Anews.ycombinator.com">Search</a> | <a href="http://ycombinator.com/bookmarklet.html">Bookmarklet</a> | <a href="http://ycombinator.com/newsguidelines.html">Guidelines</a> | <a href="http://ycombinator.com/newsfaq.html">FAQ</a> | <a href="http://ycombinator.com/newsnews.html">News News</a> | <a href="item?id=363">Feature Requests</a> | <a href="http://ycombinator.com">Y Combinator</a> | <a href="http://ycombinator.com/apply.html">Apply</a> | <a href="http://ycombinator.com/lib.html">Library</a></span><br><br>
28
+ <script type="text/javascript"
29
+ src="http://www.co2stats.com/propres.php?s=1138"></script><img src="http://ycombinator.com/images/s.gif" height=1 width=15><a href="http://www.webmynd.com/html/hackernews.html"><img
30
+ src="http://ycombinator.com/images/hnsearch.png" border=0
31
+ style="padding-bottom:12px"></a><img src="http://ycombinator.com/images/s.gif" height=1 width=25><a href="http://mixpanel.com/?from=yc"><img border=0
32
+ src="http://mixpanel.com/site_media/images/mixpanel_partner_logo_borderless.gif"
33
+ alt="Analytics by Mixpanel"
34
+ style="padding-bottom:8px"
35
+ /></a><br>
36
+ </center></td></tr></table></center></body></html>
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe LinkInfoParser do
6
+
7
+ before :each do
8
+ @line = ParserHelper.link_line
9
+ @correct_title = "Flow chart: How to find out which things to throw out"
10
+ @correct_link = "http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/"
11
+ @correct_site = "mortenjust.com"
12
+ end
13
+
14
+ describe :parse do
15
+
16
+ it "should call LinkInfo.new with correct title" do
17
+ parser = LinkInfoParser.new(@line)
18
+ LinkInfo.should_receive(:new).with(@correct_title, anything, anything)
19
+ parser.parse
20
+ end
21
+
22
+ it "should call LinkInfo.new with correct link" do
23
+ parser = LinkInfoParser.new(@line)
24
+ LinkInfo.should_receive(:new).with(anything, @correct_link, anything)
25
+ parser.parse
26
+ end
27
+
28
+ it "should call LinkInfo.new with correct site if site is present" do
29
+ parser = LinkInfoParser.new(@line)
30
+ LinkInfo.should_receive(:new).with(anything, anything, @correct_site)
31
+ parser.parse
32
+ end
33
+
34
+ it "should call LinkInfo.new with nil if site is not present" do
35
+ parser = LinkInfoParser.new(ParserHelper.link_line_no_site)
36
+ LinkInfo.should_receive(:new).with(anything, anything, nil)
37
+ parser.parse
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+
@@ -0,0 +1,77 @@
1
+
2
+ class ParserHelper
3
+
4
+ #upvote: vote?for=1645686&dir=up&whence=%6e%65%77%73
5
+ #downvote: nil
6
+ def self.voting_node_no_downvote
7
+ return Nokogiri::HTML.fragment('<a id=up_1645686 href="vote?for=1645686&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645686></span>'
8
+ ).children
9
+ end
10
+
11
+ #upvote: nil
12
+ #downvote: vote?for=1645686&dir=up&whence=%6e%65%77%73
13
+ def self.voting_node_no_upvote
14
+ return Nokogiri::HTML.fragment('<span id=up_1645686></span><a id=down_1645686 href="vote?for=1645686&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a>'
15
+ ).children
16
+ end
17
+
18
+ #upvote: vote?for=1645686&dir=up&whence=%6e%65%77%73
19
+ #downvote: vote?for=1645686&dir=up&whence=%6e%65%77%73
20
+ def self.voting_node
21
+ return Nokogiri::HTML.fragment('<a id=up_1645686 href="vote?for=1645686&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><a id=down_1645686 href="vote?for=1645686&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a>'
22
+ ).children
23
+ end
24
+
25
+ #username : johnthedebs
26
+ #user page : user?id=johnthedebs
27
+ #score : 17
28
+ #comments : 2
29
+ #comment_page : item?id=1645686
30
+ def self.second_line
31
+ return Nokogiri::HTML.fragment('<td class="subtext"><span id=score_1645686>17 points</span> by <a href="user?id=johnthedebs">johnthedebs</a> 4 hours ago | <a href="item?id=1645686">2 comments</a></td>')
32
+ end
33
+
34
+ #username : johnthedebs
35
+ #user page : user?id=johnthedebs
36
+ #score : 17
37
+ #comment_page : item?id=1645686
38
+ def self.second_line_no_comments_yet
39
+ return Nokogiri::HTML.fragment('<td class="subtext"><span id=score_1645686>17 points</span> by <a href="user?id=johnthedebs">johnthedebs</a> 4 hours ago | <a href="item?id=1645686">discuss</a></td>')
40
+ end
41
+
42
+ #username : johnthedebs
43
+ #user page : user?id=johnthedebs
44
+ #score : 17
45
+ def self.second_line_comments_tag_missing
46
+ return Nokogiri::HTML.fragment('<td class="subtext"><span id=score_1645686>17 points</span> by <a href="user?id=johnthedebs">johnthedebs</a> 4 hours ago</td>')
47
+ end
48
+
49
+ #title : Flow chart: How to find out which things to throw out
50
+ #link : http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/
51
+ #site : (mortenjust.com)
52
+ def self.link_line
53
+ return Nokogiri::HTML('<td class="title"><a href="http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/" rel="nofollow">Flow chart: How to find out which things to throw out</a><span class="comhead"> (mortenjust.com) </span></td>')
54
+ end
55
+
56
+ #title : Flow chart: How to find out which things to throw out
57
+ #link : http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/
58
+ def self.link_line_no_site
59
+ return Nokogiri::HTML('<td class="title"><a href="http://mortenjust.com/2010/08/30/finding-out-which-things-to-throw-out/" rel="nofollow">Flow chart: How to find out which things to throw out</a></td>')
60
+ end
61
+
62
+ def self.first_line_full
63
+ return Nokogiri::HTML('<tr><td align=right valign=top class="title">2.</td><td><center><a id=up_1645745 href="vote?for=1645745&dir=up&whence=%6e%65%77%73"><img src="http://ycombinator.com/images/grayarrow.gif" border=0 vspace=3 hspace=2></a><span id=down_1645745></span></center></td><td class="title"><a href="http://technologizer.com/2010/08/18/the-tragic-death-of-practically-everything/">The Tragic Death of Practically Everything</a><span class="comhead"> (technologizer.com) </span></td></tr>')
64
+ end
65
+
66
+ def self.second_line_full
67
+ return Nokogiri::HTML('<tr><td colspan=2></td><td class="subtext"><span id=score_1645745>59 points</span> by <a href="user?id=dhotson">dhotson</a> 4 hours ago | <a href="item?id=1645745">5 comments</a></td></tr>')
68
+ end
69
+
70
+ def self.full_page
71
+ File.open(File.join(File.dirname(__FILE__), 'hn_test_page.html'), 'r') do |file|
72
+ return Nokogiri::HTML(file)
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyHackernews
4
+ describe TimeInfoParser do
5
+
6
+ before :each do
7
+ @parser = TimeInfoParser.new(stub(:text => " 4 hours ago | "))
8
+ end
9
+
10
+ describe :parse do
11
+
12
+ it "should pass the right value as the first parameter" do
13
+ TimeInfo.should_receive(:new).with(4, anything)
14
+ @parser.parse
15
+ end
16
+
17
+ it "should pass the right UoM descriptor as the second parameter" do
18
+ TimeInfo.should_receive(:new).with(anything, "hours")
19
+ @parser.parse
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe UserInfoParser do
6
+
7
+ before :each do
8
+ @parser = UserInfoParser.new(ParserHelper.second_line)
9
+ end
10
+
11
+ describe :parse do
12
+
13
+ it "should pass the right user name" do
14
+ UserInfo.should_receive(:new).with("johnthedebs", anything)
15
+ @parser.parse
16
+ end
17
+
18
+ it "should pass the right user page" do
19
+ UserInfo.should_receive(:new).with(anything, "user?id=johnthedebs")
20
+ @parser.parse
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require File.expand_path(File.dirname(__FILE__) + '/parser_helper')
3
+
4
+ module RubyHackernews
5
+ describe VotingInfoParser do
6
+
7
+ before :each do
8
+ @voting_element = ParserHelper.voting_node
9
+ @score_element = ParserHelper.second_line
10
+ @correct_vote_link = "vote?for=1645686&dir=up&whence=%6e%65%77%73"
11
+ end
12
+
13
+ describe :parse do
14
+
15
+ it "should assign link.href to upvote if element not nil" do
16
+ parser = VotingInfoParser.new(@voting_element, @score_element)
17
+ VotingInfo.should_receive(:new).with(anything, @correct_vote_link, anything)
18
+ parser.parse
19
+ end
20
+
21
+ it "should assing nil to upvote if element nil" do
22
+ parser = VotingInfoParser.new(ParserHelper.voting_node_no_upvote, @score_element)
23
+ VotingInfo.should_receive(:new).with(anything, nil, anything)
24
+ parser.parse
25
+ end
26
+
27
+ it "should assing link.href to downvote if element not nil" do
28
+ parser = VotingInfoParser.new(@voting_element, @score_element)
29
+ VotingInfo.should_receive(:new).with(anything, anything, @correct_vote_link)
30
+ parser.parse
31
+ end
32
+
33
+ it "should assing nil to downvote if element nil" do
34
+ parser = VotingInfoParser.new(ParserHelper.voting_node_no_downvote, @score_element)
35
+ VotingInfo.should_receive(:new).with(anything, anything, nil)
36
+ parser.parse
37
+ end
38
+
39
+ it "should assing correct score to score" do
40
+ parser = VotingInfoParser.new(@voting_element, @score_element)
41
+ VotingInfo.should_receive(:new).with(17, anything, anything)
42
+ parser.parse
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyHackernews
4
+ describe MechanizeContext do
5
+
6
+ before :each do
7
+ MechanizeContext.send(:class_variable_set, :@@contexts, nil)
8
+ MechanizeContext.send(:class_variable_set, :@@default, nil)
9
+ end
10
+
11
+ describe "create" do
12
+
13
+ it "should instantiate class variable on use" do
14
+ MechanizeContext.create()
15
+ MechanizeContext.send(:class_variable_get, :@@contexts).should_not be_nil
16
+ end
17
+
18
+ it "should assign new agent to hash[key]" do
19
+ MechanizeContext.create(:test_key)
20
+ MechanizeContext.send(:class_variable_get, :@@contexts)[:test_key].should be_kind_of Mechanize
21
+ end
22
+
23
+ it "should assign new agent to default if no key passed" do
24
+ MechanizeContext.create
25
+ MechanizeContext.send(:class_variable_get, :@@contexts)[:default].should be_kind_of Mechanize
26
+ end
27
+
28
+ end
29
+
30
+ describe "agent=" do
31
+
32
+ it "should set @@default as passed key" do
33
+ MechanizeContext.agent = :test_agent
34
+ MechanizeContext.send(:class_variable_get, :@@default).should == :test_agent
35
+ end
36
+
37
+ end
38
+
39
+ describe "agent" do
40
+
41
+ it "should return the @@default agent" do
42
+ MechanizeContext.send(:class_variable_set, :@@default, :test_value)
43
+ MechanizeContext.send(:class_variable_set, :@@contexts, {:test_value => :target})
44
+ klass = Class.new
45
+ klass.instance_eval do
46
+ include MechanizeContext
47
+ end
48
+ klass.new.agent.should == :target
49
+ end
50
+
51
+ end
52
+
53
+ describe "[]" do
54
+
55
+ it "should return the [key] agent" do
56
+ MechanizeContext.send(:class_variable_set, :@@contexts, {:test_value => :target})
57
+ klass = Class.new
58
+ klass.instance_eval do
59
+ include MechanizeContext
60
+ end
61
+ klass.new[:test_value].should == :target
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,7 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'ruby-hackernews.rb'
7
+
metadata CHANGED
@@ -1,20 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-hackernews
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
5
- prerelease:
4
+ version: 1.3.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Andrea Dallera
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-12-15 00:00:00.000000000 Z
11
+ date: 2014-08-04 00:00:00.000000000 Z
13
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
14
41
  - !ruby/object:Gem::Dependency
15
42
  name: require_all
16
43
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
44
  requirements:
19
45
  - - ! '>='
20
46
  - !ruby/object:Gem::Version
@@ -22,7 +48,6 @@ dependencies:
22
48
  type: :runtime
23
49
  prerelease: false
24
50
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
51
  requirements:
27
52
  - - ! '>='
28
53
  - !ruby/object:Gem::Version
@@ -30,7 +55,6 @@ dependencies:
30
55
  - !ruby/object:Gem::Dependency
31
56
  name: mechanize
32
57
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
58
  requirements:
35
59
  - - ! '>='
36
60
  - !ruby/object:Gem::Version
@@ -38,69 +62,98 @@ dependencies:
38
62
  type: :runtime
39
63
  prerelease: false
40
64
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
65
  requirements:
43
66
  - - ! '>='
44
67
  - !ruby/object:Gem::Version
45
68
  version: 1.0.0
46
69
  description: An interface to Hacker News
47
- email: andrea@andreadallera.com
70
+ email:
71
+ - andrea@andreadallera.com
48
72
  executables: []
49
73
  extensions: []
50
74
  extra_rdoc_files: []
51
75
  files:
76
+ - .gitignore
77
+ - Gemfile
52
78
  - README.rdoc
53
79
  - Rakefile
54
80
  - lib/ruby-hackernews.rb
81
+ - lib/ruby-hackernews/domain/comment/comment.rb
55
82
  - lib/ruby-hackernews/domain/entry/comments_info.rb
56
- - lib/ruby-hackernews/domain/entry/time_info.rb
57
- - lib/ruby-hackernews/domain/entry/voting_info.rb
58
- - lib/ruby-hackernews/domain/entry/link_info.rb
59
83
  - lib/ruby-hackernews/domain/entry/entry.rb
84
+ - lib/ruby-hackernews/domain/entry/link_info.rb
85
+ - lib/ruby-hackernews/domain/entry/time_info.rb
60
86
  - lib/ruby-hackernews/domain/entry/user_info.rb
87
+ - lib/ruby-hackernews/domain/entry/voting_info.rb
61
88
  - lib/ruby-hackernews/domain/user.rb
62
- - lib/ruby-hackernews/domain/comment/comment.rb
89
+ - lib/ruby-hackernews/services/comment_service.rb
63
90
  - lib/ruby-hackernews/services/configuration_service.rb
64
- - lib/ruby-hackernews/services/page_fetcher.rb
91
+ - lib/ruby-hackernews/services/entry_service.rb
65
92
  - lib/ruby-hackernews/services/login_service.rb
66
93
  - lib/ruby-hackernews/services/mechanize_context.rb
67
94
  - lib/ruby-hackernews/services/not_authenticated_error.rb
68
- - lib/ruby-hackernews/services/signup_service.rb
69
- - lib/ruby-hackernews/services/entry_service.rb
70
- - lib/ruby-hackernews/services/user_info_service.rb
71
- - lib/ruby-hackernews/services/comment_service.rb
72
- - lib/ruby-hackernews/services/voting_service.rb
73
- - lib/ruby-hackernews/services/text_service.rb
74
- - lib/ruby-hackernews/services/parsers/text_parser.rb
75
- - lib/ruby-hackernews/services/parsers/user_info_parser.rb
95
+ - lib/ruby-hackernews/services/page_fetcher.rb
96
+ - lib/ruby-hackernews/services/parsers/comments_info_parser.rb
97
+ - lib/ruby-hackernews/services/parsers/entry_page_parser.rb
76
98
  - lib/ruby-hackernews/services/parsers/entry_parser.rb
77
99
  - lib/ruby-hackernews/services/parsers/link_info_parser.rb
100
+ - lib/ruby-hackernews/services/parsers/text_parser.rb
78
101
  - lib/ruby-hackernews/services/parsers/time_info_parser.rb
102
+ - lib/ruby-hackernews/services/parsers/user_info_parser.rb
79
103
  - lib/ruby-hackernews/services/parsers/voting_info_parser.rb
80
- - lib/ruby-hackernews/services/parsers/comments_info_parser.rb
81
- - lib/ruby-hackernews/services/parsers/entry_page_parser.rb
82
- homepage: http://github.com/bolthar/ruby-hackernews
104
+ - lib/ruby-hackernews/services/signup_service.rb
105
+ - lib/ruby-hackernews/services/text_service.rb
106
+ - lib/ruby-hackernews/services/user_info_service.rb
107
+ - lib/ruby-hackernews/services/voting_service.rb
108
+ - lib/ruby-hackernews/version.rb
109
+ - ruby-hackernews.gemspec
110
+ - spec/HNAPI/domain/time_info_spec.rb
111
+ - spec/HNAPI/services/entries/entry_service_spec.rb
112
+ - spec/HNAPI/services/entries/parsers/comments_info_parser_spec.rb
113
+ - spec/HNAPI/services/entries/parsers/entry_page_parser_spec.rb
114
+ - spec/HNAPI/services/entries/parsers/entry_parser_spec.rb
115
+ - spec/HNAPI/services/entries/parsers/hn_test_page.html
116
+ - spec/HNAPI/services/entries/parsers/link_info_parser_spec.rb
117
+ - spec/HNAPI/services/entries/parsers/parser_helper.rb
118
+ - spec/HNAPI/services/entries/parsers/time_info_parser_spec.rb
119
+ - spec/HNAPI/services/entries/parsers/user_info_parser_spec.rb
120
+ - spec/HNAPI/services/entries/parsers/voting_info_parser_spec.rb
121
+ - spec/HNAPI/services/mechanize_context_spec.rb
122
+ - spec/spec_helper.rb
123
+ homepage: http://github.com/blissofbeing/ruby-hackernews
83
124
  licenses: []
125
+ metadata: {}
84
126
  post_install_message:
85
127
  rdoc_options: []
86
128
  require_paths:
87
129
  - lib
88
130
  required_ruby_version: !ruby/object:Gem::Requirement
89
- none: false
90
131
  requirements:
91
132
  - - ! '>='
92
133
  - !ruby/object:Gem::Version
93
134
  version: '0'
94
135
  required_rubygems_version: !ruby/object:Gem::Requirement
95
- none: false
96
136
  requirements:
97
137
  - - ! '>='
98
138
  - !ruby/object:Gem::Version
99
139
  version: '0'
100
140
  requirements: []
101
141
  rubyforge_project:
102
- rubygems_version: 1.8.24
142
+ rubygems_version: 2.0.3
103
143
  signing_key:
104
- specification_version: 3
144
+ specification_version: 4
105
145
  summary: An interface to Hacker News
106
- test_files: []
146
+ test_files:
147
+ - spec/HNAPI/domain/time_info_spec.rb
148
+ - spec/HNAPI/services/entries/entry_service_spec.rb
149
+ - spec/HNAPI/services/entries/parsers/comments_info_parser_spec.rb
150
+ - spec/HNAPI/services/entries/parsers/entry_page_parser_spec.rb
151
+ - spec/HNAPI/services/entries/parsers/entry_parser_spec.rb
152
+ - spec/HNAPI/services/entries/parsers/hn_test_page.html
153
+ - spec/HNAPI/services/entries/parsers/link_info_parser_spec.rb
154
+ - spec/HNAPI/services/entries/parsers/parser_helper.rb
155
+ - spec/HNAPI/services/entries/parsers/time_info_parser_spec.rb
156
+ - spec/HNAPI/services/entries/parsers/user_info_parser_spec.rb
157
+ - spec/HNAPI/services/entries/parsers/voting_info_parser_spec.rb
158
+ - spec/HNAPI/services/mechanize_context_spec.rb
159
+ - spec/spec_helper.rb