twat 0.6.3 → 0.9.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/.rvmrc +1 -1
- data/.travis.yml +6 -0
- data/Gemfile +1 -4
- data/Gemfile.lock +43 -23
- data/Gemfile.travis +12 -0
- data/Rakefile +9 -0
- data/TODO +4 -1
- data/lib/twat.rb +30 -26
- data/lib/twat/argparse.rb +36 -79
- data/lib/twat/config.rb +22 -34
- data/lib/twat/endpoint.rb +11 -5
- data/lib/twat/endpoints/base.rb +39 -0
- data/lib/twat/endpoints/identica.rb +5 -1
- data/lib/twat/endpoints/twitter.rb +5 -1
- data/lib/twat/exceptions.rb +93 -46
- data/lib/twat/follow_mixin.rb +134 -0
- data/lib/twat/options.rb +28 -12
- data/lib/twat/subcommand.rb +34 -0
- data/lib/twat/subcommands/add.rb +17 -0
- data/lib/twat/subcommands/base.rb +122 -0
- data/lib/twat/subcommands/config.rb +15 -0
- data/lib/twat/subcommands/delete.rb +24 -0
- data/lib/twat/subcommands/finger.rb +23 -0
- data/lib/twat/subcommands/follow.rb +18 -0
- data/lib/twat/subcommands/follow_tag.rb +19 -0
- data/lib/twat/subcommands/list.rb +17 -0
- data/lib/twat/subcommands/mentions.rb +20 -0
- data/lib/twat/subcommands/set.rb +19 -0
- data/lib/twat/subcommands/track.rb +33 -0
- data/lib/twat/subcommands/update.rb +21 -0
- data/lib/twat/subcommands/update_config.rb +16 -0
- data/lib/twat/subcommands/version.rb +14 -0
- data/lib/twat/twatopt.rb +0 -0
- data/lib/twat/tweetstack.rb +38 -0
- data/lib/twat/version.rb +7 -0
- data/man/twat.1 +8 -5
- data/spec/argparse_spec.rb +48 -0
- data/spec/helpers/environment.rb +47 -0
- data/spec/helpers/fileutils.rb +18 -0
- data/spec/helpers/fixtures/core.rb +30 -0
- data/spec/helpers/fixtures/migrations.rb +13 -0
- data/spec/helpers/oauth.rb +15 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/subcommands/add_spec.rb +61 -0
- data/spec/subcommands/config_spec.rb +13 -0
- data/spec/subcommands/delete_spec.rb +12 -0
- data/spec/subcommands/finger_spec.rb +41 -0
- data/spec/subcommands/follow_spec.rb +12 -0
- data/spec/subcommands/follow_tag_spec.rb +52 -0
- data/spec/subcommands/list_spec.rb +8 -0
- data/spec/subcommands/mentions_spec.rb +24 -0
- data/spec/subcommands/set_spec.rb +126 -0
- data/spec/subcommands/track_spec.rb +23 -0
- data/spec/subcommands/update_config_spec.rb +23 -0
- data/spec/subcommands/update_spec.rb +12 -0
- data/spec/subcommands/version_spec.rb +12 -0
- data/spec/twat_cli_spec.rb +102 -0
- data/spec/twat_spec.rb +12 -0
- data/twat.gemspec +6 -2
- metadata +135 -22
- data/lib/twat/actions.rb +0 -85
- data/lib/twat/actions/add.rb +0 -36
- data/lib/twat/actions/delete.rb +0 -14
- data/lib/twat/actions/follow.rb +0 -148
- data/lib/twat/actions/follow_user.rb +0 -11
- data/lib/twat/actions/setoption.rb +0 -14
- data/lib/twat/actions/show.rb +0 -12
- data/lib/twat/actions/tweet.rb +0 -14
- data/lib/twat/actions/updateconfig.rb +0 -9
- data/lib/twat/actions/user_feed.rb +0 -17
- data/lib/twat/actions/version.rb +0 -9
@@ -0,0 +1,34 @@
|
|
1
|
+
%w(base add config delete follow_tag follow list mentions set update update_config finger version track).each do |filename|
|
2
|
+
require File.join(File.expand_path("../subcommands/#{filename}", __FILE__))
|
3
|
+
end
|
4
|
+
|
5
|
+
module Twat
|
6
|
+
class Subcommand
|
7
|
+
|
8
|
+
def self.run
|
9
|
+
# This is evilbadscary, but seems like the best approach
|
10
|
+
$args = ::Twat::Args.new
|
11
|
+
# First, without commands dump user into follow mode
|
12
|
+
if ARGV.empty?
|
13
|
+
ARGV.insert(0, "follow_tag")
|
14
|
+
|
15
|
+
# Failing that, in the case of invalid commands, assume they want to
|
16
|
+
# tweet something.
|
17
|
+
# TODO, fuzzy match against the contents of COMMANDS and have a sook if
|
18
|
+
# they're close to an actual command
|
19
|
+
# FIXME Potentially the absense of a space would suggest that it's just a
|
20
|
+
# fucked effort at typing
|
21
|
+
elsif !Subcommands::COMMANDS.include?(ARGV[0])
|
22
|
+
ARGV.insert(0, "update")
|
23
|
+
end
|
24
|
+
|
25
|
+
# There is really no reason why this needs to be explicitly mention
|
26
|
+
# in this layout, we could just as easily look for a class in
|
27
|
+
# Subcommands:: that matches by name, however this avoids some ugly
|
28
|
+
# metaprogramming with minimal overhead, and also leaves the door
|
29
|
+
# open for aliasing etc
|
30
|
+
Subcommands::COMMANDS[ARGV[0]].new(ARGV[1..-1]).run!
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Add < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
needs_arguments(1)
|
6
|
+
|
7
|
+
endpoint = ::Twat::Endpoint.new(args.endpoint)
|
8
|
+
endpoint.authorize_account(@argv[0])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.usage
|
12
|
+
"Usage: twat add accountname"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
COMMANDS['add'] = Add
|
17
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module ::Twitter
|
2
|
+
class Status
|
3
|
+
def as_user
|
4
|
+
begin
|
5
|
+
user.screen_name
|
6
|
+
rescue NoMethodError
|
7
|
+
from_user
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Twat::Subcommands
|
14
|
+
COMMANDS = {}
|
15
|
+
class Base
|
16
|
+
|
17
|
+
include ::Twat::Exceptions
|
18
|
+
|
19
|
+
attr_reader :opts
|
20
|
+
|
21
|
+
def initialize(argv)
|
22
|
+
@argv=argv
|
23
|
+
end
|
24
|
+
|
25
|
+
def run!
|
26
|
+
with_handled_exceptions(args) do
|
27
|
+
run
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def needs_arguments(n)
|
32
|
+
unless @argv.length == n
|
33
|
+
usage_and_exit!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def needs_at_least(n)
|
38
|
+
unless @argv.length >= n
|
39
|
+
usage_and_exit!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def usage_and_exit!
|
44
|
+
usage
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
def auth!
|
49
|
+
Twitter.configure do |twit|
|
50
|
+
config.account.each do |key, value|
|
51
|
+
twit.send("#{key}=", value)
|
52
|
+
end
|
53
|
+
config.endpoint.consumer_info.each do |key, value|
|
54
|
+
twit.send("#{key}=", value)
|
55
|
+
end
|
56
|
+
twit.endpoint = config.endpoint.url
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def beep
|
61
|
+
print "\a"
|
62
|
+
end
|
63
|
+
|
64
|
+
def pad(n)
|
65
|
+
"%02d" % n
|
66
|
+
end
|
67
|
+
|
68
|
+
# Format a tweet all pretty like
|
69
|
+
def format(twt, idx = nil)
|
70
|
+
idx = pad(idx) if idx
|
71
|
+
text = deentitize(twt.text)
|
72
|
+
if config.colors?
|
73
|
+
buf = idx ? "#{idx.cyan}:" : ""
|
74
|
+
if twt.as_user == config.account_name.to_s
|
75
|
+
buf += "#{twt.as_user.bold.blue}: #{text}"
|
76
|
+
elsif text.mentions?(config.account_name)
|
77
|
+
buf += "#{twt.as_user.bold.red}: #{text}"
|
78
|
+
else
|
79
|
+
buf += "#{twt.as_user.bold.cyan}: #{text}"
|
80
|
+
end
|
81
|
+
buf.colorise!
|
82
|
+
else
|
83
|
+
buf = idx ? "#{idx}: " : ""
|
84
|
+
buf += "#{twt.as_user}: #{text}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def deentitize(text)
|
89
|
+
{"<" => "<", ">" => ">", "&" => "&", """ => '"' }.each do |k,v|
|
90
|
+
text.gsub!(k, v)
|
91
|
+
end
|
92
|
+
text
|
93
|
+
end
|
94
|
+
|
95
|
+
def config
|
96
|
+
@config ||= ::Twat::Config.new
|
97
|
+
end
|
98
|
+
|
99
|
+
def opts
|
100
|
+
@opts ||= ::Twat::Options.new
|
101
|
+
end
|
102
|
+
|
103
|
+
def args
|
104
|
+
# This is dangerous, but realistically I don't see how else to parse the
|
105
|
+
# args before creating an instance of a subclass
|
106
|
+
@args ||= $args
|
107
|
+
end
|
108
|
+
|
109
|
+
def enable_readline!
|
110
|
+
@reader = ReadlineNG::Reader.new
|
111
|
+
end
|
112
|
+
|
113
|
+
def reader
|
114
|
+
@reader
|
115
|
+
end
|
116
|
+
|
117
|
+
def usage
|
118
|
+
puts self.class.usage
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Delete < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
needs_arguments(1)
|
6
|
+
if config.accounts.delete(@argv[0])
|
7
|
+
config.save!
|
8
|
+
puts "Successfully deleted"
|
9
|
+
else
|
10
|
+
puts "No such account"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.usage
|
15
|
+
"Usage: twat delete ACCOUNTNAME"
|
16
|
+
end
|
17
|
+
|
18
|
+
def updateconfig
|
19
|
+
config.update!
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
COMMANDS['delete'] = Delete
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Finger < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
needs_at_least(1)
|
6
|
+
auth!
|
7
|
+
|
8
|
+
begin
|
9
|
+
Twitter.user_timeline(@argv[0], :count => (@argv[1] || 1).to_i).each do |tweet|
|
10
|
+
puts format(tweet)
|
11
|
+
end
|
12
|
+
# rescue Twitter::NotFound
|
13
|
+
# puts "#{@argv[0].bold.red} doesn't appear to be a valid user"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.usage
|
18
|
+
"Usage: twat finger USERNAME [count]"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
COMMANDS['finger'] = Finger
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class FollowTag < Base
|
3
|
+
include FollowMixin
|
4
|
+
|
5
|
+
def self.usage
|
6
|
+
["Usage: twat",
|
7
|
+
"Usage: twat follow_stream #hashtag"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_tweets(opts)
|
11
|
+
unless @argv.empty?
|
12
|
+
ret = Twitter.search(@argv.join(" "), opts)
|
13
|
+
else
|
14
|
+
ret = Twitter.home_timeline(opts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
COMMANDS['follow_tag'] = FollowTag
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class List < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
auth!
|
6
|
+
Twitter.home_timeline(:count => @argv[0] || 5).reverse.each do |tweet|
|
7
|
+
format(tweet)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.usage
|
12
|
+
"Usage: twat list [count]"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
COMMANDS['list'] = List
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Mentions < Base
|
3
|
+
include FollowMixin
|
4
|
+
|
5
|
+
def run
|
6
|
+
needs_arguments(0)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.usage
|
11
|
+
"Usage: twat mentions"
|
12
|
+
end
|
13
|
+
|
14
|
+
def new_tweets(opts)
|
15
|
+
ret = Twitter.mentions(opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
COMMANDS['mentions'] = Mentions
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Set < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
needs_arguments(2)
|
6
|
+
k, v = @argv[0..1]
|
7
|
+
opts.send(:"#{k}=", v)
|
8
|
+
|
9
|
+
puts "Successfully set #{k} as #{v}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.usage
|
13
|
+
"Usage: twat set option value"
|
14
|
+
# TODO Dump available options
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
COMMANDS['set'] = Set
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
module Twat::Subcommands
|
3
|
+
class Track < Base
|
4
|
+
include FollowMixin
|
5
|
+
|
6
|
+
# TODO configless mixin
|
7
|
+
def auth! # Disable authentication
|
8
|
+
end
|
9
|
+
|
10
|
+
def config # Stub out a config object
|
11
|
+
@_config ||= if (c = super).exists?
|
12
|
+
c
|
13
|
+
else
|
14
|
+
OpenStruct.new({
|
15
|
+
:polling_interval => 60,
|
16
|
+
:colors? => true,
|
17
|
+
:beep? => false,
|
18
|
+
:account_name => "IMPOSSIBLEUSERNAME!"
|
19
|
+
})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.usage
|
24
|
+
["Usage: twat track username"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def new_tweets(opts)
|
28
|
+
Twitter.user_timeline(@argv[0], opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
COMMANDS['track'] = Track
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Twat::Subcommands
|
2
|
+
class Update < Base
|
3
|
+
|
4
|
+
def run
|
5
|
+
msg = @argv.join(" ")
|
6
|
+
raise TweetTooLong if msg.length > 140
|
7
|
+
usage_and_exit! if msg == ""
|
8
|
+
|
9
|
+
auth!
|
10
|
+
|
11
|
+
Twitter.update(msg)
|
12
|
+
#puts msg
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.usage
|
16
|
+
"Usage: twat update tweet goes here"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
COMMANDS['update'] = Update
|
21
|
+
end
|
data/lib/twat/twatopt.rb
ADDED
File without changes
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Twat
|
2
|
+
class TweetStack
|
3
|
+
# A circularly linked list representing all of the tweets printed thus far,
|
4
|
+
# for the purposes of retrieving them after being printed
|
5
|
+
def initialize
|
6
|
+
@stack = {}
|
7
|
+
@_next = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def [] k
|
11
|
+
@stack[k]
|
12
|
+
end
|
13
|
+
|
14
|
+
def << v
|
15
|
+
@stack[nxt] = v
|
16
|
+
end
|
17
|
+
|
18
|
+
def include? k
|
19
|
+
@stack.keys.include?(k)
|
20
|
+
end
|
21
|
+
|
22
|
+
def last
|
23
|
+
# I see the irony
|
24
|
+
@_next
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def nxt
|
30
|
+
if @_next == 99
|
31
|
+
@_next = 1
|
32
|
+
else
|
33
|
+
@_next += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|