twat 0.6.3 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/.rvmrc +1 -1
  2. data/.travis.yml +6 -0
  3. data/Gemfile +1 -4
  4. data/Gemfile.lock +43 -23
  5. data/Gemfile.travis +12 -0
  6. data/Rakefile +9 -0
  7. data/TODO +4 -1
  8. data/lib/twat.rb +30 -26
  9. data/lib/twat/argparse.rb +36 -79
  10. data/lib/twat/config.rb +22 -34
  11. data/lib/twat/endpoint.rb +11 -5
  12. data/lib/twat/endpoints/base.rb +39 -0
  13. data/lib/twat/endpoints/identica.rb +5 -1
  14. data/lib/twat/endpoints/twitter.rb +5 -1
  15. data/lib/twat/exceptions.rb +93 -46
  16. data/lib/twat/follow_mixin.rb +134 -0
  17. data/lib/twat/options.rb +28 -12
  18. data/lib/twat/subcommand.rb +34 -0
  19. data/lib/twat/subcommands/add.rb +17 -0
  20. data/lib/twat/subcommands/base.rb +122 -0
  21. data/lib/twat/subcommands/config.rb +15 -0
  22. data/lib/twat/subcommands/delete.rb +24 -0
  23. data/lib/twat/subcommands/finger.rb +23 -0
  24. data/lib/twat/subcommands/follow.rb +18 -0
  25. data/lib/twat/subcommands/follow_tag.rb +19 -0
  26. data/lib/twat/subcommands/list.rb +17 -0
  27. data/lib/twat/subcommands/mentions.rb +20 -0
  28. data/lib/twat/subcommands/set.rb +19 -0
  29. data/lib/twat/subcommands/track.rb +33 -0
  30. data/lib/twat/subcommands/update.rb +21 -0
  31. data/lib/twat/subcommands/update_config.rb +16 -0
  32. data/lib/twat/subcommands/version.rb +14 -0
  33. data/lib/twat/twatopt.rb +0 -0
  34. data/lib/twat/tweetstack.rb +38 -0
  35. data/lib/twat/version.rb +7 -0
  36. data/man/twat.1 +8 -5
  37. data/spec/argparse_spec.rb +48 -0
  38. data/spec/helpers/environment.rb +47 -0
  39. data/spec/helpers/fileutils.rb +18 -0
  40. data/spec/helpers/fixtures/core.rb +30 -0
  41. data/spec/helpers/fixtures/migrations.rb +13 -0
  42. data/spec/helpers/oauth.rb +15 -0
  43. data/spec/spec_helper.rb +13 -0
  44. data/spec/subcommands/add_spec.rb +61 -0
  45. data/spec/subcommands/config_spec.rb +13 -0
  46. data/spec/subcommands/delete_spec.rb +12 -0
  47. data/spec/subcommands/finger_spec.rb +41 -0
  48. data/spec/subcommands/follow_spec.rb +12 -0
  49. data/spec/subcommands/follow_tag_spec.rb +52 -0
  50. data/spec/subcommands/list_spec.rb +8 -0
  51. data/spec/subcommands/mentions_spec.rb +24 -0
  52. data/spec/subcommands/set_spec.rb +126 -0
  53. data/spec/subcommands/track_spec.rb +23 -0
  54. data/spec/subcommands/update_config_spec.rb +23 -0
  55. data/spec/subcommands/update_spec.rb +12 -0
  56. data/spec/subcommands/version_spec.rb +12 -0
  57. data/spec/twat_cli_spec.rb +102 -0
  58. data/spec/twat_spec.rb +12 -0
  59. data/twat.gemspec +6 -2
  60. metadata +135 -22
  61. data/lib/twat/actions.rb +0 -85
  62. data/lib/twat/actions/add.rb +0 -36
  63. data/lib/twat/actions/delete.rb +0 -14
  64. data/lib/twat/actions/follow.rb +0 -148
  65. data/lib/twat/actions/follow_user.rb +0 -11
  66. data/lib/twat/actions/setoption.rb +0 -14
  67. data/lib/twat/actions/show.rb +0 -12
  68. data/lib/twat/actions/tweet.rb +0 -14
  69. data/lib/twat/actions/updateconfig.rb +0 -9
  70. data/lib/twat/actions/user_feed.rb +0 -17
  71. 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
+ {"&lt;" => "<", "&gt;" => ">", "&amp;" => "&", "&quot;" => '"' }.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,15 @@
1
+ module Twat::Subcommands
2
+ class Config < Base
3
+
4
+ def run
5
+ raise AlreadyConfigured if config.exists?
6
+ config.create!
7
+ end
8
+
9
+ def self.usage
10
+ "Usage: twat config"
11
+ end
12
+
13
+ end
14
+ COMMANDS['config'] = Config
15
+ 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,18 @@
1
+ module Twat::Subcommands
2
+ class Follow < Base
3
+
4
+ def run
5
+ needs_arguments(1)
6
+ auth!
7
+
8
+ Twitter.follow(@argv[0])
9
+ end
10
+
11
+ def self.usage
12
+ "Usage: twat follow USERNAME"
13
+ end
14
+
15
+ end
16
+ COMMANDS['follow'] = Follow
17
+ end
18
+
@@ -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
@@ -0,0 +1,16 @@
1
+ module Twat::Subcommands
2
+ class UpdateConfig < Base
3
+
4
+ def run
5
+
6
+ config.update_config
7
+
8
+ end
9
+
10
+ def self.usage
11
+ "Usage: twat update_config"
12
+ end
13
+
14
+ end
15
+ COMMANDS['update_config'] = UpdateConfig
16
+ end
@@ -0,0 +1,14 @@
1
+ module Twat::Subcommands
2
+ class Version < Base
3
+
4
+ def run
5
+ puts "twat: #{Twat::VERSION}"
6
+ end
7
+
8
+ def self.usage
9
+ "Usage: twat version"
10
+ end
11
+
12
+ end
13
+ COMMANDS['version'] = Version
14
+ end
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