tweetwine 0.2.12 → 0.3.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.
Files changed (67) hide show
  1. data/CHANGELOG.rdoc +7 -0
  2. data/Gemfile +17 -0
  3. data/README.md +57 -47
  4. data/Rakefile +17 -26
  5. data/bin/tweetwine +11 -12
  6. data/contrib/tweetwine-completion.bash +2 -3
  7. data/example/application_behavior_example.rb +173 -0
  8. data/example/example_helper.rb +44 -28
  9. data/example/fixture/config.yaml +8 -0
  10. data/example/fixture/shorten_rubygems.html +5 -0
  11. data/example/fixture/shorten_rubylang.html +5 -0
  12. data/example/fixture/update_utf8.json +1 -0
  13. data/example/fixture/update_with_urls.json +1 -0
  14. data/example/fixture/{update.json → update_without_urls.json} +0 -0
  15. data/example/search_statuses_example.rb +49 -16
  16. data/example/show_followers_example.rb +7 -8
  17. data/example/show_friends_example.rb +7 -8
  18. data/example/show_home_example.rb +19 -16
  19. data/example/show_mentions_example.rb +8 -9
  20. data/example/show_user_example.rb +16 -13
  21. data/example/update_status_example.rb +143 -26
  22. data/example/use_http_proxy_example.rb +40 -20
  23. data/lib/tweetwine/basic_object.rb +19 -0
  24. data/lib/tweetwine/character_encoding.rb +59 -0
  25. data/lib/tweetwine/cli.rb +354 -230
  26. data/lib/tweetwine/config.rb +65 -0
  27. data/lib/tweetwine/http.rb +120 -0
  28. data/lib/tweetwine/oauth.rb +104 -0
  29. data/lib/tweetwine/obfuscate.rb +21 -0
  30. data/lib/tweetwine/option_parser.rb +31 -0
  31. data/lib/tweetwine/promise.rb +39 -0
  32. data/lib/tweetwine/twitter.rb +211 -0
  33. data/lib/tweetwine/{io.rb → ui.rb} +30 -21
  34. data/lib/tweetwine/url_shortener.rb +15 -9
  35. data/lib/tweetwine/util.rb +30 -15
  36. data/lib/tweetwine.rb +72 -12
  37. data/man/tweetwine.7 +43 -69
  38. data/man/tweetwine.7.ronn +57 -47
  39. data/test/character_encoding_test.rb +87 -0
  40. data/test/cli_test.rb +19 -6
  41. data/test/config_test.rb +244 -0
  42. data/test/fixture/oauth.rb +21 -0
  43. data/test/fixture/test_config.yaml +4 -4
  44. data/test/http_test.rb +199 -0
  45. data/test/oauth_test.rb +77 -0
  46. data/test/obfuscate_test.rb +16 -0
  47. data/test/option_parser_test.rb +60 -0
  48. data/test/promise_test.rb +56 -0
  49. data/test/test_helper.rb +76 -8
  50. data/test/twitter_test.rb +625 -0
  51. data/test/{io_test.rb → ui_test.rb} +92 -74
  52. data/test/url_shortener_test.rb +115 -135
  53. data/test/util_test.rb +136 -85
  54. data/tweetwine.gemspec +53 -0
  55. metadata +112 -56
  56. data/example/show_metadata_example.rb +0 -86
  57. data/lib/tweetwine/client.rb +0 -187
  58. data/lib/tweetwine/meta.rb +0 -5
  59. data/lib/tweetwine/options.rb +0 -24
  60. data/lib/tweetwine/retrying_http.rb +0 -99
  61. data/lib/tweetwine/startup_config.rb +0 -50
  62. data/man/tweetwine.1 +0 -109
  63. data/man/tweetwine.1.ronn +0 -69
  64. data/test/client_test.rb +0 -544
  65. data/test/options_test.rb +0 -45
  66. data/test/retrying_http_test.rb +0 -147
  67. data/test/startup_config_test.rb +0 -162
