rudy 0.3.2 → 0.4.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.
data/bin/rudy-ec2 ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Rudy -- Your friend in staging and deploying to EC2
4
+ #
5
+ # See rudy -h for usage
6
+ #
7
+
8
+ #
9
+ # No Ruby 1.9.1 support. Only 1.8.x for now :[
10
+ unless RUBY_VERSION < "1.9"
11
+ puts "Sorry! We're using the right_aws gem and it doesn't support Ruby 1.9 (md5 error)."
12
+ exit 1
13
+ else
14
+ require 'rubygems'
15
+ end
16
+
17
+ RUDY_HOME = File.join(File.dirname(__FILE__), '..')
18
+ RUDY_LIB = File.join(RUDY_HOME, 'lib')
19
+ $:.unshift RUDY_LIB # Put our local lib in first place
20
+
21
+ require 'drydock'
22
+ extend Drydock
23
+
24
+ project "Rudy" # This also runs require 'ruby'
25
+
26
+ debug :on
27
+
28
+ capture :stderr
29
+
30
+ global :A, :accesskey, String, "AWS Access Key"
31
+ global :S, :secretkey, String, "AWS Secret Access Key"
32
+ global :R, :region, String, "Connect to a specific EC2 region (default: #{Rudy::DEFAULT_REGION})"
33
+ global :z, :zone, String, "Connect to a specific EC2 zone (default: #{Rudy::DEFAULT_ZONE})"
34
+ global :q, :quiet, "Run with less output"
35
+ global :v, :verbose, "Increase verbosity of output (i.e. -v or -vv or -vvv)" do
36
+ @verbose ||= 0
37
+ @verbose += 1
38
+ end
39
+ global :V, :version, "Display version number" do
40
+ puts "Rudy version: #{Rudy::VERSION}"
41
+ exit 0
42
+ end
43
+
44
+
45
+
46
+
47
+
48
+ # ----------------------------------- AMAZON EC2 COMMANDS --------
49
+ # ------------------------------------------------------------------
50
+
51
+ usage "rudy [global options] addresses [-A address instance ID]"
52
+ desc "Manage Amazon Elastic IP addresses"
53
+ argv :address, :instanceid
54
+ action :A, :associate, "Associate an address to a running instance"
55
+ command :addresses => Rudy::Command::Addresses
56
+ command_alias :addresses, :address
57
+
58
+
59
+ usage "rudy images [-C -i name [-b bucket -a account]] [-D AMI-ID]"
60
+ desc "Manage EC2 Machine Images (AMIs)"
61
+ option :a, :account, String, "Your Amazon Account Number"
62
+ option :i, :image_name, String, "The name of the image"
63
+ option :b, :bucket_name, String, "The name of the bucket that will store the image"
64
+ action :C, :create, "Create an image"
65
+ #action :P, :prepare, "Prepare a running instance to be used as an image"
66
+ action :D, :destroy, "Deregister an image (currently _does not_ remove images files from S3)"
67
+ argv :ami
68
+ command :images => Rudy::Command::Images
69
+ command_alias :images, :image
70
+
71
+
72
+ desc "Manage EC2 Volumes"
73
+ action :D, :destroy, "Destroy a volume"
74
+ argv :vol
75
+ command :volumes => Rudy::Command::Volumes
76
+ command_alias :volumes, :volume
77
+
78
+
79
+ usage "rudy [global options] instances [-D] [-S -i image ID] [instance ID OR group name]"
80
+ desc "Manage EC2 Instances"
81
+ option :all, "Display all instances"
82
+ option :a, :address, String, "Amazon elastic IP"
83
+ option :i, :image, String, "Amazon machine image ID (ami)"
84
+ #option :v, :volume, String, "Amazon volume ID"
85
+ action :D, :destroy, "Destroy the given instance IDs. All data will be lost!"
86
+ #action :S, :start, "Start an instance"
87
+ #action :R, :restart, "Restart an instance"
88
+ argv :filter
89
+ command :instances => Rudy::Command::Instances
90
+ command_alias :instances, :instance
91
+
92
+
93
+ usage "rudy [global options] groups [-C] [-a IP addresses] [-p ports] [group name]"
94
+ desc "Manage EC2 Security Groups"
95
+ option :all, "Display all security groups"
96
+ option :r, :protocols, Array, "Comma-separated list of protocols. One of: tcp (default), udp, icmp"
97
+ option :p, :ports, Array, "List of comma-separated ports to authorize (default: 22,80,443)"
98
+ option :a, :addresses, Array, "List of comma-separated IP addresses to authorize (default: your external IP)"
99
+ action :C, :create, "Create a security group"
100
+ action :D, :destroy, "Destroy a security group"
101
+ action :M, :modify, "Modify a security group"
102
+ argv :group
103
+ command :groups => Rudy::Command::Groups
104
+ command_alias :groups, :group
105
+
106
+
107
+
data/lib/console.rb CHANGED
@@ -1,341 +1,385 @@
1
-
1
+ #---
2
2
  # Adapted from: http://github.com/oneup/ruby-console/tree/tput
