tuomas-tweetwine 0.2.1 → 0.2.2
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/CHANGELOG.rdoc +10 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +1 -21
- data/Rakefile +17 -15
- data/bin/tweetwine +2 -4
- data/lib/tweetwine/client.rb +4 -2
- data/lib/tweetwine/io.rb +6 -8
- data/lib/tweetwine/meta.rb +1 -8
- data/lib/tweetwine/options.rb +7 -2
- data/lib/tweetwine/rest_client_wrapper.rb +1 -1
- data/lib/tweetwine/url_shortener.rb +3 -3
- data/lib/tweetwine.rb +1 -1
- data/test/client_test.rb +73 -6
- data/test/io_test.rb +51 -15
- data/test/options_test.rb +28 -4
- data/test/rest_client_wrapper_test.rb +10 -3
- data/test/startup_config_test.rb +1 -1
- data/test/test_helper.rb +1 -2
- data/test/url_shortener_test.rb +107 -47
- data/test/util_test.rb +2 -2
- metadata +6 -5
data/CHANGELOG.rdoc
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
=== 0.2.2 released 2009-09-03
|
|
2
|
+
|
|
3
|
+
* Highlight hashtags in statuses.
|
|
4
|
+
* URL shortening step shortens only unique URLs.
|
|
5
|
+
* URL shortening step is skipped if
|
|
6
|
+
- there is a connection error to the URL shortening service, or
|
|
7
|
+
- required libraries are not installed (nokogiri).
|
|
8
|
+
* Fixed a colorization bug for duplicate URLs.
|
|
9
|
+
* Removed dependencies to Rubygems.
|
|
10
|
+
|
|
1
11
|
=== 0.2.1 released 2009-08-17
|
|
2
12
|
|
|
3
13
|
* Command line option "-v" show version information.
|
data/MIT-LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2009 Tuomas Kareinen.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
CHANGED
|
@@ -84,24 +84,4 @@ com >.
|
|
|
84
84
|
|
|
85
85
|
== Legal notes
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Copyright (c) 2009 Tuomas Kareinen.
|
|
90
|
-
|
|
91
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
92
|
-
of this software and associated documentation files (the "Software"), to
|
|
93
|
-
deal in the Software without restriction, including without limitation the
|
|
94
|
-
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
95
|
-
sell copies of the Software, and to permit persons to whom the Software is
|
|
96
|
-
furnished to do so, subject to the following conditions:
|
|
97
|
-
|
|
98
|
-
The above copyright notice and this permission notice shall be included in
|
|
99
|
-
all copies or substantial portions of the Software.
|
|
100
|
-
|
|
101
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
102
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
103
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
104
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
105
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
106
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
107
|
-
IN THE SOFTWARE.
|
|
87
|
+
Copyright (c) 2009 Tuomas Kareinen. See MIT-LICENSE.txt in this directory.
|
data/Rakefile
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
|
|
2
|
+
|
|
1
3
|
require "rubygems"
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
require "
|
|
5
|
-
|
|
6
|
-
version = Tweetwine::Meta::VERSION
|
|
5
|
+
name = "tweetwine"
|
|
6
|
+
require "#{name}"
|
|
7
|
+
version = Tweetwine::VERSION
|
|
7
8
|
|
|
8
9
|
require "rake/clean"
|
|
9
10
|
|
|
10
11
|
require "rake/gempackagetask"
|
|
11
12
|
spec = Gem::Specification.new do |s|
|
|
12
|
-
s.name =
|
|
13
|
+
s.name = name
|
|
13
14
|
s.version = version
|
|
14
15
|
s.homepage = "http://github.com/tuomas/tweetwine"
|
|
15
16
|
s.summary = "A simple Twitter agent for command line use"
|
|
@@ -19,14 +20,14 @@ spec = Gem::Specification.new do |s|
|
|
|
19
20
|
s.email = "tkareine@gmail.com"
|
|
20
21
|
|
|
21
22
|
s.platform = Gem::Platform::RUBY
|
|
22
|
-
s.files = FileList["Rakefile", "*.rdoc", "bin/**/*", "lib/**/*", "test/**/*"].to_a
|
|
23
|
+
s.files = FileList["Rakefile", "MIT-LICENSE.txt", "*.rdoc", "bin/**/*", "lib/**/*", "test/**/*"].to_a
|
|
23
24
|
s.executables = ["tweetwine"]
|
|
24
25
|
|
|
25
26
|
s.add_dependency("rest-client", ">= 1.0.0")
|
|
26
27
|
|
|
27
28
|
s.has_rdoc = true
|
|
28
|
-
s.extra_rdoc_files = FileList["*.rdoc"].to_a
|
|
29
|
-
s.rdoc_options << "--title" << "#{
|
|
29
|
+
s.extra_rdoc_files = FileList["MIT-LICENSE.txt", "*.rdoc"].to_a
|
|
30
|
+
s.rdoc_options << "--title" << "#{name} #{version}" \
|
|
30
31
|
<< "--main" << "README.rdoc" \
|
|
31
32
|
<< "--exclude" << "test" \
|
|
32
33
|
<< "--line-numbers"
|
|
@@ -45,29 +46,30 @@ task :gemspec do
|
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
task :install => [:package] do
|
|
48
|
-
sh %{sudo gem install pkg/#{
|
|
49
|
+
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
task :uninstall => [:clean] do
|
|
52
|
-
sh %{sudo gem uninstall #{
|
|
53
|
+
sh %{sudo gem uninstall #{name}}
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
require "rake/rdoctask"
|
|
56
57
|
desc "Create documentation"
|
|
57
58
|
Rake::RDocTask.new(:rdoc) do |rd|
|
|
58
59
|
rd.rdoc_dir = "rdoc"
|
|
59
|
-
rd.title = "#{
|
|
60
|
+
rd.title = "#{name} #{version}"
|
|
60
61
|
rd.main = "README.rdoc"
|
|
61
|
-
rd.rdoc_files.include("*.rdoc", "lib/**/*.rb")
|
|
62
|
+
rd.rdoc_files.include("MIT-LICENSE.txt", "*.rdoc", "lib/**/*.rb")
|
|
62
63
|
rd.options << "--line-numbers"
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
require "rake/testtask"
|
|
66
67
|
desc "Run tests"
|
|
67
|
-
Rake::TestTask.new do |t|
|
|
68
|
-
t.test_files = FileList["test
|
|
68
|
+
Rake::TestTask.new(:test) do |t|
|
|
69
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
69
70
|
t.verbose = true
|
|
70
71
|
t.warning = true
|
|
72
|
+
t.libs << "test"
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
desc "Find code smells"
|
|
@@ -77,7 +79,7 @@ end
|
|
|
77
79
|
|
|
78
80
|
desc "Search unfinished parts of source code"
|
|
79
81
|
task :todo do
|
|
80
|
-
FileList["**/*.rb"].egrep
|
|
82
|
+
FileList["**/*.rb", "**/*.rdoc", "**/*.txt"].egrep /(TODO|FIXME)/
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
task :default => :test
|
data/bin/tweetwine
CHANGED
|
@@ -10,10 +10,8 @@ trap("INT") do
|
|
|
10
10
|
exit(EXIT_SIGINT)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
require "rubygems"
|
|
14
13
|
require "optparse"
|
|
15
|
-
|
|
16
|
-
require File.dirname(__FILE__) << "/../lib/tweetwine"
|
|
14
|
+
require "tweetwine"
|
|
17
15
|
|
|
18
16
|
include Tweetwine
|
|
19
17
|
|
|
@@ -47,7 +45,7 @@ Usage: tweetwine [options...] [command]
|
|
|
47
45
|
options[:page_num] = arg
|
|
48
46
|
end
|
|
49
47
|
opt.on("-v", "--version", "Show version information and exit") do
|
|
50
|
-
puts
|
|
48
|
+
puts "#{File.basename($0)} #{VERSION}"
|
|
51
49
|
exit(EXIT_VERSION)
|
|
52
50
|
end
|
|
53
51
|
opt.on_tail("-h", "--help", "Show this help message and exit") do
|
data/lib/tweetwine/client.rb
CHANGED
|
@@ -159,12 +159,14 @@ module Tweetwine
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def shorten_urls!(status)
|
|
162
|
-
url_pairs = URI.extract(status, ["http", "https"]).map do |url_to_be_shortened|
|
|
162
|
+
url_pairs = URI.extract(status, ["http", "https"]).uniq.map do |url_to_be_shortened|
|
|
163
163
|
[url_to_be_shortened, @url_shortener.shorten(url_to_be_shortened)]
|
|
164
164
|
end
|
|
165
165
|
url_pairs.reject { |pair| pair.last.nil? || pair.last.empty? }.each do |url_pair|
|
|
166
|
-
status.
|
|
166
|
+
status.gsub!(url_pair.first, url_pair.last)
|
|
167
167
|
end
|
|
168
|
+
rescue ClientError, LoadError => e
|
|
169
|
+
@io.warn "#{e}. Skipping URL shortening..."
|
|
168
170
|
end
|
|
169
171
|
end
|
|
170
172
|
end
|
data/lib/tweetwine/io.rb
CHANGED
|
@@ -9,7 +9,8 @@ module Tweetwine
|
|
|
9
9
|
:yellow => 33
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
HASHTAG_REGEX = /#[\w-]+/
|
|
13
|
+
USERNAME_REGEX = /@\w+/
|
|
13
14
|
|
|
14
15
|
def initialize(options)
|
|
15
16
|
@input = options[:input] || $stdin
|
|
@@ -78,9 +79,10 @@ module Tweetwine
|
|
|
78
79
|
def format_status(status)
|
|
79
80
|
status = status.dup
|
|
80
81
|
if @colorize
|
|
81
|
-
colorize_all!(:yellow, status,
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
colorize_all!(:yellow, status, USERNAME_REGEX)
|
|
83
|
+
colorize_all!(:magenta, status, HASHTAG_REGEX)
|
|
84
|
+
URI.extract(status, ["http", "https"]).uniq.each do |url|
|
|
85
|
+
colorize_all!(:cyan, status, url)
|
|
84
86
|
end
|
|
85
87
|
end
|
|
86
88
|
status
|
|
@@ -104,10 +106,6 @@ module Tweetwine
|
|
|
104
106
|
str.gsub!(pattern) { |s| colorize_str(COLOR_CODES[color.to_sym], s) }
|
|
105
107
|
end
|
|
106
108
|
|
|
107
|
-
def colorize_first!(color, str, pattern)
|
|
108
|
-
str.sub!(pattern) { |s| colorize_str(COLOR_CODES[color.to_sym], s) }
|
|
109
|
-
end
|
|
110
|
-
|
|
111
109
|
def colorize!(color, str)
|
|
112
110
|
str.replace colorize_str(COLOR_CODES[color.to_sym], str)
|
|
113
111
|
end
|
data/lib/tweetwine/meta.rb
CHANGED
data/lib/tweetwine/options.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module Tweetwine
|
|
2
2
|
class Options
|
|
3
|
-
def initialize(options)
|
|
3
|
+
def initialize(options, source = nil)
|
|
4
4
|
@hash = options.to_hash
|
|
5
|
+
@source = source
|
|
5
6
|
end
|
|
6
7
|
|
|
7
8
|
def [](key)
|
|
@@ -10,7 +11,11 @@ module Tweetwine
|
|
|
10
11
|
|
|
11
12
|
def require(key)
|
|
12
13
|
value = @hash[key]
|
|
13
|
-
|
|
14
|
+
if value.nil?
|
|
15
|
+
msg = "Option #{key} is required"
|
|
16
|
+
msg << " for #{@source}" if @source
|
|
17
|
+
raise ArgumentError, msg
|
|
18
|
+
end
|
|
14
19
|
value
|
|
15
20
|
end
|
|
16
21
|
end
|
|
@@ -10,7 +10,7 @@ module Tweetwine
|
|
|
10
10
|
|
|
11
11
|
def self.method_missing(name, *args, &block)
|
|
12
12
|
RestClient.send(name, *args, &block)
|
|
13
|
-
rescue RestClient::Exception, SystemCallError => e
|
|
13
|
+
rescue RestClient::Exception, SocketError, SystemCallError => e
|
|
14
14
|
raise ClientError, e.message
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
module Tweetwine
|
|
2
2
|
class UrlShortener
|
|
3
3
|
def initialize(options)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@method = options[:method].to_sym || :get
|
|
4
|
+
options = Options.new(options, "URL shortening")
|
|
5
|
+
@method = (options[:method] || :get).to_sym
|
|
7
6
|
@service_url = options.require :service_url
|
|
8
7
|
@url_param_name = options.require :url_param_name
|
|
9
8
|
@extra_params = options[:extra_params] || {}
|
|
@@ -16,6 +15,7 @@ module Tweetwine
|
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def shorten(url)
|
|
18
|
+
require "nokogiri"
|
|
19
19
|
rest = case @method
|
|
20
20
|
when :get
|
|
21
21
|
tmp = @extra_params.dup
|
data/lib/tweetwine.rb
CHANGED
data/test/client_test.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
require "json"
|
|
3
3
|
|
|
4
4
|
Mocha::Configuration.allow(:stubbing_non_existent_method)
|
|
@@ -12,9 +12,9 @@ class ClientTest < Test::Unit::TestCase
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
should "raise exception when no authentication data is given" do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
assert_raise(ArgumentError) { Client.new(@io, {}) }
|
|
16
|
+
assert_raise(ArgumentError) { Client.new(@io, { :password => "bar" }) }
|
|
17
|
+
assert_raise(ArgumentError) { Client.new(@io, { :username => "", :password => "bar" }) }
|
|
18
18
|
assert_nothing_raised { Client.new(@io, { :username => "foo", :password => "bar" }) }
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -29,7 +29,7 @@ class ClientTest < Test::Unit::TestCase
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
should "raise an exception for configured number of statuses if not in allowed range" do
|
|
32
|
-
|
|
32
|
+
assert_raise(ArgumentError) { Client.new(@io, { :username => "foo", :password => "bar", :num_statuses => 0 }) }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
should "use default page number if not configured otherwise" do
|
|
@@ -43,7 +43,7 @@ class ClientTest < Test::Unit::TestCase
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
should "raise an exception for configured page number if not in allowed range" do
|
|
46
|
-
|
|
46
|
+
assert_raise(ArgumentError) { Client.new(@io, { :username => "foo", :password => "bar", :page_num => 0 }) }
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -343,6 +343,73 @@ class ClientTest < Test::Unit::TestCase
|
|
|
343
343
|
@io.expects(:show_record).with(gen_records[0])
|
|
344
344
|
@client.update(status)
|
|
345
345
|
end
|
|
346
|
+
|
|
347
|
+
should "reuse a shortened URL for duplicate long URLs" do
|
|
348
|
+
long_urls = ["http://www.w3.org/TR/1999/REC-xpath-19991116"] * 2
|
|
349
|
+
long_status = long_urls.join(" and ")
|
|
350
|
+
short_url = "http://shorten.it/2k7mk"
|
|
351
|
+
short_status = ([short_url] * 2).join(" and ")
|
|
352
|
+
status_records, gen_records = create_test_statuses(
|
|
353
|
+
{ :user => @username,
|
|
354
|
+
:status => {
|
|
355
|
+
:created_at => Time.at(1).to_s,
|
|
356
|
+
:text => short_status,
|
|
357
|
+
:in_reply_to => nil
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
RestClientWrapper.expects(:post) \
|
|
362
|
+
.with("#{@base_url}/statuses/update.json", {:status => short_status}) \
|
|
363
|
+
.returns(status_records[0].to_json)
|
|
364
|
+
@url_shortener.expects(:shorten).with(long_urls.first).returns(short_url)
|
|
365
|
+
@io.expects(:show_status_preview).with(short_status)
|
|
366
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
|
367
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
|
368
|
+
@io.expects(:show_record).with(gen_records[0])
|
|
369
|
+
@client.update(long_status)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
context "in erroneous situations" do
|
|
373
|
+
setup do
|
|
374
|
+
@url = "http://www.w3.org/TR/1999/REC-xpath-19991116"
|
|
375
|
+
@status = "skimming through #{@url}"
|
|
376
|
+
@status_records, @gen_records = create_test_statuses(
|
|
377
|
+
{ :user => @username,
|
|
378
|
+
:status => {
|
|
379
|
+
:created_at => Time.at(1).to_s,
|
|
380
|
+
:text => @status,
|
|
381
|
+
:in_reply_to => nil
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
should "skip shortening URLs if required libraries are not found" do
|
|
388
|
+
RestClientWrapper.expects(:post) \
|
|
389
|
+
.with("#{@base_url}/statuses/update.json", {:status => @status}) \
|
|
390
|
+
.returns(@status_records[0].to_json)
|
|
391
|
+
@url_shortener.expects(:shorten).with(@url).raises(LoadError, "gem not found")
|
|
392
|
+
@io.expects(:warn)
|
|
393
|
+
@io.expects(:show_status_preview).with(@status)
|
|
394
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
|
395
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
|
396
|
+
@io.expects(:show_record).with(@gen_records[0])
|
|
397
|
+
@client.update(@status)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
should "skip shortening URLs upon connection error to the URL shortening service" do
|
|
401
|
+
RestClientWrapper.expects(:post) \
|
|
402
|
+
.with("#{@base_url}/statuses/update.json", {:status => @status}) \
|
|
403
|
+
.returns(@status_records[0].to_json)
|
|
404
|
+
@url_shortener.expects(:shorten).with(@url).raises(ClientError, "connection error")
|
|
405
|
+
@io.expects(:warn)
|
|
406
|
+
@io.expects(:show_status_preview).with(@status)
|
|
407
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
|
408
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
|
409
|
+
@io.expects(:show_record).with(@gen_records[0])
|
|
410
|
+
@client.update(@status)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
346
413
|
end
|
|
347
414
|
end
|
|
348
415
|
|
data/test/io_test.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
module Tweetwine
|
|
4
4
|
|
|
@@ -9,7 +9,6 @@ class IOTest < Test::Unit::TestCase
|
|
|
9
9
|
@output = mock()
|
|
10
10
|
@io = IO.new({ :input => @input, :output => @output })
|
|
11
11
|
end
|
|
12
|
-
|
|
13
12
|
should "output prompt and return input as trimmed" do
|
|
14
13
|
@output.expects(:print).with("The answer: ")
|
|
15
14
|
@input.expects(:gets).returns(" 42 ")
|
|
@@ -63,7 +62,7 @@ fooman
|
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
should "output a record as status info when status is given, without in-reply info" do
|
|
66
|
-
status = "Hi, @barman! Lulz woo!"
|
|
65
|
+
status = "Hi, @barman! Lulz woo! #hellos"
|
|
67
66
|
record = {
|
|
68
67
|
:user => "fooman",
|
|
69
68
|
:status => {
|
|
@@ -153,14 +152,14 @@ Wondering the meaning of life.
|
|
|
153
152
|
:user => "barman",
|
|
154
153
|
:status => {
|
|
155
154
|
:created_at => Time.at(1),
|
|
156
|
-
:text => "Hi, @fooman! How are you doing?",
|
|
155
|
+
:text => "Hi, @fooman! How are you doing? #hellos",
|
|
157
156
|
:in_reply_to => "fooman"
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
159
|
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
|
161
160
|
@output.expects(:puts).with(<<-END
|
|
162
161
|
\033[32mbarman\033[0m, in reply to \033[32mfooman\033[0m, 2 secs ago:
|
|
163
|
-
Hi, \033[33m@fooman\033[0m! How are you doing?
|
|
162
|
+
Hi, \033[33m@fooman\033[0m! How are you doing? \033[35m#hellos\033[0m
|
|
164
163
|
|
|
165
164
|
END
|
|
166
165
|
)
|
|
@@ -178,7 +177,7 @@ Hi, \033[33m@fooman\033[0m! How are you doing?
|
|
|
178
177
|
@io.show_status_preview(status)
|
|
179
178
|
end
|
|
180
179
|
|
|
181
|
-
should "highlight HTTP and HTTPS
|
|
180
|
+
should "highlight HTTP and HTTPS URLs in a status" do
|
|
182
181
|
record = {
|
|
183
182
|
:user => "barman",
|
|
184
183
|
:status => {
|
|
@@ -196,7 +195,25 @@ Three links: \033[36mhttp://bit.ly/18rU_Vx\033[0m \033[36mhttp://is.gd/1qLk3\033
|
|
|
196
195
|
@io.show_record(record)
|
|
197
196
|
end
|
|
198
197
|
|
|
199
|
-
should "highlight
|
|
198
|
+
should "highlight HTTP and HTTPS URLs in a status, even if duplicates" do
|
|
199
|
+
record = {
|
|
200
|
+
:user => "barman",
|
|
201
|
+
:status => {
|
|
202
|
+
:created_at => Time.at(1),
|
|
203
|
+
:text => "Duplicate links: http://is.gd/1qLk3 and http://is.gd/1qLk3",
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
|
207
|
+
@output.expects(:puts).with(<<-END
|
|
208
|
+
\033[32mbarman\033[0m, 2 secs ago:
|
|
209
|
+
Duplicate links: \033[36mhttp://is.gd/1qLk3\033[0m and \033[36mhttp://is.gd/1qLk3\033[0m
|
|
210
|
+
|
|
211
|
+
END
|
|
212
|
+
)
|
|
213
|
+
@io.show_record(record)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
should "highlight usernames in a status" do
|
|
200
217
|
record = {
|
|
201
218
|
:user => "barman",
|
|
202
219
|
:status => {
|
|
@@ -215,16 +232,35 @@ I salute you \033[33m@fooman\033[0m, \033[33m@barbaz\033[0m, and \033[33m@spoonm
|
|
|
215
232
|
end
|
|
216
233
|
end
|
|
217
234
|
|
|
218
|
-
context "
|
|
219
|
-
should "match a proper
|
|
220
|
-
assert_full_match IO::
|
|
221
|
-
assert_full_match IO::
|
|
235
|
+
context "Username regex" do
|
|
236
|
+
should "match a proper username reference" do
|
|
237
|
+
assert_full_match IO::USERNAME_REGEX, "@nick"
|
|
238
|
+
assert_full_match IO::USERNAME_REGEX, "@nick_man"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
should "not match an inproper username reference" do
|
|
242
|
+
assert_no_full_match IO::USERNAME_REGEX, "@"
|
|
243
|
+
assert_no_full_match IO::USERNAME_REGEX, "nick"
|
|
244
|
+
assert_no_full_match IO::USERNAME_REGEX, "@nick-man"
|
|
245
|
+
assert_no_full_match IO::USERNAME_REGEX, " @nick"
|
|
246
|
+
assert_no_full_match IO::USERNAME_REGEX, "@nick "
|
|
247
|
+
assert_no_full_match IO::USERNAME_REGEX, " @nick "
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
context "Hashtag regex" do
|
|
252
|
+
should "match a proper hashtag reference" do
|
|
253
|
+
assert_full_match IO::HASHTAG_REGEX, "#mayhem"
|
|
254
|
+
assert_full_match IO::HASHTAG_REGEX, "#friday_mayhem"
|
|
255
|
+
assert_full_match IO::HASHTAG_REGEX, "#friday-mayhem"
|
|
222
256
|
end
|
|
223
257
|
|
|
224
|
-
should "not match an inproper
|
|
225
|
-
assert_no_full_match IO::
|
|
226
|
-
assert_no_full_match IO::
|
|
227
|
-
assert_no_full_match IO::
|
|
258
|
+
should "not match an inproper hashtag reference" do
|
|
259
|
+
assert_no_full_match IO::USERNAME_REGEX, "#"
|
|
260
|
+
assert_no_full_match IO::USERNAME_REGEX, "mayhem"
|
|
261
|
+
assert_no_full_match IO::USERNAME_REGEX, " #mayhem"
|
|
262
|
+
assert_no_full_match IO::USERNAME_REGEX, "#mayhem "
|
|
263
|
+
assert_no_full_match IO::USERNAME_REGEX, " #mayhem "
|
|
228
264
|
end
|
|
229
265
|
end
|
|
230
266
|
end
|
data/test/options_test.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
module Tweetwine
|
|
4
4
|
|
|
@@ -9,9 +9,33 @@ class OptionsTest < Test::Unit::TestCase
|
|
|
9
9
|
assert_equal nil, Options.new({})[:a]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
context "for requiring options" do
|
|
13
|
+
should "raise ArgumentError if there's no value for the required option (a value that is nil)" do
|
|
14
|
+
assert_equal "alpha", Options.new({:a => "alpha"}).require(:a)
|
|
15
|
+
assert_raise(ArgumentError) { Options.new({}).require(:a) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
should "indicate the required option upon failure" do
|
|
19
|
+
error = nil
|
|
20
|
+
begin
|
|
21
|
+
Options.new({}).require(:a)
|
|
22
|
+
flunk "should have raised ArgumentError"
|
|
23
|
+
rescue ArgumentError => e
|
|
24
|
+
error = e
|
|
25
|
+
end
|
|
26
|
+
assert_equal("Option a is required", e.to_s)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
should "indicate the required option upon failure, with optional error source" do
|
|
30
|
+
error = nil
|
|
31
|
+
begin
|
|
32
|
+
Options.new({}, "test options").require(:a)
|
|
33
|
+
flunk "should have raised ArgumentError"
|
|
34
|
+
rescue ArgumentError => e
|
|
35
|
+
error = e
|
|
36
|
+
end
|
|
37
|
+
assert_equal("Option a is required for test options", e.to_s)
|
|
38
|
+
end
|
|
15
39
|
end
|
|
16
40
|
end
|
|
17
41
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
require "rest_client"
|
|
3
3
|
|
|
4
4
|
module Tweetwine
|
|
@@ -9,14 +9,21 @@ class RestClientWrapperTest < Test::Unit::TestCase
|
|
|
9
9
|
RestClient.expects(:get) \
|
|
10
10
|
.with("https://secret:agent@hushhush.net") \
|
|
11
11
|
.raises(RestClient::Unauthorized)
|
|
12
|
-
|
|
12
|
+
assert_raise(ClientError) { RestClientWrapper.get("https://secret:agent@hushhush.net") }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
should "raise ClientError when connection cannot be established" do
|
|
16
16
|
RestClient.expects(:get) \
|
|
17
17
|
.with("http://www.invalid.net") \
|
|
18
18
|
.raises(Errno::ECONNRESET)
|
|
19
|
-
|
|
19
|
+
assert_raise(ClientError) { RestClientWrapper.get("http://www.invalid.net") }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
should "raise ClientError when host cannot be resolved" do
|
|
23
|
+
RestClient.expects(:get) \
|
|
24
|
+
.with("http://unknown.net") \
|
|
25
|
+
.raises(SocketError)
|
|
26
|
+
assert_raise(ClientError) { RestClientWrapper.get("http://unknown.net") }
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
end
|
data/test/startup_config_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/url_shortener_test.rb
CHANGED
|
@@ -1,66 +1,126 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
|
|
3
3
|
module Tweetwine
|
|
4
4
|
|
|
5
5
|
class UrlShortenerTest < Test::Unit::TestCase
|
|
6
|
-
context "
|
|
7
|
-
should "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
context "Upon initialization, an URL shortener" do
|
|
7
|
+
should "raise exception if service URL is not given" do
|
|
8
|
+
assert_raise(ArgumentError) do
|
|
9
|
+
UrlShortener.new({
|
|
10
|
+
:service_url => nil,
|
|
11
|
+
:url_param_name => "url",
|
|
12
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
13
|
+
})
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
should "raise exception if URL parameter name is not given" do
|
|
18
|
+
assert_raise(ArgumentError) do
|
|
19
|
+
UrlShortener.new({
|
|
20
|
+
:service_url => "http://shorten.it/create",
|
|
21
|
+
:url_param_name => nil,
|
|
22
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
23
|
+
})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
should "raise exception if XPath selector is not given" do
|
|
28
|
+
assert_raise(ArgumentError) do
|
|
29
|
+
UrlShortener.new({
|
|
30
|
+
:service_url => "http://shorten.it/create",
|
|
31
|
+
:url_param_name => "url",
|
|
32
|
+
:xpath_selector => nil
|
|
33
|
+
})
|
|
34
|
+
end
|
|
17
35
|
end
|
|
18
36
|
|
|
19
|
-
should "
|
|
37
|
+
should "fallback to use GET method if not given explicitly" do
|
|
20
38
|
url_shortener = UrlShortener.new({
|
|
21
|
-
:method => "get",
|
|
22
39
|
:service_url => "http://shorten.it/create",
|
|
23
40
|
:url_param_name => "url",
|
|
24
|
-
:extra_params => {
|
|
25
|
-
:token => "xyz"
|
|
26
|
-
},
|
|
27
41
|
:xpath_selector => "//input[@id='short_url']/@value"
|
|
28
42
|
})
|
|
29
|
-
|
|
30
|
-
.with("http://shorten.it/create?token=xyz&url=http://www.ruby-doc.org/core/")
|
|
31
|
-
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
|
43
|
+
assert_equal(:get, url_shortener.instance_variable_get(:@method))
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
35
|
-
context "
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
context "At runtime, an URL shortener" do
|
|
48
|
+
context "An URL shortener configured for HTTP GET" do
|
|
49
|
+
should "use parameters as URL query parameters, with just the URL parameter" do
|
|
50
|
+
url_shortener = UrlShortener.new({
|
|
51
|
+
:method => "get",
|
|
52
|
+
:service_url => "http://shorten.it/create",
|
|
53
|
+
:url_param_name => "url",
|
|
54
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
55
|
+
})
|
|
56
|
+
RestClientWrapper.expects(:get) \
|
|
57
|
+
.with("http://shorten.it/create?url=http://www.ruby-doc.org/core/")
|
|
58
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
should "use parameters as URL query parameters, with additional extra parameters" do
|
|
62
|
+
url_shortener = UrlShortener.new({
|
|
63
|
+
:method => "get",
|
|
64
|
+
:service_url => "http://shorten.it/create",
|
|
65
|
+
:url_param_name => "url",
|
|
66
|
+
:extra_params => {
|
|
67
|
+
:token => "xyz"
|
|
68
|
+
},
|
|
69
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
70
|
+
})
|
|
71
|
+
RestClientWrapper.expects(:get) \
|
|
72
|
+
.with("http://shorten.it/create?token=xyz&url=http://www.ruby-doc.org/core/")
|
|
73
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
|
74
|
+
end
|
|
46
75
|
end
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
:
|
|
55
|
-
}
|
|
56
|
-
:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
context "An URL shortener configured for HTTP POST" do
|
|
78
|
+
should "use parameters as payload, with just the URL parameter" do
|
|
79
|
+
url_shortener = UrlShortener.new({
|
|
80
|
+
:method => "post",
|
|
81
|
+
:service_url => "http://shorten.it/create",
|
|
82
|
+
:url_param_name => "url",
|
|
83
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
84
|
+
})
|
|
85
|
+
RestClientWrapper.expects(:post) \
|
|
86
|
+
.with("http://shorten.it/create", {:url => "http://www.ruby-doc.org/core/"})
|
|
87
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
should "use parameters as payload, with additional extra parameters" do
|
|
91
|
+
url_shortener = UrlShortener.new({
|
|
92
|
+
:method => "post",
|
|
93
|
+
:service_url => "http://shorten.it/create",
|
|
94
|
+
:url_param_name => "url",
|
|
95
|
+
:extra_params => {
|
|
96
|
+
:token => "xyz"
|
|
97
|
+
},
|
|
98
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
99
|
+
})
|
|
100
|
+
RestClientWrapper.expects(:post) \
|
|
101
|
+
.with("http://shorten.it/create", {
|
|
102
|
+
:token => "xyz",
|
|
103
|
+
:url => "http://www.ruby-doc.org/core/"
|
|
104
|
+
})
|
|
105
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "An URL shortener in erroenous situations" do
|
|
110
|
+
should "raise ClientError upon connection error" do
|
|
111
|
+
url_shortener = UrlShortener.new({
|
|
112
|
+
:method => "post",
|
|
113
|
+
:service_url => "http://shorten.it/create",
|
|
114
|
+
:url_param_name => "url",
|
|
115
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
|
116
|
+
})
|
|
117
|
+
RestClientWrapper.expects(:post) \
|
|
118
|
+
.with("http://shorten.it/create", {
|
|
119
|
+
:url => "http://www.ruby-doc.org/core/"
|
|
120
|
+
}) \
|
|
121
|
+
.raises(ClientError, "connection error")
|
|
122
|
+
assert_raise(ClientError) { url_shortener.shorten("http://www.ruby-doc.org/core/") }
|
|
123
|
+
end
|
|
64
124
|
end
|
|
65
125
|
end
|
|
66
126
|
end
|
data/test/util_test.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "test_helper"
|
|
2
2
|
require "time"
|
|
3
3
|
|
|
4
4
|
module Tweetwine
|
|
@@ -50,7 +50,7 @@ class UtilTest < Test::Unit::TestCase
|
|
|
50
50
|
assert_equal 6, Util.parse_int_gt("6", 8, 4, "ethical working hours per day")
|
|
51
51
|
assert_equal 8, Util.parse_int_gt(nil, 8, 4, "ethical working hours per day")
|
|
52
52
|
assert_equal 8, Util.parse_int_gt(false, 8, 4, "ethical working hours per day")
|
|
53
|
-
|
|
53
|
+
assert_raise(ArgumentError) { Util.parse_int_gt(3, 8, 4, "ethical working hours per day") }
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tuomas-tweetwine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tuomas Kareinen
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-
|
|
12
|
+
date: 2009-09-03 00:00:00 -07:00
|
|
13
13
|
default_executable: tweetwine
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -29,10 +29,12 @@ executables:
|
|
|
29
29
|
extensions: []
|
|
30
30
|
|
|
31
31
|
extra_rdoc_files:
|
|
32
|
+
- MIT-LICENSE.txt
|
|
32
33
|
- CHANGELOG.rdoc
|
|
33
34
|
- README.rdoc
|
|
34
35
|
files:
|
|
35
36
|
- Rakefile
|
|
37
|
+
- MIT-LICENSE.txt
|
|
36
38
|
- CHANGELOG.rdoc
|
|
37
39
|
- README.rdoc
|
|
38
40
|
- bin/tweetwine
|
|
@@ -57,11 +59,10 @@ files:
|
|
|
57
59
|
- test/util_test.rb
|
|
58
60
|
has_rdoc: false
|
|
59
61
|
homepage: http://github.com/tuomas/tweetwine
|
|
60
|
-
licenses:
|
|
61
62
|
post_install_message:
|
|
62
63
|
rdoc_options:
|
|
63
64
|
- --title
|
|
64
|
-
-
|
|
65
|
+
- tweetwine 0.2.2
|
|
65
66
|
- --main
|
|
66
67
|
- README.rdoc
|
|
67
68
|
- --exclude
|
|
@@ -84,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
84
85
|
requirements: []
|
|
85
86
|
|
|
86
87
|
rubyforge_project:
|
|
87
|
-
rubygems_version: 1.
|
|
88
|
+
rubygems_version: 1.2.0
|
|
88
89
|
signing_key:
|
|
89
90
|
specification_version: 3
|
|
90
91
|
summary: A simple Twitter agent for command line use
|