tweetwine 0.2.5 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +30 -17
- data/README.rdoc +16 -17
- data/Rakefile +2 -0
- data/bin/tweetwine +3 -68
- data/example/example_helper.rb +31 -41
- data/example/fixtures/{statuses.json → home.json} +0 -0
- data/example/fixtures/mentions.json +1 -0
- data/example/fixtures/search.json +1 -0
- data/example/fixtures/update.json +1 -0
- data/example/fixtures/user.json +1 -0
- data/example/fixtures/users.json +1 -0
- data/example/search_statuses_example.rb +36 -0
- data/example/show_followers_example.rb +23 -0
- data/example/show_friends_example.rb +23 -0
- data/example/show_home_example.rb +51 -0
- data/example/show_mentions_example.rb +23 -0
- data/example/show_metadata_example.rb +54 -7
- data/example/show_user_example.rb +37 -0
- data/example/update_status_example.rb +65 -0
- data/lib/tweetwine/cli.rb +241 -0
- data/lib/tweetwine/client.rb +94 -57
- data/lib/tweetwine/io.rb +39 -28
- data/lib/tweetwine/meta.rb +1 -1
- data/lib/tweetwine/retrying_http.rb +93 -0
- data/lib/tweetwine/startup_config.rb +14 -15
- data/lib/tweetwine/url_shortener.rb +13 -8
- data/lib/tweetwine/util.rb +14 -0
- data/lib/tweetwine.rb +2 -1
- data/test/cli_test.rb +16 -0
- data/test/client_test.rb +275 -205
- data/test/fixtures/test_config.yaml +2 -1
- data/test/io_test.rb +89 -62
- data/test/retrying_http_test.rb +127 -0
- data/test/startup_config_test.rb +52 -27
- data/test/test_helper.rb +32 -17
- data/test/url_shortener_test.rb +18 -18
- data/test/util_test.rb +145 -47
- metadata +20 -7
- data/example/show_latest_statuses_example.rb +0 -45
- data/lib/tweetwine/rest_client_wrapper.rb +0 -37
- data/test/rest_client_wrapper_test.rb +0 -68
data/lib/tweetwine/io.rb
CHANGED
@@ -15,7 +15,7 @@ module Tweetwine
|
|
15
15
|
def initialize(options)
|
16
16
|
@input = options[:input] || $stdin
|
17
17
|
@output = options[:output] || $stdout
|
18
|
-
@
|
18
|
+
@colors = options[:colors] || false
|
19
19
|
end
|
20
20
|
|
21
21
|
def prompt(prompt)
|
@@ -34,7 +34,7 @@ module Tweetwine
|
|
34
34
|
def confirm(msg)
|
35
35
|
@output.print "#{msg} [yN] "
|
36
36
|
confirmation = @input.gets.strip
|
37
|
-
confirmation.downcase[0,1] == "y"
|
37
|
+
confirmation.downcase[0, 1] == "y"
|
38
38
|
end
|
39
39
|
|
40
40
|
def show_status_preview(status)
|
@@ -46,6 +46,7 @@ module Tweetwine
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def show_record(record)
|
49
|
+
clean_record!(record)
|
49
50
|
if record[:status]
|
50
51
|
show_record_as_user_with_status(record)
|
51
52
|
else
|
@@ -55,63 +56,73 @@ module Tweetwine
|
|
55
56
|
|
56
57
|
private
|
57
58
|
|
59
|
+
def clean_record!(record)
|
60
|
+
record.each_pair do |key, value|
|
61
|
+
if value.is_a? Hash
|
62
|
+
clean_record!(value)
|
63
|
+
else
|
64
|
+
unless value.nil?
|
65
|
+
value = value.to_s
|
66
|
+
record[key] = value.empty? ? nil : value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
58
72
|
def show_record_as_user(record)
|
59
73
|
@output.puts <<-END
|
60
|
-
#{format_user(record[:
|
74
|
+
#{format_user(record[:from_user])}
|
61
75
|
|
62
76
|
END
|
63
77
|
end
|
64
78
|
|
65
79
|
def show_record_as_user_with_status(record)
|
66
80
|
@output.puts <<-END
|
67
|
-
#{format_record_header(record)}
|
68
|
-
#{format_status(record[:status]
|
81
|
+
#{format_record_header(record[:from_user], record[:to_user], record[:created_at])}
|
82
|
+
#{format_status(record[:status])}
|
69
83
|
|
70
84
|
END
|
71
85
|
end
|
72
86
|
|
73
87
|
def format_user(user)
|
74
|
-
user = user
|
75
|
-
colorize!(:green, user) if @colorize
|
88
|
+
user = colorize(:green, user) if @colors
|
76
89
|
user
|
77
90
|
end
|
78
91
|
|
79
92
|
def format_status(status)
|
80
|
-
|
81
|
-
|
82
|
-
colorize_all_by_group
|
83
|
-
colorize_all_by_group!(:magenta, status, HASHTAG_REGEX)
|
93
|
+
if @colors
|
94
|
+
status = colorize_all_by_group(:yellow, status, USERNAME_REGEX)
|
95
|
+
status = colorize_all_by_group(:magenta, status, HASHTAG_REGEX)
|
84
96
|
URI.extract(status, ["http", "https"]).uniq.each do |url|
|
85
|
-
colorize_all
|
97
|
+
status = colorize_all(:cyan, status, url)
|
86
98
|
end
|
87
99
|
end
|
88
100
|
status
|
89
101
|
end
|
90
102
|
|
91
|
-
def format_record_header(
|
92
|
-
time_diff_value, time_diff_unit = Util.humanize_time_diff(
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
"in reply to #{
|
103
|
+
def format_record_header(from_user, to_user, created_at)
|
104
|
+
time_diff_value, time_diff_unit = Util.humanize_time_diff(created_at, Time.now)
|
105
|
+
if @colors
|
106
|
+
from_user = colorize(:green, from_user)
|
107
|
+
to_user = colorize(:green, to_user) if to_user
|
108
|
+
end
|
109
|
+
if to_user
|
110
|
+
"#{from_user}, in reply to #{to_user}, #{time_diff_value} #{time_diff_unit} ago:"
|
99
111
|
else
|
100
|
-
""
|
112
|
+
"#{from_user}, #{time_diff_value} #{time_diff_unit} ago:"
|
101
113
|
end
|
102
|
-
"#{from_user}, #{in_reply_to}#{time_diff_value} #{time_diff_unit} ago:"
|
103
114
|
end
|
104
115
|
|
105
|
-
def colorize_all
|
106
|
-
str.gsub
|
116
|
+
def colorize_all(color, str, pattern)
|
117
|
+
str.gsub(pattern) { |s| colorize_str(COLOR_CODES[color.to_sym], s) }
|
107
118
|
end
|
108
119
|
|
109
|
-
def colorize_all_by_group
|
110
|
-
|
120
|
+
def colorize_all_by_group(color, str, pattern)
|
121
|
+
Util.str_gsub_by_group(str, pattern) { |s| colorize_str(COLOR_CODES[color.to_sym], s) }
|
111
122
|
end
|
112
123
|
|
113
|
-
def colorize
|
114
|
-
|
124
|
+
def colorize(color, str)
|
125
|
+
colorize_str(COLOR_CODES[color.to_sym], str)
|
115
126
|
end
|
116
127
|
|
117
128
|
def colorize_str(color_code, str)
|
data/lib/tweetwine/meta.rb
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "rest_client"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
class HttpError < RuntimeError; end
|
5
|
+
|
6
|
+
module RetryingHttp
|
7
|
+
class Base
|
8
|
+
MAX_RETRIES = 3
|
9
|
+
RETRY_BASE_WAIT_TIMEOUT = 4
|
10
|
+
|
11
|
+
def self.use_retries_with(*methods)
|
12
|
+
methods.each do |method_name|
|
13
|
+
module_eval do
|
14
|
+
non_retrying_method_name = "original_#{method_name}".to_sym
|
15
|
+
alias_method non_retrying_method_name, method_name
|
16
|
+
define_method(method_name) do |*args|
|
17
|
+
do_with_retries { send(non_retrying_method_name, *args) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def do_with_retries
|
26
|
+
tries = 0
|
27
|
+
begin
|
28
|
+
tries += 1
|
29
|
+
yield
|
30
|
+
rescue Errno::ECONNRESET => e
|
31
|
+
if tries < MAX_RETRIES
|
32
|
+
timeout = RETRY_BASE_WAIT_TIMEOUT**tries
|
33
|
+
@io.warn("Could not connect -- retrying in #{timeout} seconds") if @io
|
34
|
+
sleep timeout
|
35
|
+
retry
|
36
|
+
else
|
37
|
+
raise HttpError, e
|
38
|
+
end
|
39
|
+
rescue RestClient::Exception, SocketError, SystemCallError => e
|
40
|
+
raise HttpError, e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Client < Base
|
46
|
+
attr_accessor :io
|
47
|
+
|
48
|
+
def initialize(io)
|
49
|
+
@io = io
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(*args)
|
53
|
+
RestClient.get(*args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def post(*args)
|
57
|
+
RestClient.post(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_resource(url, options = {})
|
61
|
+
resource = Resource.new(RestClient::Resource.new(url, options))
|
62
|
+
resource.io = @io
|
63
|
+
resource
|
64
|
+
end
|
65
|
+
|
66
|
+
use_retries_with :get, :post
|
67
|
+
end
|
68
|
+
|
69
|
+
class Resource < Base
|
70
|
+
attr_accessor :io
|
71
|
+
|
72
|
+
def initialize(wrapped_resource)
|
73
|
+
@wrapped = wrapped_resource
|
74
|
+
end
|
75
|
+
|
76
|
+
def [](suburl)
|
77
|
+
instance = self.class.new(@wrapped[suburl])
|
78
|
+
instance.io = @io
|
79
|
+
instance
|
80
|
+
end
|
81
|
+
|
82
|
+
def get(*args)
|
83
|
+
@wrapped.get(*args)
|
84
|
+
end
|
85
|
+
|
86
|
+
def post(*args)
|
87
|
+
@wrapped.post(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
use_retries_with :get, :post
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -2,34 +2,33 @@ require "yaml"
|
|
2
2
|
|
3
3
|
module Tweetwine
|
4
4
|
class StartupConfig
|
5
|
-
attr_reader :options, :command
|
5
|
+
attr_reader :options, :command
|
6
6
|
|
7
|
-
def initialize(supported_commands)
|
8
|
-
|
9
|
-
raise ArgumentError, "
|
10
|
-
@
|
11
|
-
@command =
|
12
|
-
@args = []
|
7
|
+
def initialize(supported_commands, default_command, default_opts = {})
|
8
|
+
raise ArgumentError, "Must give at least one supported command" if supported_commands.empty?
|
9
|
+
raise ArgumentError, "Default command is not a supported command" unless supported_commands.include? default_command
|
10
|
+
@supported_commands, @default_command = supported_commands, default_command
|
11
|
+
@options, @command = default_opts, nil
|
13
12
|
end
|
14
13
|
|
15
|
-
def parse(args = [], config_file = nil, &
|
16
|
-
options = parse_options(args, config_file, &
|
17
|
-
command = if args.empty? then @
|
14
|
+
def parse(args = [], config_file = nil, &cmd_option_parser)
|
15
|
+
options = @options.merge(parse_options(args, config_file, &cmd_option_parser))
|
16
|
+
command = if args.empty? then @default_command else args.shift.to_sym end
|
18
17
|
raise ArgumentError, "Unknown command" unless @supported_commands.include? command
|
19
|
-
@options, @command
|
18
|
+
@options, @command = options, command
|
20
19
|
self
|
21
20
|
end
|
22
21
|
|
23
22
|
private
|
24
23
|
|
25
|
-
def parse_options(args, config_file, &
|
26
|
-
cmd_options = if
|
24
|
+
def parse_options(args, config_file, &cmd_option_parser)
|
25
|
+
cmd_options = if cmd_option_parser then parse_cmdline_args(args, &cmd_option_parser) else {} end
|
27
26
|
config_options = if config_file && File.exists?(config_file) then parse_config_file(config_file) else {} end
|
28
27
|
config_options.merge(cmd_options)
|
29
28
|
end
|
30
29
|
|
31
|
-
def parse_cmdline_args(args, &
|
32
|
-
|
30
|
+
def parse_cmdline_args(args, &cmd_option_parser)
|
31
|
+
cmd_option_parser.call(args)
|
33
32
|
end
|
34
33
|
|
35
34
|
def parse_config_file(config_file)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Tweetwine
|
2
2
|
class UrlShortener
|
3
|
-
def initialize(
|
4
|
-
@
|
3
|
+
def initialize(http_client, options)
|
4
|
+
@http_client = http_client
|
5
5
|
options = Options.new(options, "URL shortening")
|
6
6
|
@method = (options[:method] || :get).to_sym
|
7
7
|
@service_url = options.require :service_url
|
@@ -17,22 +17,27 @@ module Tweetwine
|
|
17
17
|
|
18
18
|
def shorten(url)
|
19
19
|
require "nokogiri"
|
20
|
-
|
20
|
+
response = @http_client.send(@method, *get_service_url_and_params(url))
|
21
|
+
doc = Nokogiri::HTML(response)
|
22
|
+
doc.xpath(@xpath_selector).first.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def get_service_url_and_params(url_to_shorten)
|
28
|
+
case @method
|
21
29
|
when :get
|
22
30
|
tmp = @extra_params.dup
|
23
|
-
tmp << "#{@url_param_name}=#{
|
31
|
+
tmp << "#{@url_param_name}=#{url_to_shorten}"
|
24
32
|
service_url = "#{@service_url}?#{tmp.join('&')}"
|
25
33
|
[service_url]
|
26
34
|
when :post
|
27
35
|
service_url = @service_url
|
28
|
-
params = @extra_params.merge({ @url_param_name.to_sym =>
|
36
|
+
params = @extra_params.merge({ @url_param_name.to_sym => url_to_shorten })
|
29
37
|
[service_url, params]
|
30
38
|
else
|
31
39
|
raise "Unrecognized HTTP request method"
|
32
40
|
end
|
33
|
-
response = @rest_client.send(@method, *rest)
|
34
|
-
doc = Nokogiri::HTML(response)
|
35
|
-
doc.xpath(@xpath_selector).first.to_s
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
data/lib/tweetwine/util.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "time"
|
2
|
+
require "uri"
|
2
3
|
|
3
4
|
module Tweetwine
|
4
5
|
module Util
|
@@ -59,6 +60,19 @@ module Tweetwine
|
|
59
60
|
dup_str
|
60
61
|
end
|
61
62
|
|
63
|
+
def self.percent_encode(str)
|
64
|
+
URI.escape(str.to_s, /[^#{URI::PATTERN::UNRESERVED}]/)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.find_hash_path(hash, path)
|
68
|
+
return nil if hash.nil?
|
69
|
+
path = [path] if !path.is_a? Array
|
70
|
+
path.inject(hash) do |result, key|
|
71
|
+
return hash.default if key.nil? || result.nil?
|
72
|
+
result[key]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
62
76
|
private
|
63
77
|
|
64
78
|
def self.pluralize_unit(value, unit)
|
data/lib/tweetwine.rb
CHANGED
data/test/cli_test.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
|
5
|
+
class CLITest < Test::Unit::TestCase
|
6
|
+
context "A CLI, upon initialization" do
|
7
|
+
should "disallow using #new to create a new instance" do
|
8
|
+
assert_raise(NoMethodError) { CLI.new("-v", "test", "") {} }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Other unit tests are meaningless. See /example directory for tests
|
13
|
+
# about the functionality of the application.
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|