@@ -0,0 +1,59 @@
1
+ # coding: utf-8
2
+
3
+ module Tweetwine
4
+ class CharacterEncoding
5
+ class << self
6
+ if "".respond_to?(:encode)
7
+ def to_utf8(str)
8
+ result = str.encode('UTF-8')
9
+ raise TranscodeError, "invalid UTF-8 byte sequence when transcoding '#{str}'" unless result.valid_encoding?
10
+ result
11
+ end
12
+ else
13
+ def to_utf8(str)
14
+ if guess_external_encoding != 'UTF-8'
15
+ begin
16
+ require "iconv"
17
+ Iconv.conv('UTF-8//TRANSLIT', guess_external_encoding, str)
18
+ rescue => e
19
+ raise TranscodeError, e
20
+ end
21
+ else
22
+ str
23
+ end
24
+ end
25
+ end
26
+
27
+ def forget_guess
28
+ @guess_external_encoding = nil
29
+ end
30
+
31
+ private
32
+
33
+ def guess_external_encoding
34
+ @guess_external_encoding ||= begin
35
+ guess = guess_external_encoding_from_kcode || guess_external_encoding_from_env_lang
36
+ raise TranscodeError, "could not determine your external encoding" unless guess
37
+ guess
38
+ end
39
+ end
40
+
41
+ def guess_external_encoding_from_kcode
42
+ guess = nil
43
+ guess = case $KCODE
44
+ when 'EUC' then 'EUC-JP'
45
+ when 'SJIS' then 'SHIFT-JIS'
46
+ when 'UTF8' then 'UTF-8'
47
+ else nil
48
+ end if defined?($KCODE)
49
+ guess
50
+ end
51
+
52
+ def guess_external_encoding_from_env_lang
53
+ lang = ENV['LANG']
54
+ return 'UTF-8' if lang =~ /(utf-8|utf8)\z/i
55
+ Util.blank?(lang) ? nil : lang
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/tweetwine/cli.rb CHANGED
@@ -1,254 +1,378 @@
1
1
  # coding: utf-8
2
2
 
3
- require "optparse"
4
-
5
3
  module Tweetwine
