ruby-hackernews 1.0.0
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.
- data/README.rdoc +114 -0
- data/Rakefile +34 -0
- data/lib/HNAPI/domain/comment/comment.rb +55 -0
- data/lib/HNAPI/domain/entry/comments_info.rb +12 -0
- data/lib/HNAPI/domain/entry/entry.rb +57 -0
- data/lib/HNAPI/domain/entry/link_info.rb +14 -0
- data/lib/HNAPI/domain/entry/time_info.rb +20 -0
- data/lib/HNAPI/domain/entry/user_info.rb +12 -0
- data/lib/HNAPI/domain/entry/voting_info.rb +14 -0
- data/lib/HNAPI/domain/user.rb +18 -0
- data/lib/HNAPI/services/comment_service.rb +62 -0
- data/lib/HNAPI/services/configuration_service.rb +28 -0
- data/lib/HNAPI/services/entry_service.rb +52 -0
- data/lib/HNAPI/services/login_service.rb +26 -0
- data/lib/HNAPI/services/mechanize_context.rb +28 -0
- data/lib/HNAPI/services/not_authenticated_error.rb +8 -0
- data/lib/HNAPI/services/parsers/comments_info_parser.rb +16 -0
- data/lib/HNAPI/services/parsers/entry_page_parser.rb +24 -0
- data/lib/HNAPI/services/parsers/entry_parser.rb +19 -0
- data/lib/HNAPI/services/parsers/link_info_parser.rb +17 -0
- data/lib/HNAPI/services/parsers/time_info_parser.rb +15 -0
- data/lib/HNAPI/services/parsers/user_info_parser.rb +13 -0
- data/lib/HNAPI/services/parsers/voting_info_parser.rb +15 -0
- data/lib/HNAPI/services/voting_service.rb +11 -0
- data/lib/ruby-hackernews.rb +8 -0
- metadata +122 -0
data/README.rdoc
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
|
2
|
+
= ruby-hackernews
|
3
|
+
|
4
|
+
An API over Hacker News
|
5
|
+
|
6
|
+
== Requirements
|
7
|
+
|
8
|
+
mechanize (>= 1.0.0)
|
9
|
+
require_all (>= 1.1.0)
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
gem install ruby-hackernews
|
14
|
+
|
15
|
+
then, in your script:
|
16
|
+
|
17
|
+
require 'ruby-hackernews'
|
18
|
+
|
19
|
+
before using it.
|
20
|
+
|
21
|
+
== Entries
|
22
|
+
|
23
|
+
You can get entries on the main page with:
|
24
|
+
|
25
|
+
Entry.all # returns an array of entries
|
26
|
+
|
27
|
+
You can provide a number of pages:
|
28
|
+
|
29
|
+
Entry.all(3) # will return the entries on the first 3 pages
|
30
|
+
|
31
|
+
There are methods for getting specific entry types:
|
32
|
+
|
33
|
+
Entry.questions # gets the first page of questions (ask NH)
|
34
|
+
Entry.newest # get the first page of new links (new)
|
35
|
+
Entry.jobs # get the forst page of job offest (jobs)
|
36
|
+
|
37
|
+
Each Entry instance has the following data:
|
38
|
+
|
39
|
+
entry = Entry.all.first #get the top entry on the mainpage
|
40
|
+
|
41
|
+
entry.number # entry's position on HN
|
42
|
+
|
43
|
+
entry.link.title # the link's name on HN
|
44
|
+
entry.link.href # the actual link
|
45
|
+
entry.link.site # the referring's site, if any
|
46
|
+
|
47
|
+
entry.voting.score # the entry's score on HN
|
48
|
+
|
49
|
+
entry.user.name # submitter's user name
|
50
|
+
|
51
|
+
entry.time # elapsed time from submission
|
52
|
+
|
53
|
+
After you've logged in (see below) you can do the following
|
54
|
+
|
55
|
+
entry.upvote #votes the entry
|
56
|
+
entry.write_comment("mycomment") #adds a comment to the entry
|
57
|
+
Entry.submit("mytitle", "myurl") #submit a new link
|
58
|
+
Entry.submit("myquestion") #submit a new question (ask HN)
|
59
|
+
|
60
|
+
== Comments
|
61
|
+
|
62
|
+
You get an entry's comments with:
|
63
|
+
|
64
|
+
entry.comments
|
65
|
+
|
66
|
+
You can also get the newest comments on HN with:
|
67
|
+
|
68
|
+
Comments.newest
|
69
|
+
Comments.newest(3) # get the first 3 pages of new comments
|
70
|
+
|
71
|
+
Each Comment instance has the following data:
|
72
|
+
|
73
|
+
comment = Entry.all.first.comments.first # gets the first comment of the first entry on HN's main page
|
74
|
+
|
75
|
+
comment.text # comment's body
|
76
|
+
comment.user.name # poster's user name on HN
|
77
|
+
comment.voting.score # comment's score
|
78
|
+
|
79
|
+
Comments are enumerable and threaded, so you can do like:
|
80
|
+
|
81
|
+
comment[2][0] # gets the third reply to this comment, then the first reply to the reply
|
82
|
+
comment.first # get the first reply to this comment
|
83
|
+
comment.select do |c| # gets all the comment replies which text contains "test"
|
84
|
+
text ~= /test/
|
85
|
+
end
|
86
|
+
comment.parent # gets the comment's parent (nil if no parent)
|
87
|
+
|
88
|
+
Once you're logged in (see below), you can do the following:
|
89
|
+
|
90
|
+
comment.upvote
|
91
|
+
comment.downvote
|
92
|
+
comment.reply("mycomment")
|
93
|
+
|
94
|
+
== Logging in
|
95
|
+
|
96
|
+
You define a user with:
|
97
|
+
|
98
|
+
user = User.new("username")
|
99
|
+
|
100
|
+
Then, you log in with:
|
101
|
+
|
102
|
+
user.login("password")
|
103
|
+
|
104
|
+
You can log out with:
|
105
|
+
|
106
|
+
user.logout
|
107
|
+
|
108
|
+
You have to log out before logging in with a different user.
|
109
|
+
|
110
|
+
== TO DO
|
111
|
+
|
112
|
+
Get user info (submission, comments...)
|
113
|
+
Create account
|
114
|
+
Change user info/settings
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
spec = Gem::Specification.new do |s|
|
11
|
+
s.name = 'ruby-hackernews'
|
12
|
+
s.version = '1.0.0'
|
13
|
+
s.add_dependency('require_all', '>= 1.1.0')
|
14
|
+
s.add_dependency('mechanize', '>= 1.0.0')
|
15
|
+
s.has_rdoc = false
|
16
|
+
s.homepage = "http://github.com/bolthar/ruby-hackernews"
|
17
|
+
s.summary = 'An interface to Hacker News'
|
18
|
+
s.description = s.summary
|
19
|
+
s.author = 'Andrea Dallera'
|
20
|
+
s.email = 'andrea@andreadallera.com'
|
21
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib}/**/*")
|
22
|
+
s.require_path = "lib"
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(spec) do |p|
|
26
|
+
p.gem_spec = spec
|
27
|
+
p.need_tar = true
|
28
|
+
p.need_zip = true
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new do |t|
|
32
|
+
t.warning = true
|
33
|
+
t.spec_files = FileList['spec/**/*.rb']
|
34
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
class Comment
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :text
|
6
|
+
attr_reader :voting
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
attr_accessor :parent
|
10
|
+
attr_reader :children
|
11
|
+
|
12
|
+
def initialize(text, voting, user_info, reply_link)
|
13
|
+
@text = text
|
14
|
+
@voting = voting
|
15
|
+
@user = user_info
|
16
|
+
@reply_link = reply_link
|
17
|
+
@children = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(comment)
|
21
|
+
comment.parent = self
|
22
|
+
@children << comment
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@children.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(other_comment)
|
30
|
+
return other_comment.voting.score <=> @voting.score
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(method, *args, &block)
|
34
|
+
@children.send(method, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.newest(pages = 1)
|
38
|
+
return CommentService.new.get_new_comments(pages)
|
39
|
+
end
|
40
|
+
|
41
|
+
def reply(text)
|
42
|
+
return false unless @reply_link
|
43
|
+
CommentService.new.write_comment(@reply_link, text)
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
def upvote
|
48
|
+
VotingService.new.vote(@voting.upvote)
|
49
|
+
end
|
50
|
+
|
51
|
+
def downvote
|
52
|
+
VotingService.new.vote(@voting.downvote)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
class Entry
|
3
|
+
|
4
|
+
attr_reader :number
|
5
|
+
attr_reader :link
|
6
|
+
attr_reader :voting
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
def initialize(number, link, voting, user, comments, time)
|
10
|
+
@number = number
|
11
|
+
@link = link
|
12
|
+
@voting = voting
|
13
|
+
@user = user
|
14
|
+
@time = time
|
15
|
+
@comments_info = comments
|
16
|
+
end
|
17
|
+
|
18
|
+
def comments
|
19
|
+
unless @comments
|
20
|
+
@comments = CommentService.new.get_comments(@comments_info.page)
|
21
|
+
end
|
22
|
+
return @comments
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.all(pages = 1)
|
26
|
+
return EntryService.new.get_entries(pages)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.newest(pages = 1)
|
30
|
+
return EntryService.new.get_new_entries(pages)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.questions(pages = 1)
|
34
|
+
return EntryService.new.get_questions(pages)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.jobs(pages = 1)
|
38
|
+
return EntryService.new.get_jobs(pages)
|
39
|
+
end
|
40
|
+
|
41
|
+
def time
|
42
|
+
return @time.time
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_comment(text)
|
46
|
+
return CommentService.new.write_comment(@comments_info.page, text)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.submit(*args)
|
50
|
+
return EntryService.new.submit(*args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def upvote
|
54
|
+
return VotingService.new.vote(@voting.upvote)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
class TimeInfo
|
3
|
+
|
4
|
+
SECOND = 1
|
5
|
+
MINUTE = 60 * SECOND
|
6
|
+
HOUR = 60 * MINUTE
|
7
|
+
DAY = 24 * HOUR
|
8
|
+
|
9
|
+
def time
|
10
|
+
return Time.at(@unit_of_measure * @value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(value, unit_of_measure)
|
14
|
+
descriptor = unit_of_measure[unit_of_measure.length - 1].chr == "s" ? unit_of_measure[0..unit_of_measure.length - 2] : unit_of_measure
|
15
|
+
@value = value
|
16
|
+
@unit_of_measure = self.class.const_get(descriptor.upcase)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
class CommentService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def get_comments(page_url)
|
6
|
+
comments = []
|
7
|
+
last = comments
|
8
|
+
current_level = -1
|
9
|
+
page = agent.get(page_url)
|
10
|
+
page.search("//table")[3].search("table/tr").select do |tr|
|
11
|
+
tr.search("span.comment").inner_html != "[deleted]"
|
12
|
+
end.each do |tr|
|
13
|
+
comment = parse_comment(tr)
|
14
|
+
level = tr.search("img[@src='http://ycombinator.com/images/s.gif']").first['width'].to_i / 40
|
15
|
+
difference = current_level - level
|
16
|
+
target = last
|
17
|
+
(difference + 1).times do
|
18
|
+
target = target.parent || comments
|
19
|
+
end
|
20
|
+
target << comment
|
21
|
+
last = comment
|
22
|
+
current_level = level
|
23
|
+
end
|
24
|
+
return comments
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_new_comments(pages = 1)
|
28
|
+
parser = EntryPageParser.new(agent.get(ConfigurationService.comments_url))
|
29
|
+
comments = []
|
30
|
+
pages.times do
|
31
|
+
lines = parser.get_lines
|
32
|
+
lines.each do |line|
|
33
|
+
comments << parse_comment(line)
|
34
|
+
end
|
35
|
+
next_url = parser.get_next_url || break
|
36
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
37
|
+
end
|
38
|
+
return comments
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_comment(element)
|
42
|
+
text = ""
|
43
|
+
element.search("span.comment").first.children.each do |ch|
|
44
|
+
text = ch.inner_html.gsub(/<.{1,2}>/,"")
|
45
|
+
end
|
46
|
+
header = element.search("span.comhead").first
|
47
|
+
voting = VotingInfoParser.new(element.search("td/center/a"), header).parse
|
48
|
+
user_info = UserInfoParser.new(header).parse
|
49
|
+
reply_link = element.search("td[@class='default']/p//u//a").first
|
50
|
+
reply_url = reply_link['href'] if reply_link
|
51
|
+
return Comment.new(text, voting, user_info, reply_url)
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_comment(page_url, comment)
|
55
|
+
require_authentication
|
56
|
+
form = agent.get(page_url).forms.first
|
57
|
+
form.text = comment
|
58
|
+
form.submit
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class ConfigurationService
|
3
|
+
|
4
|
+
def self.base_url
|
5
|
+
return "http://news.ycombinator.com/"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.new_url
|
9
|
+
return File.join(self.base_url, "newest")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.ask_url
|
13
|
+
return File.join(self.base_url, "ask")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.jobs_url
|
17
|
+
return File.join(self.base_url, "jobs")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.comments_url
|
21
|
+
return File.join(self.base_url, "newcomments")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.submit_url
|
25
|
+
return File.join(self.base_url, "submit")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
class EntryService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def get_entries(pages = 1, url = ConfigurationService.base_url)
|
6
|
+
parser = EntryPageParser.new(agent.get(url))
|
7
|
+
entry_infos = []
|
8
|
+
pages.times do
|
9
|
+
lines = parser.get_lines
|
10
|
+
(lines.length / 2).times do
|
11
|
+
entry_infos << EntryParser.new(lines.shift, lines.shift).parse
|
12
|
+
end
|
13
|
+
next_url = parser.get_next_url || break
|
14
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
15
|
+
end
|
16
|
+
return entry_infos
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_new_entries(pages = 1)
|
20
|
+
return get_entries(pages, ConfigurationService.new_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_questions(pages = 1)
|
24
|
+
return get_entries(pages, ConfigurationService.ask_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_jobs(pages = 1)
|
28
|
+
return get_entries(pages, ConfigurationService.jobs_url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def submit(*args)
|
32
|
+
require_authentication
|
33
|
+
form = agent.get(ConfigurationService.submit_url).forms.first
|
34
|
+
submit_link(form, args[0], args[1]) if args.length == 2
|
35
|
+
submit_question(form, args[0]) if args.length == 1
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def submit_link(form, title, url)
|
41
|
+
form.t = title
|
42
|
+
form.u = url
|
43
|
+
form.submit
|
44
|
+
end
|
45
|
+
|
46
|
+
def submit_question(form, text)
|
47
|
+
form.x = text
|
48
|
+
form.submit
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class LoginService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def login(username, password)
|
6
|
+
raise "You are logged in already - logout first." if authenticated?
|
7
|
+
page = agent.get(ConfigurationService.base_url)
|
8
|
+
login_url = page.search(".pagetop/a").last['href'].sub("/","")
|
9
|
+
login_page = agent.get(ConfigurationService.base_url + login_url)
|
10
|
+
form = login_page.forms.first
|
11
|
+
form.u = username
|
12
|
+
form.p = password
|
13
|
+
page = form.submit
|
14
|
+
return page.title != nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def logout
|
18
|
+
require_authentication
|
19
|
+
page = agent.get(ConfigurationService.base_url)
|
20
|
+
login_url = page.search(".pagetop/a").last['href'].sub("/","")
|
21
|
+
logout_page = agent.get(ConfigurationService.base_url + login_url)
|
22
|
+
agent.cookie_jar.jar.clear
|
23
|
+
return logout_page.search(".pagetop/a").last.inner_html == "login"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module MechanizeContext
|
3
|
+
|
4
|
+
@@contexts = {}
|
5
|
+
|
6
|
+
def self.agent=(key)
|
7
|
+
@@default = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def agent
|
11
|
+
@@default ||= :default
|
12
|
+
@@contexts[@@default] = Mechanize.new unless @@contexts[@@default]
|
13
|
+
return @@contexts[@@default]
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
return @@contexts[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def require_authentication
|
21
|
+
raise NotAuthenticatedError unless authenticated?
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticated?(key = :default)
|
25
|
+
return @@contexts[key].cookie_jar.jar.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CommentsInfoParser
|
2
|
+
|
3
|
+
def initialize(comments_element)
|
4
|
+
@element = comments_element.search("a")[1]
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse
|
8
|
+
comments_info = nil
|
9
|
+
if @element
|
10
|
+
comments = @element.inner_html.split[0].to_i
|
11
|
+
comments_page = @element['href']
|
12
|
+
comments_info = CommentsInfo.new(comments, comments_page)
|
13
|
+
end
|
14
|
+
return comments_info
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
class EntryPageParser
|
3
|
+
|
4
|
+
def initialize(page)
|
5
|
+
@page = page
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_lines
|
9
|
+
lines = @page.search("//table")[2].search("tr").select do |tr|
|
10
|
+
tr['style'] !~ /height/ &&
|
11
|
+
tr.children.first.attributes.count != 0
|
12
|
+
end
|
13
|
+
more_link = lines.last.search("a").first
|
14
|
+
lines.pop if more_link && more_link.inner_html == "More"
|
15
|
+
return lines
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_next_url
|
19
|
+
more_link = @page.search("//table")[2].search("tr/td/a").select { |node| node.inner_html == "More"}.first
|
20
|
+
return more_link['href'] if more_link
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
class EntryParser
|
3
|
+
|
4
|
+
def initialize(first_line, second_line)
|
5
|
+
@first_line = first_line
|
6
|
+
@second_line = second_line
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
number = @first_line.search("[@class='title']")[0].inner_html.sub(".","").to_i
|
11
|
+
link = LinkInfoParser.new(@first_line.search("[@class='title']")[1]).parse
|
12
|
+
voting = VotingInfoParser.new(@first_line.search("td/center/a"), @second_line.search("[@class='subtext']")[0]).parse
|
13
|
+
user = UserInfoParser.new(@second_line.search("[@class='subtext']")[0]).parse
|
14
|
+
comments = CommentsInfoParser.new(@second_line.search("[@class='subtext']")[0]).parse
|
15
|
+
time = TimeInfoParser.new(@second_line.search("[@class='subtext']").children[3]).parse
|
16
|
+
return Entry.new(number, link, voting, user, comments, time)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
class LinkInfoParser
|
3
|
+
|
4
|
+
def initialize(link_element)
|
5
|
+
@element = link_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
link = @element.search("a")[0]['href']
|
10
|
+
title = @element.search("a")[0].inner_html
|
11
|
+
site_element = @element.search("span")
|
12
|
+
site = site_element.inner_html.sub("(","").sub(")","").strip if site_element.any?
|
13
|
+
return LinkInfo.new(title, link, site)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class TimeInfoParser
|
3
|
+
|
4
|
+
def initialize(time_element)
|
5
|
+
@element = time_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
value = @element.text.strip.split[0].to_i
|
10
|
+
unit_of_measure = @element.text.strip.split[1]
|
11
|
+
return TimeInfo.new(value, unit_of_measure)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
class UserInfoParser
|
3
|
+
|
4
|
+
def initialize(user_element)
|
5
|
+
@element = user_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
user_name = @element.search("a")[0].inner_html
|
10
|
+
user_page = @element.search("a")[0]['href']
|
11
|
+
return UserInfo.new(user_name, user_page)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class VotingInfoParser
|
3
|
+
|
4
|
+
def initialize(voting_element, score_element)
|
5
|
+
@voting_element = voting_element
|
6
|
+
@score_element = score_element
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
upvote = @voting_element[0]['href'] if @voting_element[0]
|
11
|
+
downvote = @voting_element[1]['href'] if @voting_element[1]
|
12
|
+
score = @score_element.search("span")[0].inner_html.split[0].to_i
|
13
|
+
return VotingInfo.new(score, upvote, downvote)
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-hackernews
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrea Dallera
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-08 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: require_all
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 19
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
- 0
|
34
|
+
version: 1.1.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: mechanize
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 23
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 0
|
49
|
+
- 0
|
50
|
+
version: 1.0.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: An interface to Hacker News
|
54
|
+
email: andrea@andreadallera.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- README.rdoc
|
63
|
+
- Rakefile
|
64
|
+
- lib/HNAPI/domain/comment/comment.rb
|
65
|
+
- lib/HNAPI/domain/user.rb
|
66
|
+
- lib/HNAPI/domain/entry/link_info.rb
|
67
|
+
- lib/HNAPI/domain/entry/voting_info.rb
|
68
|
+
- lib/HNAPI/domain/entry/time_info.rb
|
69
|
+
- lib/HNAPI/domain/entry/user_info.rb
|
70
|
+
- lib/HNAPI/domain/entry/entry.rb
|
71
|
+
- lib/HNAPI/domain/entry/comments_info.rb
|
72
|
+
- lib/HNAPI/services/parsers/link_info_parser.rb
|
73
|
+
- lib/HNAPI/services/parsers/entry_parser.rb
|
74
|
+
- lib/HNAPI/services/parsers/voting_info_parser.rb
|
75
|
+
- lib/HNAPI/services/parsers/user_info_parser.rb
|
76
|
+
- lib/HNAPI/services/parsers/time_info_parser.rb
|
77
|
+
- lib/HNAPI/services/parsers/entry_page_parser.rb
|
78
|
+
- lib/HNAPI/services/parsers/comments_info_parser.rb
|
79
|
+
- lib/HNAPI/services/voting_service.rb
|
80
|
+
- lib/HNAPI/services/mechanize_context.rb
|
81
|
+
- lib/HNAPI/services/comment_service.rb
|
82
|
+
- lib/HNAPI/services/login_service.rb
|
83
|
+
- lib/HNAPI/services/entry_service.rb
|
84
|
+
- lib/HNAPI/services/not_authenticated_error.rb
|
85
|
+
- lib/HNAPI/services/configuration_service.rb
|
86
|
+
- lib/ruby-hackernews.rb
|
87
|
+
has_rdoc: true
|
88
|
+
homepage: http://github.com/bolthar/ruby-hackernews
|
89
|
+
licenses: []
|
90
|
+
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 3
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
version: "0"
|
114
|
+
requirements: []
|
115
|
+
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 1.3.7
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: An interface to Hacker News
|
121
|
+
test_files: []
|
122
|
+
|