3
+ # See: http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
4
+ # See: man terminfo
5
+ #+++
3
6
 
4
- if __FILE__ == $0
5
- puts "This is a module, don't run it from the command line."
6
- exit
7
- end
8
7
 
9
8
  class String
10
- def color(*arg) # colorize a string
11
- Console.color(arg) +
9
+
10
+ # +col+, +bgcol+, and +attribute+ are symbols corresponding
11
+ # to Console::COLOURS, Console::BGCOLOURS, and Console::ATTRIBUTES.
12
+ # Returns the string in the format attributes + string + defaults.
13
+ #
14
+ # "MONKEY_JUNK".colour(:blue, :white, :blink) # => "\e[34;47;5mMONKEY_JUNK\e[39;49;0m"
15
+ #
16
+ def colour(col, bgcol = nil, attribute = nil)
17
+ Console.style(col, bgcol, attribute) +
12
18
  self +
13
- Console.color(:default)
19
+ Console.style(:default, :default, :default)
14
20
  end
15
- def printAt(*arg)
16
- row = 0
17
- col = 0
18
- if not arg.is_a?(Array)
19
- arg = [arg]
20
- end
21
- if arg.length > 0
22
- row = arg[0]
23
- end
24
- if arg.length > 1
25
- col = arg[1]
26
- end
27
- Cursor.save
28
- Cursor.position = row, col
29
- print self
30
- Cursor.restore
21
+ alias :color :colour
22
+
23
+ # See colour
24
+ def bgcolour(bgcol = :default)
25
+ Console.style(nil, bgcol, nil) +
26
+ self +
27
+ Console.style(nil, :default, nil)
31
28
  end
32
- end
33
-
34
- class Console
35
- def self.clear
36
- system "tput clear"
29
+ alias :bgcolor :bgcolour
30
+
31
+ # See colour
32
+ def att(a = :default)
33
+ Console.style(nil, nil, a) +
34
+ self +
35
+ Console.style(nil, nil, :default)
37
36
  end
38
-
39
- def self.reset
40
- system "tput reset"
37
+
38
+
39
+ # Print the string at +x+ +y+. When +minus+ is any true value
40
+ # the length of the string is subtracted from the value of x
41
+ # before printing.
42
+ def print_at(x=nil, y=nil, minus=false)
43
+ args = {:minus=>minus}
44
+ args[:x] &&= x
45
+ args[:y] &&= y
46
+ Console.print_at(self, args)
41
47
  end
42
48
 
