termtter 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/History.txt +4 -0
  2. data/README.rdoc +97 -0
  3. data/Rakefile +46 -0
  4. data/bin/kill_termtter +22 -0
  5. data/bin/termtter +7 -0
  6. data/lib/filter/en2ja.rb +11 -0
  7. data/lib/filter/english.rb +8 -0
  8. data/lib/filter/expand-tinyurl.rb +24 -0
  9. data/lib/filter/fib.rb +15 -0
  10. data/lib/filter/ignore.rb +19 -0
  11. data/lib/filter/reply.rb +8 -0
  12. data/lib/filter/reverse.rb +13 -0
  13. data/lib/filter/url_addspace.rb +16 -0
  14. data/lib/filter/yhara.rb +20 -0
  15. data/lib/plugin/april_fool.rb +15 -0
  16. data/lib/plugin/bomb.rb +29 -0
  17. data/lib/plugin/clear.rb +14 -0
  18. data/lib/plugin/confirm.rb +9 -0
  19. data/lib/plugin/cool.rb +10 -0
  20. data/lib/plugin/devel.rb +13 -0
  21. data/lib/plugin/english.rb +59 -0
  22. data/lib/plugin/erb.rb +17 -0
  23. data/lib/plugin/favorite.rb +75 -0
  24. data/lib/plugin/fib.rb +8 -0
  25. data/lib/plugin/filter.rb +69 -0
  26. data/lib/plugin/follow.rb +56 -0
  27. data/lib/plugin/graduatter.rb +9 -0
  28. data/lib/plugin/grass.rb +27 -0
  29. data/lib/plugin/group.rb +60 -0
  30. data/lib/plugin/growl.rb +62 -0
  31. data/lib/plugin/hatebu.rb +59 -0
  32. data/lib/plugin/history.rb +82 -0
  33. data/lib/plugin/keyword.rb +18 -0
  34. data/lib/plugin/log.rb +63 -0
  35. data/lib/plugin/modify_arg_hook_sample.rb +7 -0
  36. data/lib/plugin/msagent.rb +26 -0
  37. data/lib/plugin/multi_reply.rb +36 -0
  38. data/lib/plugin/notify-send.rb +17 -0
  39. data/lib/plugin/otsune.rb +21 -0
  40. data/lib/plugin/outputz.rb +35 -0
  41. data/lib/plugin/pause.rb +3 -0
  42. data/lib/plugin/plugin.rb +53 -0
  43. data/lib/plugin/post_exec_hook_sample.rb +9 -0
  44. data/lib/plugin/pre_exec_hook_sample.rb +9 -0
  45. data/lib/plugin/primes.rb +23 -0
  46. data/lib/plugin/quicklook.rb +38 -0
  47. data/lib/plugin/reblog.rb +40 -0
  48. data/lib/plugin/reload.rb +3 -0
  49. data/lib/plugin/say.rb +24 -0
  50. data/lib/plugin/scrape.rb +41 -0
  51. data/lib/plugin/screen.rb +24 -0
  52. data/lib/plugin/shell.rb +14 -0
  53. data/lib/plugin/sl.rb +48 -0
  54. data/lib/plugin/spam.rb +9 -0
  55. data/lib/plugin/standard_plugins.rb +269 -0
  56. data/lib/plugin/stdout.rb +62 -0
  57. data/lib/plugin/system_status.rb +33 -0
  58. data/lib/plugin/translation.rb +28 -0
  59. data/lib/plugin/update_editor.rb +53 -0
  60. data/lib/plugin/uri-open.rb +69 -0
  61. data/lib/plugin/wassr_post.rb +22 -0
  62. data/lib/plugin/yhara.rb +148 -0
  63. data/lib/plugin/yonda.rb +20 -0
  64. data/lib/termtter/api.rb +14 -0
  65. data/lib/termtter/client.rb +399 -0
  66. data/lib/termtter/command.rb +77 -0
  67. data/lib/termtter/connection.rb +39 -0
  68. data/lib/termtter/hook.rb +18 -0
  69. data/lib/termtter/status.rb +26 -0
  70. data/lib/termtter/task.rb +16 -0
  71. data/lib/termtter/task_manager.rb +116 -0
  72. data/lib/termtter/twitter.rb +173 -0
  73. data/lib/termtter/user.rb +13 -0
  74. data/lib/termtter/version.rb +3 -0
  75. data/lib/termtter.rb +157 -0
  76. data/spec/plugin/cool_spec.rb +10 -0
  77. data/spec/plugin/fib_spec.rb +16 -0
  78. data/spec/plugin/filter_spec.rb +18 -0
  79. data/spec/plugin/plugin_spec.rb +25 -0
  80. data/spec/plugin/shell_spec.rb +10 -0
  81. data/spec/plugin/spam_spec.rb +17 -0
  82. data/spec/plugin/standard_plugins_spec.rb +31 -0
  83. data/spec/spec_helper.rb +4 -0
  84. data/spec/termtter/client_spec.rb +175 -0
  85. data/spec/termtter/command_spec.rb +161 -0
  86. data/spec/termtter/task_manager_spec.rb +78 -0
  87. data/spec/termtter/task_spec.rb +22 -0
  88. data/spec/termtter/user_spec.rb +27 -0
  89. data/spec/termtter_spec.rb +43 -0
  90. data/test/friends_timeline.json +5 -0
  91. data/test/search.json +8 -0
  92. data/test/test_termtter.rb +86 -0
  93. metadata +177 -0