6
- class CLI
7
- EXIT_HELP = 1
8
- EXIT_VERSION = 2
9
- EXIT_ERROR = 255
10
-
11
- def self.launch(args, exec_name, config_file, extra_opts = {})
12
- new(args, exec_name, config_file, extra_opts, &default_dependencies).execute(args)
13
- rescue ArgumentError, HttpError => e
14
- puts "Error: #{e.message}"
15
- exit(EXIT_ERROR)
16
- end
17
-
18
- def execute(args)
19
- if @config.command != :help
20
- cmd_options = parse_command_options(@config.command, args)
21
- @client.send(@config.command, args, cmd_options)
4
+ module CLI
5
+ DEFAULT_COMMAND = :home
6
+ DEFAULT_CONFIG = {
7
+ :colors => :false,
8
+ :config_file => "#{(ENV['HOME'] || ENV['USERPROFILE'])}/.tweetwine",
9
+ :env_lookouts => [:http_proxy],
10
+ :excludes => [:command],
11
+ :shorten_urls => {:disable => true},
12
+ :username => ENV['USER']
13
+ }.freeze
14
+ EXEC_NAME = 'tweetwine'
15
+
16
+ class << self
17
+ def start(args = ARGV, overriding_default_conf = nil)
18
+ init(args, overriding_default_conf)
19
+ run(args)
20
+ end
21
+
22
+ def config
23
+ @config ||= read_config
24
+ end
25
+
26
+ def http
27
+ @http ||= Http::Client.new(config)
28
+ end
29
+
30
+ def oauth
31
+ @oauth ||= OAuth.new(config[:oauth_access])
32
+ end
33
+
34
+ def twitter
35
+ @twitter ||= Twitter.new(config)
36
+ end
37
+
38
+ def ui
39
+ @ui ||= UI.new(config)
40
+ end
41
+
42
+ def url_shortener
43
+ @url_shorterer ||= UrlShortener.new(config[:shorten_urls])
44
+ end
45
+
46
+ def commands
47
+ @commands ||= {
48
+ :primaries => {},
49
+ :secondaries => {}
50
+ }
51
+ end
52
+
53
+ def register_command(cmd_class, names)
54
+ commands[:primaries][names.first.to_sym] = cmd_class
55
+ names[1..-1].each { |name| commands[:secondaries][name.to_sym] = cmd_class }
56
+ end
57
+
58
+ def find_command(name)
59
+ name = name.to_sym
60
+ commands[:primaries][name] || commands[:secondaries][name]
61
+ end
62
+
63
+ def global_option_parser
64
+ @global_option_parser ||= OptionParser.new do |parser, options|
65
+ parser.on '-c', '--colors', 'Enable ANSI colors for output.' do
66
+ options[:colors] = true
67
+ end
68
+ parser.on '-f', '--config <file>', String, "Configuration file (default #{DEFAULT_CONFIG[:config_file]})." do |arg|
69
+ options[:config_file] = arg
70
+ end
71
+ parser.on '-h', '--help', 'Show this help and exit.' do
72
+ options[:command] = :help
73
+ end
74
+ parser.on '--http-proxy <url>', String, 'Enable HTTP(S) proxy.' do |arg|
75
+ options[:http_proxy] = arg
76
+ end
77
+ parser.on '--no-colors', 'Disable ANSI colors for output.' do
78
+ options[:colors] = false
79
+ end
80
+ parser.on '--no-http-proxy', 'Disable HTTP(S) proxy.' do
81
+ options[:http_proxy] = nil
82
+ end
83
+ parser.on '--no-url-shorten', 'Disable URL shortening.' do
84
+ options[:shorten_urls] ||= {}
85
+ options[:shorten_urls][:disable] = true
86
+ end
87
+ parser.on '-n', '--num <n>', Integer, "Number of statuses per page (default #{Twitter::DEFAULT_NUM_STATUSES})." do |arg|
88
+ options[:num_statuses] = arg
89
+ end
90
+ parser.on '-p', '--page <p>', Integer, "Page number for statuses (default #{Twitter::DEFAULT_PAGE_NUM})." do |arg|
91
+ options[:page] = arg
92
+ end
93
+ parser.on '-u', '--username <user>', String, "User to authenticate (default '#{DEFAULT_CONFIG[:username]}')." do |arg|
94
+ options[:username] = arg
95
+ end
96
+ parser.on '-v', '--version', "Show version and exit." do
97
+ options[:command] = :version
98
+ end
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def init(args, overriding_default_conf = nil)
105
+ @config, @http, @oauth, @twitter, @ui, @url_shortener = nil # reset
106
+ @config = read_config(args, overriding_default_conf)
107
+ end
108
+
109
+ def run(args)
110
+ proposed_command = config[:command]
111
+ found_command = find_command proposed_command
112
+ raise UnknownCommandError, "unknown command: #{proposed_command}" unless found_command
113
+ found_command.new(args).run
114
+ self
115
+ end
116
+
117
+ def read_config(cmdline_args = [], overriding_default_config = nil)
118
+ default_config = overriding_default_config ? DEFAULT_CONFIG.merge(overriding_default_config) : DEFAULT_CONFIG
119
+ config = Config.read(cmdline_args, default_config) do |args|
120
+ parse_config_from_cmdline(args)
121
+ end
122
+ config
123
+ end
124
+
125
+ def parse_config_from_cmdline(args)
126
+ options = global_option_parser.parse(args)
127
+ unless options[:command]
128
+ cmd_via_arg = args.shift
129
+ options[:command] = cmd_via_arg ? cmd_via_arg.to_sym : DEFAULT_COMMAND
130
+ end
131
+ options
132
+ end
133
+ end
134
+ end
135
+
136
+ class Command
137
+ class << self
138
+ def inherited(child)
139
+ # Silence warnings about uninitialized variables if a child does not
140
+ # set its about, name, or usage.
141
+ child.instance_eval do
142
+ @about, @name, @usage = nil
143
+ end
144
+ end
145
+
146
+ def about(description = nil)
147
+ return @about unless description
148
+ @about = description.chomp
149
+ end
150
+
151
+ def register(*names)
152
+ @name = names.first
153
+ CLI.register_command(self, names)
154
+ end
155
+
156
+ def name
157
+ @name
158
+ end
159
+
160
+ # Usage description for the command, use if overriding #parse.
161
+ def usage(description = nil)
162
+ return @usage unless description
163
+ @usage = description
164
+ end
165
+
166
+ def show_usage(about_cmd = self)
167
+ about = about_cmd.about
168
+ name = about_cmd.name
169
+ usage = about_cmd.usage
170
+ result = <<-END
171
+ #{about}
172
+
173
+ Usage: #{CLI::EXEC_NAME} #{name} #{usage}
174
+ END
175
+ CLI.ui.info result.strip!
176
+ end
177
+
178
+ def abort_with_usage
179
+ show_usage
180
+ exit CommandLineError.status_code
181
+ end
182
+ end
183
+
184
+ def initialize(args)
185
+ parsing_succeeded = parse(args)
186
+ self.class.abort_with_usage unless parsing_succeeded
187
+ end
188
+
189
+ # Default behavior, which succeeds always; override for real argument
190
+ # parsing if the command needs arguments.
191
+ def parse(args)
192
+ true
193
+ end
194
+ end
195
+
196
+ class HelpCommand < Command
197
+ register "help"
198
+ about "Show help and exit. Try it with <command> argument."
199
+ usage <<-END
200
+ [<command>]
201
+
202
+ If <command> is given, show specific help about that command. If no
203
+ <command> is given, show general help.
204
+ END
205
+
206
+ def parse(args)
207
+ # Did we arrive here via '-h' option? If so, we cannot have
208
+ # +proposed_command+ because '-h' does not take an argument. Otherwise,
209
+ # try to read the argument.
210
+ proposed_command = args.include?('-h') ? nil : args.shift
211
+ if proposed_command
212
+ @command = CLI.find_command proposed_command
213
+ CLI.ui.error "unknown command: #{proposed_command}\n\n" unless @command
214
+ @command
215
+ else
216
+ @command = nil
217
+ true
218
+ end
219
+ end
220
+
221
+ def run
222
+ if @command
223
+ show_command_help
22
224
  else
