ruby-hackernews 1.3.3 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
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