43
- def self.color(*arg) # colorize a string
44
- if arg[0].is_a?(Array)
45
- arg = arg[0]
46
- end
47
- if arg.length == 0
48
- arg = :default, :red, :bg_default
49
- end
50
- attribute = { # mapper for the attributes
51
- :normal => 0,
52
- :bright => 1,
53
- :dim => 2,
54
- :underscore => 4,
55
- :blink => 5,
56
- :reverse => 7,
57
- :hidden => 8,
58
- :default => 0
59
- }
60
- fg_color = { # mapper for the foreground color
61
- :black => 30,
62
- :red => 31,
63
- :green => 32,
64
- :yellow => 33,
65
- :blue => 34,
66
- :magenta => 35,
67
- :cyan => 36,
68
- :white => 37,
69
- :default => 39
70
- }
71
- bg_color = { # mapper for the background color
72
- :bg_black => 40,
73
- :bg_red => 41,
74
- :bg_green => 42,
75
- :bg_yellow => 43,
76
- :bg_blue => 44,
77
- :bg_magenta => 45,
78
- :bg_cyan => 46,
79
- :bg_white => 47,
80
- :bg_default => 49
81
- }
82
- if arg.length > 0 # turn symbols into numbers
83
- arg[0] = attribute[arg[0]] # attributes
84
- end
85
- if arg.length > 1
86
- arg[1] = fg_color[arg[1]] # foreground color
87
- end
88
- if arg.length > 2
89
- arg[2] = bg_color[arg[2]] # background color
90
- end
91
- "\e[#{arg.join(";")}m" # magic ansi escape sequence
49
+ # Returns the string with escape code attributes removed.
50
+ # NOTE: The non-printable attributes count towards the string size.
51
+ # You can use this method to get the "visible" size:
52
+ #
53
+ # "\e[34;47;5mMONKEY_JUNK\e[39;49;0m".noatt.size # => 11
54
+ # "\e[34;47;5mMONKEY_JUNK\e[39;49;0m".size # => 31
55
+ #
56
+ def noatt
57
+ gsub(/\e\[[\d;]*m/, '')
92
58
  end
59
+ end
93
60
 
94
- def self.color=(arg)
95
- if not arg.is_a?(Array)
96
- arg = [arg]
97
- end
98
- if arg.length == 0
99
- arg = :default, :red, :bg_default
100
- end
101
- print self.color(arg)
102
- end
61
+ class Object
103
62
 
104
- def self.width
105
- `tput cols`.chomp.to_i
63
+ # Executes tput +capnam+ with +args+. Returns true if tcap gives
64
+ # 0 exit status and false otherwise.
65
+ #
66
+ # tput :cup, 1, 4
67
+ # $ tput cup 1 4
68
+ #
69
+ def tput(capnam, *args)
70
+ system("tput #{capnam} #{args.flatten.join(' ')}")
106
71
  end
107
-
108
- def self.height
109
- `tput lines`.chomp.to_i
72
+
73
+ # Executes tput +capnam+ with +args+. Returns the output of tput.
74
+ #
75
+ # tput_val :cols # => 16
76
+ # $ tput cols # => 16
77
+ #
78
+ def tput_val(capnam, *args)
79
+ `tput #{capnam} #{args.flatten.join(' ')}`.chomp
110
80
  end
111
81
  end
112
82
 
113
- class Cursor
114
- def self.position
115
- row = ""
116
- col = ""
117
- c = ""
118
-
119
- termsettings = `stty -g`
120
- system("stty raw -echo")
121
- print "\e[6n"
122
- while (c = STDIN.getc.chr) != ";"
123
- if c == "\e" or c == "["
124
- next
125
- else
126
- row += c
127
- end
128
- end
129
- while (c = STDIN.getc.chr) != "R"
130
- col += c
131
- end
132
- system("stty #{termsettings}")
133
-
134
- [row, col]
135
- end
136
83
 
137
- def self.position=(arg)
138
- row = 0
139
- col = 0
140
- if not arg.is_a?(Array)
141
- arg = [arg]
142
- end
143
- if arg.length > 0
144
- row = arg[0]
145
- end
146
- if arg.length > 1
147
- col = arg[1]
148
- end
149
- system "tput cup #{row} #{col}"
150
- end
151
84
 
152
- def self.up(*arg)
153
- if arg.length == 0
154
- arg[0] == 1
85
+ module Console
86
+ extend self
87
+ require 'timeout'
88
+ require 'thread'
89
+
90
+ # ANSI escape sequence numbers for text attributes
91
+ ATTRIBUTES = {
92
+ :normal => 0,
93
+ :bright => 1,
94
+ :dim => 2,
95
+ :underline => 4,
96
+ :blink => 5,
97
+ :reverse => 7,
98
+ :hidden => 8,
99
+ :default => 0,
100
+ }.freeze unless defined? ATTRIBUTES
101
+
102
+ # ANSI escape sequence numbers for text colours
103
+ COLOURS = {
104
+ :black => 30,
105
+ :red => 31,
106
+ :green => 32,
107
+ :yellow => 33,
108
+ :blue => 34,
109
+ :magenta => 35,
110
+ :cyan => 36,
111
+ :white => 37,
112
+ :default => 39,
113
+ :random => 30 + rand(10).to_i
114
+ }.freeze unless defined? COLOURS
115
+
116
+ # ANSI escape sequence numbers for background colours
117
+ BGCOLOURS = {
118
+ :black => 40,
119
+ :red => 41,
120
+ :green => 42,
121
+ :yellow => 43,
122
+ :blue => 44,
123
+ :magenta => 45,
124
+ :cyan => 46,
125
+ :white => 47,
126
+ :default => 49,
127
+ :random => 40 + rand(10).to_i
128
+ }.freeze unless defined? BGCOLOURS
129
+
130
+
131
+ def print_left(str, props={})
132
+ props[:x] ||= 0
133
+ props[:y] ||= Cursor.y
134
+ # print_at("x:#{props[:x]} y:#{props[:y]}", {:x => 0, :y => 10})
135
+ print_at(str, props)
136
+ end
137
+ def print_right(str, props={})
138
+ props[:x] ||= width
139
+ props[:y] ||= Cursor.y
140
+ props[:minus] = true unless props.has_key?(:minus)
141
+ print_at(str, props)
142
+ end
143
+ def print_spaced(*args)
144
+ props = (args.last.is_a? Hash) ? args.pop : {}
145
+ props[:y] = Cursor.y
146
+ chunk_width = (width / args.flatten.size).to_i
147
+ chunk_at = 0
148
+ args.each do |chunk|
149
+ props[:x] = chunk_at
150
+ print_at(chunk.to_s[0, chunk_width], props)
151
+ chunk_at += chunk_width
155
152
  end
156
- print "\e[#{arg[0]}A"
153
+ puts
154
+ end
155
+ def print_center(str, props={})
156
+ props[:x] = ((width - str.noatt.length) / 2).to_i-1
157
+ props[:y] ||= height
158
+ print_at(str, props)
159
+ end
160
+ def print_at(str, props={})
161
+ print_at_lamb = lambda {
162
+ #props[:x] ||= 0
163
+ #props[:y] ||= 0
164
+ props[:minus] = false unless props.has_key?(:minus)
165
+ props[:x] = props[:x]-str.noatt.size if props[:x] && props[:minus] # Subtract the str length from the position
166
+ Cursor.save
167
+ Cursor.move = 0
168
+ print str
169
+ Cursor.restore
170
+ }
171
+ RUBY_VERSION =~ /1.9/ ? Thread.exclusive(&print_at_lamb) : print_at_lamb.call
157
172
  end
158
-
159
- def self.down(*arg)
160
- if arg.length == 0
161
- arg[0] == 1
162
- end
163
- print "\e[#{arg[0]}B"
173
+
174
+ def self.style(col, bgcol=nil, att=nil)
175
+ valdor = []
176
+ valdor << COLOURS[col] if COLOURS.has_key?(col)
177
+ valdor << BGCOLOURS[bgcol] if BGCOLOURS.has_key?(bgcol)
178
+ valdor << ATTRIBUTES[att] if ATTRIBUTES.has_key?(att)
179
+ "\e[#{valdor.join(";")}m" # => \e[8;34;42m
164
180
  end
165
-
166
- def self.right(*arg)
167
- if arg.length == 0
168
- arg[0] == 1
169
- end
170
- system "tput cuf #{arg[0]}"
181
+
182
+ def self.clear
183
+ tput :clear
171
184
  end
172
185
 
173
- def self.left(*arg)
174
- if arg.length == 0
175
- arg[0] == 1
176
- end
177
- system "tput cub #{arg[0]}"
186
+ def reset
187
+ tput :reset
178
188
  end
179
-
180
- def self.save
181
- system "tput sc"
189
+
190
+ def width
191
+ tput_val(:cols).to_i
182
192
  end
183
193
 
184
- def self.restore
185
- system "tput rc"
194
+ def height
195
+ tput_val(:lines).to_i
186
196
  end
187
197
  end
188
198
 
189
- class Win
190
- def initialize
191
- @row = 1
192
- @col = 1
193
- @width = 10
194
- @height = 5
195
- @text = ""
196
- @border = :single
197
- @fg = :default
198
- @bg = :bg_default
199
- @bordercolor = :default
199
+ module Cursor
200
+ extend self
201
+
202
+ # Returns [x,y] for the current cursor position.
203
+ def position
204
+ yx = [0,0]
205
+
206
+ position_lamb = lambda {
207
+ begin
208
+ # NOTE: Can we get cursor position from tput?
209
+ termsettings = `stty -g`
210
+
211
+ # DEBUGGING: The following code works in Ruby 1.9 but not 1.8.
212
+
213
+ system("stty raw -echo")
214
+ print "\e[6n" # Forces output of: \e[49;1R (\e is not printable)
215
+ c = ''
216
+ (pos ||= '') << c while (c = STDIN.getc) != 'R'# NOTE: There must be a better way!
217
+ yx = pos.scan(/(\d+);(\d+)/).flatten
218
+ yx[0] = yx[0].to_i - 1 # It returns 1 for the first column, but we want 0
219
+ yx[1] = yx[1].to_i - 1
220
+ ensure
221
+ system("stty #{termsettings}") # Get out of raw mode
222
+ end
223
+ }
224
+
225
+ RUBY_VERSION =~ /1.9/ ? Thread.exclusive(&position_lamb) : position_lamb.call
226
+ yx.reverse
200
227
  end
201
228
 
202
- def width=(width)
203
- @width = width
204
- end
229
+ def x; position[0]; end
230
+ def y; position[1]; end
205
231
 
206
- def width
207
- @width
232
+ def move=(*args)
233
+ x,y = *args.flatten
234
+ tput(:cup, y, x) # "tput cup" takes y before x
208
235
  end
209
236
 
210
- def height=(height)
211
- @height = height
237
+ def up(n=1)
238
+ tput :cuu, n
212
239
  end
213
240
 
214
- def height
215
- @height
241
+ def down(n=1)
242
+ tput :cud, n
216
243
  end
217
244
 
218
- def text=(text)
219
- @text = text
245
+ def right(x=1)
246
+ tput :cuf, x
220
247
  end
221
248
 
222
- def text
223
- @text
249
+ def left(x=1)
250
+ tput :cub, x
224
251
  end
225
-
226
- def border=(arg)
227
- if not arg.is_a?(Array)
228
- arg = [arg]
229
- end
230
- if arg.length > 0
231
- @border = arg[0]
232
- end
233
- if arg.length > 1
234
- @bordercolor = arg[1]
235
- end
252
+
253
+ def line(n=1)
254
+ tput :il, n
236
255
  end
237
-
238
- def border
239
- [@border, @bordercolor]
256
+
257
+ def save
258
+ tput :sc
240
259
  end
241
260
 
242
- def position=(arg)
243
- if not arg.is_a?(Array)
244
- arg = [arg]
245
- end
246
- if arg.length > 0
247
- @row = arg[0]
248
- end
249
- if arg.length > 1
250
- @col = arg[1]
251
- end
261
+ def restore
262
+ tput :rc
252
263
  end
253
-
254
- def position
255
- [@row, @col]
264
+
265
+ def clear_line
266
+ tput :el
256
267
  end
268
+
269
+ # TODO: replace methods with this kinda thing
270
+ #@@capnames = {
271
+ # :restore => [:rc],
272
+ # :save => [:sc],
273
+ # :clear_line => [:el],
274
+ # :line => [:il, 1, 1],
275
+ #
276
+ # :up => [:cuu, 1, 1],
277
+ # :down => [:cud, 1, 1],
278
+ # :right => [:cuf, 1, 1],
279
+ # :left => [:cub, 1, 1],
280
+ #
281
+ # :move => [:cup, 2, 0, 0]
282
+ #}
283
+ #
284
+ #@@capnames.each_pair do |meth, cap|
285
+ # module_eval <<-RUBY
286
+ # def #{meth}(*args)
287
+ # tput '#{cap[0]}'
288
+ # end
289
+ # RUBY
290
+ #end
291
+
292
+ end
257
293
 
258
- def fg=(fg)
259
- @fg = fg
294
+ class Window
295
+ attr_accessor :row, :col, :width, :height, :text, :fg, :bg
296
+ attr_reader :threads
297
+
298
+ def initialize(*args)
299
+ @row = 1
300
+ @col = 1
301
+ @width = 10
302
+ @height = 5
303
+ @text = ""
304
+ @fg = :default
305
+ @bg = :default
306
+ @threads = []
260
307
  end
261
308
 
262
- def fg
263
- @fg
309
+ def position=(x,y=nil)
310
+ @x = x
311
+ @y = y if y
264
312
  end
265
313
 
266
- def bg=(bg)
267
- @bg = bg
314
+ def position
315
+ [@row, @col]
268
316
  end
269
-
270
- def bg
271
- @bg
317
+
318
+ def self.bar(len, unit='=')
319
+ unit*len
272
320
  end
273
321
 
274
- def drawBorder
275
- single = {
276
- :upper_left => "\u250C",
277
- :upper_right => "\u2510",
278
- :lower_left => "\u2514",
279
- :lower_right => "\u2518",
280
- :horizontal => "\u2500",
281
- :vertical => "\u2502"
282
- }
283
- bold = {
284
- :upper_left => "\u250D",
285
- :upper_right => "\u2511",
286
- :lower_left => "\u2515",
287
- :lower_right => "\u2519",
288
- :horizontal => "\u2501",
289
- :vertical => "\u2503"
290
- }
291
- double = {
292
- :upper_left => "\u2554",
293
- :upper_right => "\u2557",
294
- :lower_left => "\u255A",
295
- :lower_right => "\u255D",
296
- :horizontal => "\u2550",
297
- :vertical => "\u2551"
298
- }
299
- round = {
300
- :upper_left => "\u256D",
301
- :upper_right => "\u256E",
302
- :lower_left => "\u2570",
303
- :lower_right => "\u256F",
304
- :horizontal => "\u2500",
305
- :vertical => "\u2502"
306
- }
307
- mapper = case @border
308
- when :single then single
309
- when :bold then bold
310
- when :double then double
311
- when :round then round
312
- else single
313
- end
314
- (mapper[:upper_left] + mapper[:horizontal] * (@width - 2) + mapper[:upper_right]).color(:normal, @bordercolor, @bg).printAt @row, @col
315
- (mapper[:lower_left] + mapper[:horizontal] * (@width - 2) + mapper[:lower_right]).color(:normal, @bordercolor, @bg).printAt @row + @height - 1, @col
316
- 0.upto(@height - 3) do |i|
317
- (mapper[:vertical] + " " * (@width - 2) + mapper[:vertical]).color(:normal, @bordercolor, @bg).printAt @row + i + 1, @col
322
+
323
+ # Execute the given block every +n+ seconds in a separate thread.
324
+ # The lower limit for +n+ is 1 second.
325
+ # Returns a Thread object.
326
+ def every_n_seconds(n)
327
+ #n = 1 if n < 1
328
+ thread = Thread.new do
329
+
330
+ begin
331
+ while true
332
+ before = Time.now
333
+ yield
334
+ interval = n - (Time.now - before)
335
+ sleep(interval) if interval > 0
336
+ end
337
+ rescue Interrupt
338
+ break
339
+ ensure
340
+ thread
341
+ end
318
342
  end
319
343
  end
320
-
321
- def draw
322
- text = @text.split("\n")
323
- 0.upto(text.length - 1) do |i|
324
- if @border != :none
325
- text[i] = text[i][0, @width - 2]
326
- else
327
- text[i] = text[i][0, @width]
328
- end
344
+
345
+ # Print text to the screen via +type+ every +refresh+ seconds.
346
+ # Print the return value of the block to the screen using the
347
+ # print_+type+ method. +refresh+ is number of seconds to wait
348
+ # +props+ is the hash sent to print_+type+.
349
+ # Returns a Thread object.
350
+ #
351
+ # # Print the time in the upper right corner every second
352
+ # thread1 = Console.static(:right, 1, {:y => 0}) do
353
+ # Time.now.utc.strftime("%Y-%m-%d %H:%M:%S").colour(:blue, :white, :underline)
354
+ # end
355
+ #
356
+ def static(type, refresh=2, props={}, &b)
357
+ meth = "print_#{type}"
358
+ raise "#{meth} is not supported" unless Console.respond_to?(meth)
359
+
360
+ refresh ||= 0
361
+ refreh = refresh.to_s.to_i
362
+
363
+ thread = every_n_seconds(refresh) do
364
+ Console.send(meth, b.call, props.clone)
329
365
  end
330
- if @border == :none
331
- 0.upto((text.length > @height ? @height : text.length) - 1) do |i|
332
- text[i].color(:normal, @fg, @bg).printAt @row + i , @col
366
+
367
+ @threads << thread
368
+
369
+ thread
370
+ end
371
+
372
+ def join_threads
373
+ begin
374
+ @threads.each do |t|
375
+ t.join
333
376
  end
334
- else
335
- drawBorder
336
- 0.upto(@text.length > @height - 3 ? @height - 3 : text.length) do |i|
337
- text[i].to_s.color(:normal, @fg, @bg).printAt @row + i + 1, @col + 1
377
+ rescue Interrupt
378
+ ensure
379
+ @threads.each do |t|
380
+ t.kill
338
381
  end
339
382
  end
340
383
  end
384
+
341
385
  end