23
- show_help_command_and_exit(args)
225
+ show_general_help
24
226
  end
25
227
  end
26
228
 
27
229
  private
28
230
 
29
- def self.default_dependencies
30
- lambda do |options|
31
- io = Tweetwine::IO.new(options)
32
- http_client = RetryingHttp::Client.new(io)
33
- url_shortener = lambda { |opts| UrlShortener.new(http_client, opts) }
34
- Client::Dependencies.new(io, http_client, url_shortener)
35
- end
36
- end
37
-
38
- def initialize(args, exec_name, config_file, extra_opts = {}, &dependencies_blk)
39
- @global_option_parser = create_global_option_parser(exec_name)
40
- @config = StartupConfig.new(Client::COMMANDS + [:help], Client::DEFAULT_COMMAND, extra_opts)
41
- @config.parse(args, config_file, [:http_proxy], &@global_option_parser)
42
- @client = Client.new(yield(@config.options), @config.options) if @config.command != :help
43
- end
44
-
45
- def show_help_command_and_exit(args)
46
- help_about_cmd = args.shift
47
- if help_about_cmd
48
- help_about_cmd = help_about_cmd.to_sym
49
- parse_command_options(help_about_cmd, ["-h"]) if Client::COMMANDS.include?(help_about_cmd)
50
- end
51
- @global_option_parser.call(["-h"])
52
- end
53
-
54
- def self.create_option_parser
55
- lambda do |args|
56
- parsed_options = {}
57
- begin
58
- parser = OptionParser.new do |opt|
59
- opt.on_tail("-h", "--help", "Show this help message and exit") {
60
- puts opt
61
- exit(EXIT_HELP)
62
- }
63
- schema = yield parsed_options
64
- opt.banner = schema[:help]
65
- schema[:opts].each do |opt_schema|
66
- opt.on(*option_schema_to_ary(opt_schema), &opt_schema[:action])
67
- end if schema[:opts]
68
- end.order!(args)
69
- rescue OptionParser::ParseError => e
70
- raise ArgumentError, e.message
231
+ def show_command_help
232
+ self.class.show_usage @command
233
+ end
234
+
235
+ def show_general_help
236
+ command_descriptions = CLI.commands[:primaries].
237
+ entries.
238
+ sort { |a, b| a.first.to_s <=> b.first.to_s }.
239
+ map { |cmd, klass| [cmd, klass.about] }
240
+ CLI.ui.info <<-END
241
+ A simple but tasty Twitter agent for command line use, made for fun.
242
+
243
+ Usage: #{CLI::EXEC_NAME} [global_options...] [<command>] [command_options...]
244
+
245
+ Global options:
246
+
247
+ #{CLI.global_option_parser.help}
248
+
249
+ Commands:
250
+
251
+ #{command_descriptions.map { |cmd, desc| " %-14s%s" % [cmd, desc] }.join("\n") }
252
+ END
253
+ end
254
+ end
255
+
256
+ class HomeCommand < Command
257
+ register "home", "h"
258
+ about "Show authenticated user's home timeline (the default command)."
259
+
260
+ def run
261
+ CLI.twitter.home
262
+ end
263
+ end
264
+
265
+ class FollowersCommand < Command
266
+ register "followers", "fo"
267
+ about "Show authenticated user's followers and their latest tweets."
268
+
269
+ def run
270
+ CLI.twitter.followers
271
+ end
272
+ end
273
+
274
+ class FriendsCommand < Command
275
+ register "friends", "fr"
276
+ about "Show authenticated user's friends and their latest tweets."
277
+
278
+ def run
279
+ CLI.twitter.friends
280
+ end
281
+ end
282
+
283
+ class MentionsCommand < Command
284
+ register "mentions", "men", "m"
285
+ about "Show latest tweets that mention or are replies to the authenticated user."
286
+
287
+ def run
288
+ CLI.twitter.mentions
289
+ end
290
+ end
291
+
292
+ class SearchCommand < Command
293
+ def self.parser
294
+ @parser ||= OptionParser.new do |parser, options|
295
+ parser.on '-a', '--and', 'All words match (default).' do
296
+ options[:operator] = :and
297
+ end
298
+ parser.on '-o', '--or', 'Any word matches.' do
299
+ options[:operator] = :or
71
300
  end
