rst 0.1.1 → 0.2.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/.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
|