emasaka-termtter 1.0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/History.txt +4 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +55 -0
  4. data/bin/kill_termtter +22 -0
  5. data/bin/termtter +6 -0
  6. data/lib/plugins/addspace.rb +27 -0
  7. data/lib/plugins/april_fool.rb +15 -0
  8. data/lib/plugins/bomb.rb +39 -0
  9. data/lib/plugins/clear.rb +14 -0
  10. data/lib/plugins/command_plus.rb +47 -0
  11. data/lib/plugins/confirm.rb +12 -0
  12. data/lib/plugins/cool.rb +10 -0
  13. data/lib/plugins/countter.rb +23 -0
  14. data/lib/plugins/devel.rb +18 -0
  15. data/lib/plugins/direct_messages.rb +36 -0
  16. data/lib/plugins/en2ja.rb +11 -0
  17. data/lib/plugins/english.rb +25 -0
  18. data/lib/plugins/erb.rb +17 -0
  19. data/lib/plugins/exec_and_update.rb +14 -0
  20. data/lib/plugins/expand-tinyurl.rb +33 -0
  21. data/lib/plugins/fib.rb +28 -0
  22. data/lib/plugins/fib_filter.rb +14 -0
  23. data/lib/plugins/filter.rb +69 -0
  24. data/lib/plugins/graduatter.rb +8 -0
  25. data/lib/plugins/grass.rb +27 -0
  26. data/lib/plugins/group.rb +64 -0
  27. data/lib/plugins/growl.rb +52 -0
  28. data/lib/plugins/growl2.rb +143 -0
  29. data/lib/plugins/hatebu.rb +59 -0
  30. data/lib/plugins/history.rb +93 -0
  31. data/lib/plugins/ignore.rb +19 -0
  32. data/lib/plugins/keyword.rb +18 -0
  33. data/lib/plugins/l2.rb +25 -0
  34. data/lib/plugins/log.rb +73 -0
  35. data/lib/plugins/me.rb +10 -0
  36. data/lib/plugins/modify_arg_hook_sample.rb +7 -0
  37. data/lib/plugins/msagent.rb +38 -0
  38. data/lib/plugins/multi_reply.rb +27 -0
  39. data/lib/plugins/notify-send.rb +22 -0
  40. data/lib/plugins/notify-send2.rb +52 -0
  41. data/lib/plugins/open_url.rb +59 -0
  42. data/lib/plugins/otsune.rb +21 -0
  43. data/lib/plugins/outputz.rb +33 -0
  44. data/lib/plugins/pause.rb +3 -0
  45. data/lib/plugins/post_exec_hook_sample.rb +9 -0
  46. data/lib/plugins/pre_exec_hook_sample.rb +9 -0
  47. data/lib/plugins/primes.rb +30 -0
  48. data/lib/plugins/quicklook.rb +41 -0
  49. data/lib/plugins/random.rb +23 -0
  50. data/lib/plugins/reblog.rb +40 -0
  51. data/lib/plugins/reload.rb +3 -0
  52. data/lib/plugins/reply.rb +8 -0
  53. data/lib/plugins/retweet.rb +46 -0
  54. data/lib/plugins/reverse.rb +13 -0
  55. data/lib/plugins/say.rb +26 -0
  56. data/lib/plugins/scrape.rb +41 -0
  57. data/lib/plugins/screen-notify.rb +20 -0
  58. data/lib/plugins/screen.rb +24 -0
  59. data/lib/plugins/shell.rb +14 -0
  60. data/lib/plugins/shorturl_on_update.rb +36 -0
  61. data/lib/plugins/sl.rb +48 -0
  62. data/lib/plugins/spam.rb +16 -0
  63. data/lib/plugins/standard_plugins.rb +519 -0
  64. data/lib/plugins/stdout.rb +83 -0
  65. data/lib/plugins/storage.rb +55 -0
  66. data/lib/plugins/storage/DB.rb +37 -0
  67. data/lib/plugins/storage/status.rb +83 -0
  68. data/lib/plugins/storage/status_mook.rb +30 -0
  69. data/lib/plugins/switch_user.rb +22 -0
  70. data/lib/plugins/system_status.rb +33 -0
  71. data/lib/plugins/timer.rb +18 -0
  72. data/lib/plugins/translation.rb +38 -0
  73. data/lib/plugins/typable_id.rb +94 -0
  74. data/lib/plugins/update_editor.rb +53 -0
  75. data/lib/plugins/update_filter.rb +30 -0
  76. data/lib/plugins/uri-open.rb +66 -0
  77. data/lib/plugins/wassr_post.rb +22 -0
  78. data/lib/plugins/yhara.rb +148 -0
  79. data/lib/plugins/yhara_filter.rb +8 -0
  80. data/lib/plugins/yonda.rb +21 -0
  81. data/lib/termtter.rb +38 -0
  82. data/lib/termtter/api.rb +57 -0
  83. data/lib/termtter/client.rb +370 -0
  84. data/lib/termtter/command.rb +80 -0
  85. data/lib/termtter/config.rb +68 -0
  86. data/lib/termtter/config_setup.rb +38 -0
  87. data/lib/termtter/connection.rb +41 -0
  88. data/lib/termtter/hook.rb +31 -0
  89. data/lib/termtter/optparse.rb +14 -0
  90. data/lib/termtter/system_extensions.rb +161 -0
  91. data/lib/termtter/task.rb +17 -0
  92. data/lib/termtter/task_manager.rb +116 -0
  93. data/lib/termtter/version.rb +4 -0
  94. data/spec/plugins/cool_spec.rb +10 -0
  95. data/spec/plugins/english_spec.rb +19 -0
  96. data/spec/plugins/fib_spec.rb +15 -0
  97. data/spec/plugins/filter_spec.rb +18 -0
  98. data/spec/plugins/pause_spec.rb +8 -0
  99. data/spec/plugins/primes_spec.rb +15 -0
  100. data/spec/plugins/shell_spec.rb +10 -0
  101. data/spec/plugins/sl_spec.rb +8 -0
  102. data/spec/plugins/spam_spec.rb +4 -0
  103. data/spec/plugins/standard_plugins_spec.rb +23 -0
  104. data/spec/plugins/storage/DB_spec.rb +12 -0
  105. data/spec/plugins/storage/status_spec.rb +24 -0
  106. data/spec/spec_helper.rb +7 -0
  107. data/spec/termtter/client_spec.rb +255 -0
  108. data/spec/termtter/command_spec.rb +107 -0
  109. data/spec/termtter/config_spec.rb +111 -0
  110. data/spec/termtter/hook_spec.rb +78 -0
  111. data/spec/termtter/task_manager_spec.rb +78 -0
  112. data/spec/termtter/task_spec.rb +22 -0
  113. data/spec/termtter_spec.rb +29 -0
  114. data/test/friends_timeline.json +5 -0
  115. data/test/search.json +8 -0
  116. metadata +210 -0
