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 CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .rvmrc
5
6
  .DS_Store
6
7
  .yardoc
7
8
  coverage
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
- include GLI
5
+ options = {}
19
6
 
20
- program_desc 'A command line interface for rstat.us'
7
+ rst_parser = OptionParser.new do |opts|
8
+ opts.banner = "A command line interface for rstat.us.
21
9
 
22
- version Rst::VERSION
10
+ usage: rst [options] command
23
11
 
24
- desc 'Display the version and exit.'
25
- switch [:v, :version]
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
- desc 'Get the latest statuses from the whole rstat.us world'
28
- command :world do |c|
29
- c.action do |global_options, options, args|
30
- puts Rst::CLI.world.join("\n\n")
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
- pre do |global, command, options, args|
35
- if global[:v]
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! 0
31
+ exit
38
32
  end
39
- true
40
- end
41
33
 
42
- post do |global, command, options, args|
43
34
  end
44
35
 
45
- on_error do |exception|
46
- true
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)
@@ -22,5 +22,5 @@ Feature: Help text
22
22
  |--help|
23
23
  And the output should contain:
24
24
  """
25
- usage: rst [global options] command
25
+ usage: rst [options] command
26
26
  """
@@ -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
- non_blank_lines = lines.select{ |line| line.match(/\S+: \S+/) }
7
+ update_lines = lines.select{ |line| line.match(/\S+: \S+/) }
8
8
 
9
- non_blank_lines.size.should == num
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
@@ -8,7 +8,7 @@ Before do
8
8
  @puts = true
9
9
  @original_rubylib = ENV['RUBYLIB']
10
10
  ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
11
- @aruba_timeout_seconds = 5
11
+ @aruba_timeout_seconds = 10
12
12
  end
13
13
 
14
14
  After do
@@ -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
@@ -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
@@ -2,6 +2,7 @@ require "rst/version"
2
2
  require "rst/cli"
3
3
  require "rst/status"
4
4
  require "rst/client"
5
+ require "rst/user"
5
6
 
6
7
  module Rst
7
8
 
@@ -2,16 +2,57 @@ module Rst
2
2
  module CLI
3
3
  extend self
4
4
 
5
- def world(num = 20)
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 = 0
42
+ page = 0
43
+ num = params[:num] || 20
8
44
 
9
45
  while statuses.size < num do
10
46
  page += 1
11
- statuses.concat Rst::Client.messages_all(:page => page)
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
@@ -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
- root_response = Nokogiri::HTML.parse(
14
- Typhoeus::Request.get(base_uri).body
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
- link = root_response.xpath(
18
- "//a[contains(@rel, 'messages-all')]"
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
- all_response = Nokogiri::HTML.parse(
24
- Typhoeus::Request.get(url).body
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Rst
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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