rst 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/{.rvmrc → .rvmrc.example} +0 -0
- data/README.md +2 -0
- data/bin/rst +26 -34
- data/features/help.feature +1 -1
- data/features/read_one_user.feature +14 -0
- data/features/step_definitions/rst_steps.rb +15 -2
- data/features/support/env.rb +1 -1
- data/features/users_search.feature +19 -0
- data/features/version.feature +9 -0
- data/features/world.feature +11 -1
- data/lib/rst.rb +1 -0
- data/lib/rst/cli.rb +44 -3
- data/lib/rst/client.rb +85 -14
- data/lib/rst/user.rb +49 -0
- data/lib/rst/version.rb +1 -1
- data/rst.gemspec +0 -1
- data/spec/cli_spec.rb +80 -0
- data/spec/client_spec.rb +41 -6
- data/spec/data/vcr_cassettes/successful_messages_all_page_2.yml +1361 -0
- data/spec/data/vcr_cassettes/successful_messages_user.yml +709 -0
- data/spec/data/vcr_cassettes/users_search_with_results.yml +484 -0
- data/spec/status_spec.rb +2 -2
- metadata +25 -22
- data/spec/world_spec.rb +0 -28
data/.gitignore
CHANGED
data/{.rvmrc → .rvmrc.example}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -20,6 +20,8 @@ Current Functionality
|
|
20
20
|
---------------------
|
21
21
|
|
22
22
|
* Read rstat.us world statuses without being logged in.
|
23
|
+
* Read a particular user's statuses without being logged in.
|
24
|
+
* Search for users whose username contains a pattern.
|
23
25
|
|
24
26
|
Future functionality
|
25
27
|
--------------------
|
data/bin/rst
CHANGED
@@ -1,49 +1,41 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# 1.9 adds realpath to resolve symlinks; 1.8 doesn't
|
3
|
-
# have this method, so we add it so we get resolved symlinks
|
4
|
-
# and compatibility
|
5
|
-
unless File.respond_to? :realpath
|
6
|
-
class File #:nodoc:
|
7
|
-
def self.realpath path
|
8
|
-
return realpath(File.readlink(path)) if symlink?(path)
|
9
|
-
path
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
$: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
14
|
-
|
15
|
-
require 'gli'
|
16
2
|
require 'rst'
|
3
|
+
require 'optparse'
|
17
4
|
|
18
|
-
|
5
|
+
options = {}
|
19
6
|
|
20
|
-
|
7
|
+
rst_parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = "A command line interface for rstat.us.
|
21
9
|
|
22
|
-
|
10
|
+
usage: rst [options] command
|
23
11
|
|
24
|
-
|
25
|
-
|
12
|
+
Commands:
|
13
|
+
help [COMMAND] Get more help for a partiular command.
|
14
|
+
user [USERNAME] See one particular user's updates.
|
15
|
+
world See the latest updates that anyone has made.
|
16
|
+
users-search [PATTERN] Search for usernames that match the pattern.
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
Options:"
|
19
|
+
|
20
|
+
opts.on("-n NUMBER", "The number of statuses to show") do |n|
|
21
|
+
options[:num] = n.to_i
|
31
22
|
end
|
32
|
-
end
|
33
23
|
|
34
|
-
|
35
|
-
|
24
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
25
|
+
puts opts
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on_tail("-v", "--version", "Show version") do
|
36
30
|
puts "version #{Rst::VERSION}"
|
37
|
-
exit
|
31
|
+
exit
|
38
32
|
end
|
39
|
-
true
|
40
|
-
end
|
41
33
|
|
42
|
-
post do |global, command, options, args|
|
43
34
|
end
|
44
35
|
|
45
|
-
|
46
|
-
|
36
|
+
rst_parser.parse!
|
37
|
+
if ARGV.empty?
|
38
|
+
puts rst_parser.help
|
39
|
+
else
|
40
|
+
Rst::CLI.run(options, ARGV)
|
47
41
|
end
|
48
|
-
|
49
|
-
exit GLI.run(ARGV)
|
data/features/help.feature
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: read [username]
|
2
|
+
In order to read one user's updates on rstat.us
|
3
|
+
I want `rst user [username]` to return all updates by just that user
|
4
|
+
So that I can see what they are up to and if I want to follow them or not
|
5
|
+
|
6
|
+
Scenario: read a particular user
|
7
|
+
When I run `rst user carols10cents`
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the output should contain 20 updates
|
10
|
+
|
11
|
+
Scenario: no argument
|
12
|
+
When I run `rst user`
|
13
|
+
Then the exit status should be 1
|
14
|
+
And the output should contain "Username is required."
|
@@ -4,7 +4,20 @@ Then /^the output should contain (\d+) updates$/ do |num|
|
|
4
4
|
num = num.to_i
|
5
5
|
|
6
6
|
lines = all_output.split("\n")
|
7
|
-
|
7
|
+
update_lines = lines.select{ |line| line.match(/\S+: \S+/) }
|
8
8
|
|
9
|
-
|
9
|
+
update_lines.size.should == num
|
10
10
|
end
|
11
|
+
|
12
|
+
Then /^the output should contain (\d+) users$/ do |num|
|
13
|
+
num = num.to_i
|
14
|
+
|
15
|
+
lines = all_output.split("\n")
|
16
|
+
user_lines = lines.select{ |line| line.match(/\S+ \(.+\): \S+/) }
|
17
|
+
|
18
|
+
user_lines.size.should == num
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^the output should be the version$/ do
|
22
|
+
all_output.should match(/^version \d+\.\d+.\d+$/)
|
23
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: users-search [pattern]
|
2
|
+
If I don't know someone's username exactly
|
3
|
+
I want to be able to look it up with a pattern
|
4
|
+
|
5
|
+
Scenario: search that returns results
|
6
|
+
When I run `rst users-search carols10cent`
|
7
|
+
Then the exit status should be 0
|
8
|
+
And the output should contain 2 users
|
9
|
+
|
10
|
+
Scenario: search that does not return results
|
11
|
+
When I run `rst users-search carols10centz`
|
12
|
+
Then the exit status should be 0
|
13
|
+
And the output should contain 0 users
|
14
|
+
And the output should contain "No users that match."
|
15
|
+
|
16
|
+
Scenario: no argument
|
17
|
+
When I run `rst users-search`
|
18
|
+
Then the exit status should be 1
|
19
|
+
And the output should contain "Username search pattern is required."
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Feature: Version
|
2
|
+
In order to know what version of rst I'm using
|
3
|
+
I want rst to print that out
|
4
|
+
So I don't have to look it up in the code
|
5
|
+
|
6
|
+
Scenario: rst --version
|
7
|
+
When I run `rst --version`
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the output should be the version
|
data/features/world.feature
CHANGED
@@ -6,4 +6,14 @@ Feature: world
|
|
6
6
|
Scenario: default world
|
7
7
|
When I run `rst world`
|
8
8
|
Then the exit status should be 0
|
9
|
-
And the output should contain 20 updates
|
9
|
+
And the output should contain 20 updates
|
10
|
+
|
11
|
+
Scenario: more updates
|
12
|
+
When I run `rst world -n 35`
|
13
|
+
Then the exit status should be 0
|
14
|
+
And the output should contain 35 updates
|
15
|
+
|
16
|
+
Scenario: fewer updates
|
17
|
+
When I run `rst world -n 5`
|
18
|
+
Then the exit status should be 0
|
19
|
+
And the output should contain 5 updates
|
data/lib/rst.rb
CHANGED
data/lib/rst/cli.rb
CHANGED
@@ -2,16 +2,57 @@ module Rst
|
|
2
2
|
module CLI
|
3
3
|
extend self
|
4
4
|
|
5
|
-
def
|
5
|
+
def run(options, args = [])
|
6
|
+
command = args[0]
|
7
|
+
rest_of_args = args[1, args.length]
|
8
|
+
|
9
|
+
results = send(command.gsub(/-/, "_"), options, rest_of_args)
|
10
|
+
|
11
|
+
puts results.join("\n\n")
|
12
|
+
rescue Exception => e
|
13
|
+
puts e.message
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def world(params = {}, args = [])
|
18
|
+
statuses(:messages_all, params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def user(params = {}, args = [])
|
22
|
+
username = args[0]
|
23
|
+
raise "Username is required." unless username
|
24
|
+
statuses(:messages_user, params.merge(:username => username))
|
25
|
+
end
|
26
|
+
|
27
|
+
def users_search(params = {}, args = [])
|
28
|
+
search_pattern = args[0]
|
29
|
+
raise "Username search pattern is required." unless search_pattern
|
30
|
+
users = Rst::Client.users_search(:pattern => search_pattern)
|
31
|
+
if users.empty?
|
32
|
+
["No users that match."]
|
33
|
+
else
|
34
|
+
users
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def statuses(which, params = {})
|
6
41
|
statuses = []
|
7
|
-
page
|
42
|
+
page = 0
|
43
|
+
num = params[:num] || 20
|
8
44
|
|
9
45
|
while statuses.size < num do
|
10
46
|
page += 1
|
11
|
-
|
47
|
+
messages = Rst::Client.send(which, params.merge(:page => page))
|
48
|
+
|
49
|
+
break if messages.empty?
|
50
|
+
|
51
|
+
statuses.concat messages
|
12
52
|
end
|
13
53
|
|
14
54
|
statuses.take(num)
|
15
55
|
end
|
56
|
+
|
16
57
|
end
|
17
58
|
end
|
data/lib/rst/client.rb
CHANGED
@@ -5,29 +5,100 @@ module Rst
|
|
5
5
|
module Client
|
6
6
|
extend self
|
7
7
|
|
8
|
-
def base_uri
|
9
|
-
"http://rstat.us"
|
10
|
-
end
|
11
|
-
|
12
8
|
def messages_all(params = {:page => 1})
|
13
|
-
|
14
|
-
|
9
|
+
messages_all_path = find_a_in(root_response, :rel => "messages-all")
|
10
|
+
messages_all_uri = resolve_relative_uri(
|
11
|
+
:relative => messages_all_path,
|
12
|
+
:base => base_uri
|
15
13
|
)
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
).first
|
20
|
-
|
21
|
-
url = (URI(base_uri) + URI(link["href"])).to_s
|
15
|
+
all_response = get_body(messages_all_uri)
|
16
|
+
current_uri = messages_all_uri
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
(params[:page] - 1).times do
|
19
|
+
next_page_path = find_a_in(all_response, :rel => "next")
|
20
|
+
current_uri = resolve_relative_uri(
|
21
|
+
:relative => next_page_path,
|
22
|
+
:base => current_uri
|
23
|
+
)
|
24
|
+
all_response = get_body(current_uri)
|
25
|
+
end
|
26
26
|
|
27
27
|
messages = all_response.css("div#messages ul.all li").map { |li|
|
28
28
|
Rst::Status.parse(li)
|
29
29
|
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def messages_user(params = {})
|
33
|
+
search_results = users_search(:pattern => params[:username])
|
34
|
+
|
35
|
+
result = search_results.detect { |sr|
|
36
|
+
sr.username.match(/^#{params[:username]}$/i)
|
37
|
+
}
|
38
|
+
|
39
|
+
user_uri = resolve_relative_uri(
|
40
|
+
:relative => result.path,
|
41
|
+
:base => base_uri
|
42
|
+
)
|
43
|
+
|
44
|
+
user_response = get_body(user_uri)
|
45
|
+
|
46
|
+
message_nodes = user_response.css("div#messages ul.messages-user li")
|
47
|
+
message_nodes.map { |li| Rst::Status.parse(li) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def users_search(params = {})
|
51
|
+
users_search_path = find_a_in(root_response, :rel => "users-search")
|
52
|
+
users_search_uri = resolve_relative_uri(
|
53
|
+
:relative => users_search_path,
|
54
|
+
:base => base_uri
|
55
|
+
)
|
56
|
+
|
57
|
+
users_search_response = get_body(users_search_uri)
|
58
|
+
|
59
|
+
form = users_search_response.css("form.users-search").first
|
60
|
+
search_uri = resolve_relative_uri(
|
61
|
+
:relative => form["action"],
|
62
|
+
:base => users_search_uri
|
63
|
+
)
|
30
64
|
|
65
|
+
user_lookup_query = "#{search_uri}?search=#{params[:pattern]}"
|
66
|
+
|
67
|
+
user_lookup_response = get_body(user_lookup_query)
|
68
|
+
|
69
|
+
search_results = user_lookup_response.css("div#users ul.search li.user")
|
70
|
+
search_results.map { |li| Rst::User.parse(li) }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def find_a_in(html, params = {})
|
76
|
+
raise "no rel specified" unless params[:rel]
|
77
|
+
|
78
|
+
link = html.xpath(
|
79
|
+
".//a[contains(@rel, '#{params[:rel]}')]"
|
80
|
+
).first["href"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def root_response
|
84
|
+
get_body(base_uri)
|
85
|
+
end
|
86
|
+
|
87
|
+
def resolve_relative_uri(params = {})
|
88
|
+
raise "no relative uri specified" unless params[:relative]
|
89
|
+
raise "no base uri specified" unless params[:base]
|
90
|
+
|
91
|
+
(URI(params[:base]) + URI(params[:relative])).to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def base_uri
|
95
|
+
"http://rstat.us"
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_body(uri)
|
99
|
+
Nokogiri::HTML.parse(
|
100
|
+
Typhoeus::Request.get(uri).body
|
101
|
+
)
|
31
102
|
end
|
32
103
|
|
33
104
|
def hydra
|
data/lib/rst/user.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rst
|
2
|
+
class User
|
3
|
+
attr_reader :username, :full_name, :description, :path
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
@username = cleanup_whitespace(params[:username])
|
7
|
+
@full_name = cleanup_whitespace(params[:full_name])
|
8
|
+
@description = cleanup_whitespace(params[:description])
|
9
|
+
@path = cleanup_whitespace(params[:path])
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{@username} (#{display_full_name}): #{display_description}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_full_name
|
17
|
+
if @full_name.nil? || @full_name == ""
|
18
|
+
"No full name"
|
19
|
+
else
|
20
|
+
@full_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def display_description
|
25
|
+
if @description.nil? || @description == ""
|
26
|
+
"No bio"
|
27
|
+
else
|
28
|
+
@description
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.parse(li)
|
33
|
+
new(
|
34
|
+
:username => li.css("span.user-text").text,
|
35
|
+
:full_name => li.css("span.user-name").text,
|
36
|
+
:description => li.css("span.description").text,
|
37
|
+
:path => li.xpath(".//a[contains(@rel, 'user')]").first["href"]
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def cleanup_whitespace(html_text)
|
44
|
+
if html_text
|
45
|
+
html_text.gsub(/\n/, ' ').squeeze(" ").strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/rst/version.rb
CHANGED
data/rst.gemspec
CHANGED
@@ -21,7 +21,6 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_development_dependency('rake','~> 0.9.2')
|
22
22
|
gem.add_development_dependency('vcr', '~> 2.1.1')
|
23
23
|
|
24
|
-
gem.add_dependency('gli', '~> 1.6.0')
|
25
24
|
gem.add_dependency('typhoeus', '~> 0.3.3')
|
26
25
|
gem.add_dependency('nokogiri', '~> 1.5.2')
|
27
26
|
end
|