@@ -0,0 +1,370 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'fileutils'
3
+ require 'logger'
4
+ require 'termcolor'
5
+
6
+ module Termtter
7
+
8
+ class CommandNotFound < StandardError; end
9
+ class CommandCanceled < StandardError; end
10
+
11
+ module Client
12
+
13
+ class << self
14
+
15
+ def init
16
+ @hooks = {}
17
+ @commands = {}
18
+ @filters = []
19
+ @since_id = nil
20
+ @input_thread = nil
21
+ @task_manager = Termtter::TaskManager.new
22
+ config.set_default(:logger, nil)
23
+ config.set_default(:update_interval, 300)
24
+ config.set_default(:prompt, '> ')
25
+ config.set_default(:devel, false)
26
+ Thread.abort_on_exception = true
27
+ end
28
+
29
+ def public_storage
30
+ @public_storage ||= {}
31
+ end
32
+
33
+ def add_filter(&b)
34
+ @filters << b
35
+ end
36
+
37
+ def clear_filter
38
+ @filters.clear
39
+ end
40
+
41
+ def register_hook(arg)
42
+ hook = case arg
43
+ when Hook
44
+ arg
45
+ when Hash
46
+ Hook.new(arg)
47
+ else
48
+ raise ArgumentError, 'must be given Termtter::Hook or Hash'
49
+ end
50
+ @hooks[hook.name] = hook
51
+ end
52
+
53
+ def get_hook(name)
54
+ @hooks[name]
55
+ end
56
+
57
+ def get_hooks(point)
58
+ @hooks.values.select do |hook|
59
+ hook.match?(point)
60
+ end
61
+ end
62
+
63
+ def register_command(arg)
64
+ command = case arg
65
+ when Command
66
+ arg
67
+ when Hash
68
+ Command.new(arg)
69
+ else
70
+ raise ArgumentError, 'must be given Termtter::Command or Hash'
71
+ end
72
+ @commands[command.name] = command
73
+ end
74
+
75
+ def get_command(name)
76
+ @commands[name]
77
+ end
78
+
79
+ def register_macro(name, macro, options = {})
80
+ command = {
81
+ :name => name.to_sym,
82
+ :exec_proc => lambda {|arg| call_commands(macro % arg)}
83
+ }.merge(options)
84
+ register_command(command)
85
+ end
86
+
87
+ # statuses => [status, status, ...]
88
+ # status => {
89
+ # :id => status id,
90
+ # :created_at => created time,
91
+ # :user_id => user id,
92
+ # :name => user name,
93
+ # :screen_name => user screen_name,
94
+ # :source => source,
95
+ # :reply_to => reply_to status id,
96
+ # :text => status,
97
+ # :original_data => original data,
98
+ # }
99
+ def output(statuses, event)
100
+ return if statuses.nil? || statuses.empty?
101
+
102
+ statuses = statuses.sort_by{|s|s.id}
103
+ call_hooks(:pre_filter, statuses, event)
104
+ filtered = apply_filters(statuses, event)
105
+ call_hooks(:post_filter, filtered, event)
106
+ get_hooks(:output).each do |hook|
107
+ filtered_for_hook = apply_filters_for_hook(filtered, hook.name)
108
+ hook.call(filtered_for_hook, event)
109
+ end
110
+ end
111
+
112
+ def apply_filters(statuses, event)
113
+ filtered = statuses.map(&:dup)
114
+ @filters.each do |f|
115
+ filtered = f.call(filtered, event)
116
+ end
117
+ filtered
118
+ end
119
+
120
+ def apply_filters_for_hook(statuses, hook_name)
121
+ # TODO
122
+ filtered = statuses.map(&:dup)
123
+ filtered
124
+ end
125
+
126
+ # return last hook return value
127
+ def call_hooks(point, *args)
128
+ result = nil
129
+ get_hooks(point).each {|hook|
130
+ break if result == false # interrupt if hook return false
131
+ result = hook.call(*args)
132
+ }
133
+ result
134
+ end
135
+
136
+ def call_commands(text, tw = nil)
137
+ return if text.empty?
138
+
139
+ command_found = false
140
+ @commands.each do |key, command|
141
+ next unless command.match?(text)
142
+ command_found = true
143
+ command_str, command_arg = Command.split_command_line(text)
144
+
145
+ modified_arg = call_hooks(
146
+ "modify_arg_for_#{command.name.to_s}",
147
+ command_str,
148
+ command_arg) || command_arg || ''
149
+
150
+ @task_manager.invoke_and_wait do
151
+ begin
152
+ call_hooks("pre_exec_#{command.name.to_s}", command_str, modified_arg)
153
+ # exec command
154
+ result = command.call(command_str, modified_arg, text)
155
+ if result
156
+ call_hooks("post_exec_#{command.name.to_s}", command_str, modified_arg, result)
157
+ end
158
+ rescue CommandCanceled
159
+ end
160
+ end
161
+ end
162
+
163
+ raise CommandNotFound, text unless command_found
164
+ end
165
+
166
+ def pause
167
+ @task_manager.pause
168
+ end
169
+
170
+ def resume
171
+ @task_manager.resume
172
+ end
173
+
174
+ def add_task(*arg, &block)
175
+ @task_manager.add_task(*arg, &block)
176
+ end
177
+
178
+ def exit
179
+ puts 'finalizing...'
180
+
181
+ call_hooks(:exit)
182
+ @task_manager.kill
183
+ @input_thread.kill if @input_thread
184
+ end
185
+
186
+ def load_default_plugins
187
+ plugin 'standard_plugins'
188
+ plugin 'stdout'
189
+ end
190
+
191
+ def load_config
192
+ legacy_config_support() if File.exist? Termtter::CONF_DIR
193
+ unless File.exist?(Termtter::CONF_FILE)
194
+ require 'termtter/config_setup'
195
+ ConfigSetup.run
196
+ end
197
+ load Termtter::CONF_FILE
198
+ end
199
+
200
+ def legacy_config_support
201
+ case File.ftype(File.expand_path('~/.termtter'))
202
+ when 'directory'
203
+ # nop
204
+ when 'file'
205
+ move_legacy_config_file
206
+ end
207
+ end
208
+
209
+ def move_legacy_config_file
210
+ FileUtils.mv(
211
+ Termtter::CONF_DIR,
212
+ File.expand_path('~/.termtter___'))
213
+ Dir.mkdir(Termtter::CONF_DIR)
214
+ FileUtils.mv(
215
+ File.expand_path('~/.termtter___'),
216
+ Termtter::CONF_FILE)
217
+ end
218
+
219
+ def post_config_load()
220
+ if config.devel
221
+ plugin 'devel'
222
+ end
223
+ end
224
+
225
+ def setup_readline
226
+ if Readline.respond_to?(:basic_word_break_characters=)
227
+ Readline.basic_word_break_characters= "\t\n\"\\'`><=;|&{("
228
+ end
229
+ Readline.completion_proc = lambda {|input|
230
+ begin
231
+ @commands.map {|name, command| command.complement(input) }.flatten.compact
232
+ rescue => e
233
+ handle_error(e)
234
+ end
235
+ }
236
+ vi_or_emacs = config.editing_mode
237
+ unless vi_or_emacs.empty?
238
+ Readline.__send__("#{vi_or_emacs}_editing_mode")
239
+ end
240
+ end
241
+
242
+ # TODO: Make pluggable
243
+ def setup_update_timeline_task()
244
+ register_command(
245
+ :name => :_update_timeline,
246
+ :exec_proc => lambda {|arg|
247
+ begin
248
+ args = @since_id ? [{:since_id => @since_id}] : []
249
+ statuses = Termtter::API.twitter.friends_timeline(*args)
250
+ unless statuses.empty?
251
+ print "\e[1K\e[0G" unless win?
252
+ @since_id = statuses[0].id
253
+ output(statuses, :update_friends_timeline)
254
+ Readline.refresh_line
255
+ end
256
+ rescue OpenURI::HTTPError => e
257
+ if e.message == '401 Unauthorized'
258
+ puts 'Could not login'
259
+ puts 'plese check your account settings'
260
+ exit!
261
+ end
262
+ rescue => e
263
+ handle_error(e)
264
+ end
265
+ }
266
+ )
267
+
268
+ add_task(:name => :update_timeline, :interval => config.update_interval, :after => config.update_interval) do
269
+ call_commands('_update_timeline')
270
+ end
271
+
272
+ call_commands('_update_timeline')
273
+ end
274
+
275
+ def trap_setting()
276
+ begin
277
+ stty_save = `stty -g`.chomp
278
+ trap("INT") do
279
+ begin
280
+ system "stty", stty_save
281
+ ensure
282
+ exit
283
+ end
284
+ end
285
+ rescue Errno::ENOENT
286
+ end
287
+ end
288
+
289
+ def start_input_thread
290
+ setup_readline()
291
+ trap_setting()
292
+ @input_thread = Thread.new do
293
+ while buf = Readline.readline(ERB.new(config.prompt).result(API.twitter.__send__(:binding)), true)
294
+ Readline::HISTORY.pop if buf.empty?
295
+ begin
296
+ call_commands(buf)
297
+ rescue CommandNotFound => e
298
+ warn "Unknown command \"#{e}\""
299
+ warn 'Enter "help" for instructions'
300
+ rescue => e
301
+ handle_error e
302
+ end
303
+ end
304
+ end
305
+ @input_thread.join
306
+ end
307
+
308
+ def logger
309
+ @logger
310
+ end
311
+
312
+ def setup_logger
313
+ @logger = config.logger || default_logger
314
+ end
315
+
316
+ def default_logger
317
+ logger = Logger.new(STDOUT)
318
+ logger.formatter = lambda { |severity, time, progname, message|
319
+ color =
320
+ case severity
321
+ when /^DEBUG/
322
+ 'blue'
323
+ when /^INFO/
324
+ 'cyan'
325
+ when /^WARN/
326
+ 'magenta'
327
+ when /^ERROR/
328
+ 'red'
329
+ when /^FATAL/
330
+ 'on_red'
331
+ else
332
+ 'white'
333
+ end
334
+ TermColor.parse("<#{color}>" + TermColor.escape("[#{severity}] #{message}\n") + "</#{color}>")
335
+ }
336
+ logger
337
+ end
338
+
339
+ def run
340
+ load_default_plugins()
341
+ load_config()
342
+ Termtter::API.setup()
343
+ setup_logger()
344
+ post_config_load()
345
+
346
+ call_hooks(:initialize)
347
+
348
+ setup_update_timeline_task()
349
+
350
+ @task_manager.run()
351
+ start_input_thread()
352
+ end
353
+
354
+ def handle_error(e)
355
+ if logger
356
+ logger.error("#{e.class.to_s}: #{e.message}")
357
+ logger.error(e.backtrace.join("\n")) if config.devel
358
+ else
359
+ raise e
360
+ end
361
+ get_hooks(:on_error).each {|hook| hook.call(e) }
362
+ rescue Exception => e
363
+ puts "Error: #{e}"
364
+ puts e.backtrace.join("\n")
365
+ end
366
+ end
367
+ end
368
+ end
369
+
370
+ Termtter::Client.init
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+
5
+ class Command
6
+
7
+ attr_accessor :name, :aliases, :exec_proc, :completion_proc, :help
8
+
9
+ # args
10
+ # name: Symbol as command name
11
+ # aliases: Array of command alias (ex. ['u', 'up'])
12
+ # exec_proc: Proc for procedure of the command. If need the proc must return object for hook.
13
+ # completion_proc: Proc for input completion. The proc must return Array of candidates (Optional)
14
+ # help: help text for the command (Optional)
15
+ def initialize(args)
16
+ raise ArgumentError, ":name is not given." unless args.has_key?(:name)
17
+ args[:exec_proc] ||= args[:exec]
18
+ args[:completion_proc] ||= args[:completion]
19
+ args[:aliases] ||= [args[:alias]].compact
20
+
21
+ cfg = {
22
+ :aliases => [],
23
+ :exec_proc => lambda {|arg| },
24
+ :comletion_proc => lambda {|command, arg| [] }
25
+ }.merge(args)
26
+
27
+ set cfg
28
+ end
29
+
30
+ def set(cfg)
31
+ @name = cfg[:name].to_sym
32
+ @aliases = cfg[:aliases].map {|e| e.to_sym }
33
+ @exec_proc = cfg[:exec_proc]
34
+ @completion_proc = cfg[:completion_proc]
35
+ @help = cfg[:help]
36
+ end
37
+
38
+ def complement(input)
39
+ if match?(input) && input =~ /^[^\s]+\s/
40
+ if completion_proc
41
+ command_str, command_arg = Command.split_command_line(input)
42
+ [completion_proc.call(command_str, command_arg || '')].flatten.compact
43
+ else
44
+ []
45
+ end
46
+ else
47
+ [name.to_s, aliases.to_s].grep(/^#{Regexp.quote(input)}/)
48
+ end
49
+ end
50
+
51
+ def call(cmd = nil, arg = nil, original_text = nil)
52
+ arg = case arg
53
+ when nil
54
+ ''
55
+ when String
56
+ arg
57
+ else
58
+ raise ArgumentError, 'arg should be String or nil'
59
+ end
60
+ exec_proc.call(arg)
61
+ end
62
+
63
+ def match?(input)
64
+ (pattern =~ input) != nil
65
+ end
66
+
67
+ def pattern
68
+ commands_regex = commands.map {|name| Regexp.quote(name.to_s) }.join('|')
69
+ /^((#{commands_regex})|(#{commands_regex})\s+(.*?))\s*$/
70
+ end
71
+
72
+ def commands
73
+ [name] + aliases
74
+ end
75
+
76
+ def self.split_command_line(line)
77
+ line.strip.split(/\s+/, 2)
78
+ end
79
+ end
80
+ end