72
- parsed_options
73
- end
74
- end
75
-
76
- def self.option_schema_to_ary(opt_schema)
77
- [:short, :long, :type, :desc].inject([]) do |result, key|
78
- result << opt_schema[key] if opt_schema[key]
79
- result
80
- end
81
- end
82
-
83
- def create_global_option_parser(exec_name)
84
- self.class.create_option_parser do |parsed|
85
- {
86
- :help => \
87
- "A simple but tasty Twitter agent for command line use, made for fun.
88
-
89
- Usage: #{exec_name} [global_options...] [command] [command_options...]
90
-
91
- [command] is one of
92
- * #{Client::COMMANDS[0...-1].join(",\n * ")}, or
93
- * #{Client::COMMANDS.last}.
94
-
95
- The default command is #{Client::DEFAULT_COMMAND}.
96
-
97
- [global_options]:
98
- ",
99
- :opts => [
100
- {
101
- :short => "-a",
102
- :long => "--auth USERNAME:PASSWORD",
103
- :desc => "Authentication",
104
- :action => lambda { |arg| parsed[:username], parsed[:password] = arg.split(":", 2) }
105
- },
106
- {
107
- :short => "-c",
108
- :long => "--colors",
109
- :desc => "Colorize output with ANSI escape codes",
110
- :action => lambda { |arg| parsed[:colors] = true }
111
- },
112
- {
113
- :short => "-n",
114
- :long => "--num N",
115
- :type => Integer,
116
- :desc => "The number of statuses in page, default #{Client::DEFAULT_NUM_STATUSES}",
117
- :action => lambda { |arg| parsed[:num_statuses] = arg }
118
- },
119
- {
120
- :long => "--no-colors",
121
- :desc => "Do not use ANSI colors",
122
- :action => lambda { |arg| parsed[:colors] = false }
123
- },
124
- {
125
- :long => "--no-http-proxy",
126
- :desc => "Do not use proxy for HTTP and HTTPS",
127
- :action => lambda { |arg| parsed[:http_proxy] = nil }
128
- },
129
- {
130
- :long => "--no-url-shorten",
131
- :desc => "Do not shorten URLs for status update",
132
- :action => lambda { |arg| parsed[:shorten_urls] = { :enable => false } }
133
- },
134
- {
135
- :short => "-p",
136
- :long => "--page N",
137
- :type => Integer,
138
- :desc => "The page number for statuses, default #{Client::DEFAULT_PAGE_NUM}",
139
- :action => lambda { |arg| parsed[:page_num] = arg }
140
- },
141
- {
142
- :long => "--http-proxy URL",
143
- :type => String,
144
- :desc => "Use proxy for HTTP and HTTPS",
145
- :action => lambda { |arg| parsed[:http_proxy] = arg }
146
- },
147
- {
148
- :short => "-v",
149
- :long => "--version",
150
- :desc => "Show version information and exit",
151
- :action => lambda do |arg|
152
- puts "#{exec_name} #{Tweetwine::VERSION}"
153
- exit(EXIT_VERSION)
154
- end
155
- }
156
- ]
157
- }
158
301
  end
159
302
  end
160
303
 