@@ -0,0 +1,399 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class CommandNotFound < StandardError; end
5
+
6
+ module Client
7
+
8
+ class << self
9
+
10
+ def init
11
+ @@hooks = []
12
+ @@new_hooks = {}
13
+ @@commands = {}
14
+ @@new_commands = {}
15
+ @@completions = []
16
+ @@filters = []
17
+ @@helps = []
18
+ @@since_id = nil
19
+ @@main_thread = nil
20
+ @@input_thread = nil
21
+ @@task_manager = Termtter::TaskManager.new
22
+ end
23
+
24
+ def public_storage
25
+ @@public_storage ||= {}
26
+ end
27
+
28
+ %w[hook completion filter].each do |n|
29
+ eval <<-EOF
30
+ def add_#{n}(&b)
31
+ @@#{n}s << b
32
+ end
33
+ EOF
34
+ end
35
+
36
+ # Deprecated
37
+ # FIXME: delete when become unnecessary
38
+ def add_command(regex, &block)
39
+ warn "Termtter:Client.add_command method will be removed. Use Termtter::Client.register_command() instead. (#{caller.first})"
40
+ @@commands[regex] = block
41
+ end
42
+
43
+ def register_hook(arg)
44
+ hook = case arg
45
+ when Hook
46
+ arg
47
+ when Hash
48
+ Hook.new(arg)
49
+ else
50
+ raise ArgumentError, 'must be given Termtter::Hook or Hash'
51
+ end
52
+ @@new_hooks[hook.name] = hook
53
+ end
54
+
55
+ def get_hook(name)
56
+ @@new_hooks[name]
57
+ end
58
+
59
+ def get_hooks(point)
60
+ @@new_hooks.values.select do |hook|
61
+ hook.match?(point)
62
+ end
63
+ end
64
+
65
+ def register_command(arg)
66
+ command = case arg
67
+ when Command
68
+ arg
69
+ when Hash
70
+ Command.new(arg)
71
+ else
72
+ raise ArgumentError, 'must be given Termtter::Command or Hash'
73
+ end
74
+ @@new_commands[command.name] = command
75
+ end
76
+
77
+ def get_command(name)
78
+ @@new_commands[name]
79
+ end
80
+
81
+ def register_macro(name, macro, options = {})
82
+ command = {
83
+ :name => name.to_sym,
84
+ :exec_proc => lambda {|arg| call_commands(macro % arg)}
85
+ }.merge(options)
86
+ register_command(command)
87
+ end
88
+
89
+ def add_help(name, desc)
90
+ @@helps << [name, desc]
91
+ end
92
+
93
+ %w[hooks commands completions helps filters].each do |n|
94
+ eval <<-EOF
95
+ def clear_#{n}
96
+ @@#{n}.clear
97
+ end
98
+ EOF
99
+ end
100
+
101
+ # memo: each filter must return Array of Status
102
+ def apply_filters(statuses)
103
+ filtered = statuses.map{|s| s.dup }
104
+ @@filters.each do |f|
105
+ filtered = f.call(filtered)
106
+ end
107
+ filtered
108
+ rescue => e
109
+ handle_error(e)
110
+ statuses
111
+ end
112
+
113
+ def do_hooks(statuses, event)
114
+ @@hooks.each do |h|
115
+ begin
116
+ h.call(statuses.dup, event, Termtter::API.twitter)
117
+ rescue => e
118
+ handle_error(e)
119
+ end
120
+ end
121
+ end
122
+
123
+ # return last hook return value
124
+ def call_new_hooks(point, *args)
125
+ result = nil
126
+ get_hooks(point).each {|hook|
127
+ break if result == false # interrupt if hook return false
128
+ result = hook.exec_proc.call(*args)
129
+ }
130
+ return result
131
+ rescue => e
132
+ if point.to_sym == :on_error
133
+ raise
134
+ else
135
+ handle_error(e)
136
+ end
137
+ end
138
+
139
+ # TODO: delete argument "tw" when unnecessary
140
+ def call_hooks(statuses, event, tw = nil)
141
+ do_hooks(statuses, :pre_filter)
142
+ do_hooks(apply_filters(statuses), event)
143
+ end
144
+
145
+ def call_commands(text, tw = nil)
146
+ return if text.empty?
147
+
148
+ command_found = false
149
+ @@commands.each do |key, command|
150
+ if key =~ text
151
+ command_found = true
152
+ @@task_manager.invoke_and_wait do
153
+ command.call($~, Termtter::API.twitter)
154
+ end
155
+ end
156
+ end
157
+
158
+ @@new_commands.each do |key, command|
159
+ command_info = command.match?(text)
160
+ if command_info
161
+ command_found = true
162
+ input_command, arg = *command_info
163
+
164
+ modified_arg = call_new_hooks("modify_arg_for_#{command.name.to_s}", input_command, arg) || arg || ''
165
+
166
+ @@task_manager.invoke_and_wait do
167
+ pre_exec_hook_result = call_new_hooks("pre_exec_#{command.name.to_s}", input_command, modified_arg)
168
+ next if pre_exec_hook_result == false
169
+
170
+ # exec command
171
+ result = command.execute(modified_arg)
172
+ if result
173
+ call_new_hooks("post_exec_#{command.name.to_s}", input_command, modified_arg, result)
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ raise CommandNotFound unless command_found
180
+ end
181
+
182
+ def pause
183
+ @@task_manager.pause
184
+ end
185
+
186
+ def resume
187
+ @@task_manager.resume
188
+ end
189
+
190
+ def add_task(*arg, &block)
191
+ @@task_manager.add_task(*arg, &block)
192
+ end
193
+
194
+ def exit
195
+ puts 'finalizing...'
196
+
197
+ call_hooks([], :exit)
198
+ call_new_hooks(:exit)
199
+ @@task_manager.kill
200
+ @@main_thread.kill if @@main_thread
201
+ @@input_thread.kill if @@input_thread
202
+ end
203
+
204
+ def load_default_plugins
205
+ plugin 'standard_plugins'
206
+ plugin 'stdout'
207
+ configatron.set_default(:devel, false)
208
+ if configatron.devel
209
+ plugin 'devel'
210
+ end
211
+ end
212
+
213
+ def load_config
214
+ conf_file = File.expand_path('~/.termtter')
215
+ if File.exist? conf_file
216
+ wrap_require do
217
+ load conf_file
218
+ end
219
+ else
220
+ ui = create_highline
221
+ username = ui.ask('your twitter username: ')
222
+ password = ui.ask('your twitter password: ') { |q| q.echo = false }
223
+
224
+ File.open(File.expand_path('~/.termtter'), 'w') {|io|
225
+ plugins = Dir.glob(File.dirname(__FILE__) + "/../lib/plugin/*.rb").map {|f|
226
+ f.match(%r|lib/plugin/(.*?).rb$|)[1]
227
+ }
228
+ plugins -= %w[stdout standard_plugins]
229
+ plugins.each do |p|
230
+ io.puts "#plugin '#{p}'"
231
+ end
232
+
233
+ io.puts
234
+ io.puts "configatron.user_name = '#{username}'"
235
+ io.puts "configatron.password = '#{password}'"
236
+ io.puts "#configatron.update_interval = 120"
237
+ io.puts "#configatron.proxy.host = 'proxy host'"
238
+ io.puts "#configatron.proxy.port = '8080'"
239
+ io.puts "#configatron.proxy.user_name = 'proxy user'"
240
+ io.puts "#configatron.proxy.password = 'proxy password'"
241
+ io.puts
242
+ io.puts "# vim: set filetype=ruby"
243
+ }
244
+ puts "generated: ~/.termtter"
245
+ puts "enjoy!"
246
+ wrap_require do
247
+ load conf_file
248
+ end
249
+ end
250
+ end
251
+
252
+ def setup_readline
253
+ Readline.basic_word_break_characters= "\t\n\"\\'`><=;|&{("
254
+ Readline.completion_proc = lambda {|input|
255
+ begin
256
+ # FIXME: when migrate to Termtter::Command
257
+ completions = @@completions.map {|completion|
258
+ completion.call(input)
259
+ }
260
+ completions += @@new_commands.map {|name, command|
261
+ command.complement(input)
262
+ }
263
+ completions.flatten.compact
264
+ rescue => e
265
+ handle_error(e)
266
+ end
267
+ }
268
+ vi_or_emacs = configatron.editing_mode
269
+ unless vi_or_emacs.empty?
270
+ Readline.__send__("#{vi_or_emacs}_editing_mode")
271
+ end
272
+ end
273
+
274
+ def setup_update_timeline_task()
275
+ register_command(
276
+ :name => :_update_timeline,
277
+ :exec_proc => lambda {|arg|
278
+ begin
279
+ statuses = Termtter::API.twitter.get_friends_timeline(@@since_id)
280
+ unless statuses.empty?
281
+ @@since_id = statuses[0].id
282
+ end
283
+ print "\e[1K\e[0G" if !statuses.empty? && !win?
284
+ call_hooks(statuses, :update_friends_timeline)
285
+ @@input_thread.kill if @@input_thread && !statuses.empty?
286
+ rescue OpenURI::HTTPError => e
287
+ if e.message == '401 Unauthorized'
288
+ puts 'Could not login'
289
+ puts 'plese check your account settings'
290
+ exit!
291
+ end
292
+ end
293
+ }
294
+ )
295
+
296
+ add_task(:name => :update_timeline, :interval => configatron.update_interval) do
297
+ call_commands('_update_timeline')
298
+ end
299
+ end
300
+
301
+ def trap_setting()
302
+ begin
303
+ stty_save = `stty -g`.chomp
304
+ trap("INT") do
305
+ begin
306
+ system "stty", stty_save
307
+ ensure
308
+ exit
309
+ end
310
+ end
311
+ rescue Errno::ENOENT
312
+ end
313
+ end
314
+
315
+ def start_input_thread
316
+ setup_readline()
317
+ trap_setting()
318
+ @@main_thread = Thread.new do
319
+ loop do
320
+ @@input_thread = create_input_thread()
321
+ @@input_thread.join
322
+ end
323
+ end
324
+ @@main_thread.join
325
+ end
326
+
327
+ def run
328
+ puts 'initializing...'
329
+
330
+ load_default_plugins()
331
+ load_config()
332
+ Termtter::API.setup()
333
+
334
+ call_hooks([], :initialize)
335
+ call_new_hooks(:initialize)
336
+
337
+ setup_update_timeline_task()
338
+ call_commands('_update_timeline')
339
+
340
+ @@task_manager.run()
341
+ start_input_thread()
342
+ end
343
+
344
+ def create_input_thread()
345
+ Thread.new do
346
+ while buf = Readline.readline(ERB.new(configatron.prompt).result(API.twitter.__send__(:binding)), true)
347
+ Readline::HISTORY.pop if /^(u|update)\s+(.+)$/ =~ buf
348
+ begin
349
+ call_commands(buf)
350
+ rescue CommandNotFound => e
351
+ puts "Unknown command \"#{buf}\""
352
+ puts 'Enter "help" for instructions'
353
+ end
354
+ end
355
+ exit # exit when press Control-D
356
+ end
357
+ end
358
+
359
+ def create_highline
360
+ HighLine.track_eof = false
361
+ if $stdin.respond_to?(:getbyte) # for ruby1.9
362
+ require 'delegate'
363
+ stdin_for_highline = SimpleDelegator.new($stdin)
364
+ def stdin_for_highline.getc
365
+ getbyte
366
+ end
367
+ else
368
+ stdin_for_highline = $stdin
369
+ end
370
+ return HighLine.new(stdin_for_highline)
371
+ end
372
+
373
+ def handle_error(e)
374
+ call_new_hooks("on_error", e)
375
+ rescue => e
376
+ puts "Error: #{e}"
377
+ puts e.backtrace.join("\n")
378
+ end
379
+
380
+ def wrap_require
381
+ # FIXME: delete this method after the major version up
382
+ alias original_require require
383
+ def require(s)
384
+ if %r|^termtter/(.*)| =~ s
385
+ puts "[WARNING] use plugin '#{$1}' instead of require"
386
+ puts " Such a legacy .termtter file will not be supported until version 1.0.0"
387
+ s = "plugin/#{$1}"
388
+ end
389
+ original_require s
390
+ end
391
+ yield
392
+ alias require original_require
393
+ end
394
+ private :wrap_require
395
+ end
396
+ end
397
+ end
398
+
399
+ Termtter::Client.init
@@ -0,0 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class Command
5
+ attr_accessor :name, :aliases, :exec_proc, :completion_proc, :help
6
+
7
+ # args
8
+ # name: Symbol as command name
9
+ # aliases: Array of command alias (ex. ['u', 'up'])
10
+ # exec_proc: Proc for procedure of the command. If need the proc must return object for hook.
11
+ # completion_proc: Proc for input completion. The proc must return Array of candidates (Optional)
12
+ # help: help text for the command (Optional)
13
+ def initialize(args)
14
+ raise ArgumentError, ":name is not given." unless args.has_key?(:name)
15
+ @name = args[:name].to_sym
16
+ @aliases = args[:aliases] ? args[:aliases].map {|i| i.to_sym } : []
17
+ @exec_proc = args[:exec_proc] || lambda {|arg|}
18
+ @completion_proc = args[:completion_proc] || lambda {|command, arg| [] }
19
+ @help = args[:help]
20
+ end
21
+
22
+ def complement(input)
23
+ command_info = match?(input)
24
+ if command_info
25
+ [completion_proc.call(command_info[0], command_info[1] || '')].flatten.compact
26
+ else
27
+ [name.to_s].grep(/^#{Regexp.quote(input)}/)
28
+ end
29
+ end
30
+
31
+ # MEMO: Termtter:Client からはこのメソッドを呼び出すことになると思う。
32
+ def exec_if_match(input)
33
+ command_info = match?(input)
34
+ if command_info
35
+ result = execute(command_info[1])
36
+ unless result.nil?
37
+ return result
38
+ else
39
+ return true
40
+ end
41
+ else
42
+ return nil
43
+ end
44
+ end
45
+
46
+ # return array like [command, arg]
47
+ def match?(input)
48
+ if pattern =~ input
49
+ [$2 || $3, $4] # $2 or $3 => command, $4 => argument
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ def execute(arg)
56
+ arg = case arg
57
+ when nil
58
+ ''
59
+ when String
60
+ arg
61
+ else
62
+ raise ArgumentError, 'arg should be String or nil'
63
+ end
64
+ exec_proc.call(arg)
65
+ end
66
+
67
+ def pattern
68
+ commands_regex = commands.map {|i| Regexp.quote(i.to_s) }.join('|')
69
+ /^((#{commands_regex})|(#{commands_regex})\s+(.*?))\s*$/
70
+ end
71
+
72
+ def commands
73
+ aliases.unshift(name)
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class Connection
5
+ attr_reader :protocol, :port, :proxy_uri
6
+
7
+ def initialize
8
+ @proxy_host = configatron.proxy.host
9
+ @proxy_port = configatron.proxy.port
10
+ @proxy_user = configatron.proxy.user_name
11
+ @proxy_password = configatron.proxy.password
12
+ @proxy_uri = nil
13
+ @enable_ssl = configatron.enable_ssl
14
+ @protocol = "http"
15
+ @port = 80
16
+
17
+ unless @proxy_host.empty?
18
+ @http_class = Net::HTTP::Proxy(@proxy_host, @proxy_port,
19
+ @proxy_user, @proxy_password)
20
+ @proxy_uri = "http://" + @proxy_host + ":" + @proxy_port + "/"
21
+ else
22
+ @http_class = Net::HTTP
23
+ end
24
+
25
+ if @enable_ssl
26
+ @protocol = "https"
27
+ @port = 443
28
+ end
29
+ end
30
+
31
+ def start(host, port, &block)
32
+ http = @http_class.new(host, port)
33
+ http.use_ssl = @enable_ssl
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
35
+ http.start(&block)
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class Hook
5
+ attr_accessor :name, :points, :exec_proc
6
+
7
+ def initialize(args)
8
+ raise ArgumentError, ":name is not given." unless args.has_key?(:name)
9
+ @name = args[:name].to_sym
10
+ @points = args[:points] ? args[:points].map {|i| i.to_sym } : []
11
+ @exec_proc = args[:exec_proc] || lambda {}
12
+ end
13
+
14
+ def match?(point)
15
+ points.include?(point.to_sym)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class Status
5
+ %w(
6
+ id text created_at truncated
7
+ in_reply_to_status_id in_reply_to_user_id in_reply_to_screen_name
8
+ user_id user_name user_screen_name user_url user_profile_image_url
9
+ ).each do |attr|
10
+ attr_accessor attr.to_sym
11
+ end
12
+
13
+ def eql?(other); self.id == other.id end
14
+ def hash; self.id end
15
+
16
+ def english?
17
+ self.class.english?(self.text)
18
+ end
19
+
20
+ # english? :: String -> Boolean
21
+ def self.english?(message)
22
+ /[一-龠]+|[ぁ-ん]+|[ァ-ヴー]+|[a-zA-Z0-9]+/ !~ message
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class Task
5
+ attr_accessor :name, :exec_at, :exec_proc, :interval
6
+ def initialize(args = {}, &block)
7
+ @name = args[:name]
8
+ @exec_at = Time.now + (args[:after] || 0)
9
+ @interval = args[:interval]
10
+ @exec_proc = block || lambda {}
11
+ end
12
+ def execute
13
+ exec_proc.call
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ class TaskManager
5
+
6
+ INTERVAL = 1
7
+
8
+ def initialize()
9
+ @tasks = {}
10
+ @work = true
11
+ @mutex = Mutex.new
12
+ @pause = false
13
+ end
14
+
15
+ def pause
16
+ @pause = true
17
+ end
18
+
19
+ def resume
20
+ @pause = false
21
+ end
22
+
23
+ def kill
24
+ @work = false
25
+ end
26
+
27
+ def run
28
+ Thread.new do
29
+ while @work
30
+ step unless @pause
31
+ sleep INTERVAL
32
+ end
33
+ end
34
+ end
35
+
36
+ def step
37
+ pull_due_tasks().each do |task|
38
+ invoke_and_wait do
39
+ task.execute
40
+ end
41
+ end
42
+ end
43
+
44
+ def invoke_later
45
+ Thread.new do
46
+ invoke_and_wait do
47
+ yield
48
+ end
49
+ end
50
+ end
51
+
52
+ def invoke_and_wait(&block)
53
+ synchronize do
54
+ begin
55
+ yield
56
+ rescue => e
57
+ Termtter::Client.handle_error(e)
58
+ end
59
+ end
60
+ end
61
+
62
+ def add_task(args = {}, &block)
63
+ synchronize do
64
+ task = Task.new(args, &block)
65
+ @tasks[task.name || task.object_id] = task
66
+ end
67
+ end
68
+
69
+ def get_task(key)
70
+ synchronize do
71
+ @tasks[key]
72
+ end
73
+ end
74
+
75
+ def delete_task(key)
76
+ synchronize do
77
+ @tasks.delete(key)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def synchronize
84
+ unless Thread.current == @thread_in_sync
85
+ @mutex.synchronize do
86
+ @thread_in_sync = Thread.current
87
+ yield
88
+ end
89
+ else
90
+ yield
91
+ end
92
+ end
93
+
94
+ def pull_due_tasks()
95
+ synchronize do
96
+ time_now = Time.now
97
+ due_tasks = []
98
+ @tasks.delete_if do |key, task|
99
+ if task.exec_at <= time_now
100
+ due_tasks << task
101
+ if task.interval
102
+ task.exec_at = time_now + task.interval
103
+ false
104
+ else
105
+ true
106
+ end
107
+ else
108
+ false
109
+ end
110
+ end
111
+ return due_tasks
112
+ end
113
+ end
114
+
115
+ end
116
+ end