161
- def self.create_command_option_parser(command_name, schema)
162
- create_option_parser do |parsed|
163
- {
164
- :help => \
165
- "#{command_name} [command_options...] #{schema[:help][:rest_args]}
304
+ register "search", "s"
305
+ about "Search latest public tweets."
306
+ usage(Promise.new {<<-END
307
+ [--all | --or] <word>...
166
308
 
167
- #{schema[:help][:desc]}
309
+ Command options:
168
310
 
169
- [command_options]:
170
- ",
171
- :opts => schema[:opts]
172
- }
311
+ #{parser.help}
312
+ END
313
+ })
314
+
315
+ def parse(args)
316
+ options = self.class.parser.parse(args)
317
+ @operator = options[:operator]
318
+ @words = args
319
+ if @words.empty?
320
+ CLI.ui.error "No search words.\n\n"
321
+ false
322
+ else
323
+ true
173
324
  end
174
325
  end
175
326
 
176
- command_parser_schemas = {
177
- :followers => {
178
- :help => {
179
- :desc => \
180
- "Show the followers of the authenticated user, together with the latest status
181
- of each follower."
182
- }
183
- },
184
- :friends => {
185
- :help => {
186
- :desc => \
187
- "Show the friends of the authenticated user, together with the latest status of
188
- each friend."
189
- }
190
- },
191
- :home => {
192
- :help => {
193
- :desc => \
194
- "Show the latest statuses of friends and own tweets (the home timeline of the
195
- authenticated user)."
196
- }
197
- },
198
- :mentions => {
199
- :help => {
200
- :desc => \
201
- "Show the latest statuses that mention the authenticated user."
202
- }
203
- },
204
- :search => {
205
- :help => {
206
- :rest_args => "word_1 [word_2...]",
207
- :desc => \
208
- "Search the latest public statuses with one or more words."
209
- },
210
- :opts => [
211
- {
212
- :short => "-a",
213
- :long => "--and",
214
- :desc => "All words must match",
215
- :action => lambda { |arg| parsed[:bin_op] = :and }
216
- },
217
- {
218
- :short => "-o",
219
- :long => "--or",
220
- :desc => "Any word matches",
221
- :action => lambda { |arg| parsed[:bin_op] = :or }
222
- }
223
- ]
224
- },
225
- :update => {
226
- :help => {
227
- :rest_args => "[status]",
228
- :desc => \
229
- "Send a status update, but confirm the action first before actually sending.
230
- The status update can either be given as an argument or via STDIN if no
231
- [status] is given."
232
- }
233
- },
234
- :user => {
235
- :help => {
236
- :rest_args => "[username]",
237
- :desc => \
238
- "Show a specific user's latest statuses. The user is identified with [username]
239
- argument; if the argument is absent, the authenticated user's statuses are
240
- shown."
241
- }
242
- }
243
- }
327
+ def run
328
+ CLI.twitter.search @words, @operator
329
+ end
330
+ end
331
+
332
+ class UpdateCommand < Command
333
+ register "update", "up"
334
+ about "Send new tweet."
335
+ usage <<-END
336
+ [<status>]
337
+
338
+ If <status> is not given, read the contents for the tweet from STDIN.
339
+ END
340
+
341
+ def parse(args)
342
+ @msg = args.join(' ')
343
+ args.clear
344
+ true
345
+ end
346
+
347
+ def run
348
+ CLI.twitter.update @msg
349
+ end
350
+ end
351
+
352
+ class UserCommand < Command
353
+ register "user", "usr"
354
+ about "Show user's timeline."
355
+ usage <<-END
356
+ [<username>]
244
357
 
245
- COMMAND_OPTION_PARSERS = Client::COMMANDS.inject({}) do |result, cmd|
246
- result[cmd] = create_command_option_parser(cmd, command_parser_schemas[cmd])
247
- result
358
+ If <username> is not given, show authenticated user's timeline.
359
+ END
360
+
361
+ def parse(args)
362
+ @user = args.empty? ? CLI.config[:username] : args.shift
248
363
  end
249
364
 
250
- def parse_command_options(command, args)
251
- COMMAND_OPTION_PARSERS[command].call(args)
365
+ def run
366
+ CLI.twitter.user(@user)
367
+ end
368
+ end
369
+
370
+ class VersionCommand < Command
371
+ register "version", "ver", "v"
372
+ about "Show program version and exit."
373
+
374
+ def run
375
+ CLI.ui.info "tweetwine #{Tweetwine::VERSION}"
252
376
  end
253
377
  end
254
378
  end