buzzcore 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,387 @@
1
+ require 'tmpdir'
2
+ require 'logger'
3
+ require 'pathname'
4
+
5
+ require 'buzzcore/logging'
6
+
7
+ module MiscUtils
8
+
9
+ def self.logger
10
+ @logger || @logger = Logger.new(STDERR)
11
+ end
12
+
13
+ # applies the given block to key/value pairs included/excluded by the given parameters
14
+ # aSource must be a hash, and may contain hashes which will be recursively processed.
15
+ # aInclude/aExclude may be nil, an array of selected keys, or a hash containing arrays of keys for inner hashes of aSource
16
+ # When aInclude/aExclude are a hash, non-hash source values may be selected by passing a value of true. Hash source values
17
+ # will be selected as a whole (all keys) when true is passed.
18
+ # input = { 'a' => 1, 'b' => {'x' => 9, 'y' => 8}, 'c' => 3 }
19
+ # filter_multilevel_hash(input,['a','b'],{'b'=>['y']}) {|h,k,v| h[k] = true}
20
+ # input now = { 'a' => true, 'b' => {'x' => true, 'y' => 8}, 'c' => 3 }
21
+ def self.filter_multilevel_hash(aSource,aInclude=nil,aExclude=nil,&block)
22
+ aSource.each do |key,value|
23
+ next if aInclude.is_a?(Array) and !aInclude.include?(key) # skip if not in aInclude array
24
+ next if aExclude.is_a?(Array) and aExclude.include?(key) # skip if in aExclude array
25
+ next if aExclude.is_a?(Hash) and aExclude[key]==true # skip if in aExclude hash with value=true
26
+ next if aInclude.is_a?(Hash) and !aInclude.include?(key) # skip if not in aInclude hash at all
27
+ if value.is_a?(Hash) # value is hash so recursively apply filter
28
+ filter_multilevel_hash(
29
+ value,
30
+ aInclude.is_a?(Hash) && (f = aInclude[key]) ? f : nil, # pass include array if provided for key
31
+ aExclude.is_a?(Hash) && (f = aExclude[key]) ? f : nil, # pass exclude array if provided for key
32
+ &block
33
+ )
34
+ else
35
+ yield(aSource,key,value)
36
+ end
37
+ end
38
+ end
39
+
40
+ # returns output string if succesful, or integer return code if not, or nil
41
+ def self.execute(aCommand,aWorkingDir=nil,aTimeout=nil,aTimeoutClass=nil)
42
+ return nil unless !aWorkingDir || File.exists?(aWorkingDir)
43
+ begin
44
+ orig_wd = Dir.getwd
45
+ pipe = nil
46
+ result = nil
47
+ Dir.chdir(aWorkingDir) if aWorkingDir
48
+ Timeout.timeout(aTimeout,aTimeoutClass || Timeout::Error) do # nil aTimeout will not time out
49
+ pipe = IO.popen(aCommand)
50
+ logger.debug "command PID:"+pipe.pid.to_s
51
+ result = pipe.read
52
+ end
53
+ ensure
54
+ pipe.close if pipe
55
+ Dir.chdir(orig_wd)
56
+ end
57
+ return result
58
+ end
59
+
60
+ def self.execute_string(aCmdString,aWorkingDir=nil)
61
+ result = nil
62
+ begin
63
+ orig_dir = Dir.pwd
64
+ Dir.chdir(aWorkingDir) if aWorkingDir
65
+ result = `#{aCmdString}`
66
+ ensure
67
+ Dir.chdir(orig_dir) if aWorkingDir
68
+ end
69
+ return result
70
+ end
71
+
72
+ def self.temp_file(aExt=nil,aDir=nil)
73
+ aExt ||= '.tmp'
74
+ File.expand_path(("%08X" % rand(0x3FFFFFFF)) + aExt, aDir||Dir.tmpdir)
75
+ end
76
+
77
+ def self.make_temp_file(aName=nil,aDir=nil,aContent=nil)
78
+ filename = aName ? File.expand_path(aName,aDir || Dir.tmpdir) : temp_file(nil,aDir)
79
+ FileUtils.mkdir_p(File.dirname(filename))
80
+ aContent ||= "content of "+filename
81
+ string_to_file(aContent,filename)
82
+ filename
83
+ end
84
+
85
+ def self.make_temp_dir(aPrefix='')
86
+ new_dir = nil
87
+ begin
88
+ new_dir = File.join(Dir.tmpdir,aPrefix+("%08X" % rand(0x3FFFFFFF)))
89
+ end until new_dir && !File.exists?(new_dir)
90
+ Dir.mkdir new_dir
91
+ return new_dir
92
+ end
93
+
94
+ def self.mkdir?(aPath,aPermissions)
95
+ if File.exists?(aPath)
96
+ File.chmod(aPermissions, aPath)
97
+ else
98
+ Dir.mkdir(aPath, aPermissions)
99
+ end
100
+ end
101
+
102
+ def self.string_to_file(aString,aFilename)
103
+ File.open(aFilename,'wb') {|file| file.puts aString }
104
+ end
105
+
106
+ def self.string_from_file(aFilename)
107
+ result = nil
108
+ File.open(aFilename, "rb") { |f| result = f.read }
109
+ return result && result[0..-2] # quick hack to stop returning false \n at end
110
+ end
111
+
112
+ def self.sniff_seperator(aPath)
113
+ result = 0.upto(aPath.length-1) do |i|
114
+ char = aPath[i,1]
115
+ break char if char=='\\' || char=='/'
116
+ end
117
+ result = File::SEPARATOR if result==0
118
+ return result
119
+ end
120
+
121
+ def self.append_slash(aPath,aSep=nil)
122
+ aSep = sniff_seperator(aPath) unless aSep
123
+ last_char = aPath[-1,1]
124
+ aPath += aSep unless last_char=='\\' || last_char=='/'
125
+ return aPath
126
+ end
127
+
128
+ def self.remove_slash(aPath)
129
+ last_char = aPath[-1,1]
130
+ aPath = aPath[0..-2] if last_char=='\\' || last_char=='/'
131
+ return aPath
132
+ end
133
+
134
+ # Remove base dir from given path. Result will be relative to base dir and not have a leading or trailing slash
135
+ #'/a/b/c','/a' = 'b/c'
136
+ #'/a/b/c','/' = 'a/b/c'
137
+ #'/','/' = ''
138
+ def self.path_debase(aPath,aBase)
139
+ aBase = MiscUtils::append_slash(aBase)
140
+ aPath = MiscUtils::remove_slash(aPath) unless aPath=='/'
141
+ aPath[0,aBase.length]==aBase ? aPath[aBase.length,aPath.length-aBase.length] : aPath
142
+ end
143
+
144
+ def self.path_rebase(aPath,aOldBase,aNewBase)
145
+ rel_path = path_debase(aPath,aOldBase)
146
+ append_slash(aNewBase)+rel_path
147
+ end
148
+
149
+ def self.path_combine(aBasePath,aPath)
150
+ return aBasePath if !aPath
151
+ return aPath if !aBasePath
152
+ return path_relative?(aPath) ? File.join(aBasePath,aPath) : aPath
153
+ end
154
+
155
+ def self.path_parent(aPath)
156
+ MiscUtils.append_slash(File.dirname(MiscUtils.remove_slash(File.expand_path(aPath))))
157
+ end
158
+
159
+ def self.simple_dir_name(aPath)
160
+ File.basename(remove_slash(aPath))
161
+ end
162
+
163
+ def self.path_parts(aPath)
164
+ sep = sniff_seperator(aPath)
165
+ aPath.split(sep)
166
+ end
167
+
168
+ def self.file_extension(aFile,aExtended=true)
169
+ f = File.basename(aFile)
170
+ dot = aExtended ? f.index('.') : f.rindex('.')
171
+ return dot ? f[dot+1..-1] : f
172
+ end
173
+
174
+ def self.file_no_extension(aFile,aExtended=true)
175
+ ext = file_extension(aFile,aExtended)
176
+ return aFile.chomp('.'+ext)
177
+ end
178
+
179
+ def self.file_change_ext(aFile,aExt,aExtend=false)
180
+ file_no_extension(aFile,false)+(aExtend ? '.'+aExt+'.'+file_extension(aFile,false) : '.'+aExt)
181
+ end
182
+
183
+ def self.platform
184
+ RUBY_PLATFORM.scan(/-(.+)$/).flatten.first
185
+ end
186
+
187
+ def self.windows_path(aPath)
188
+ aPath.gsub('/','\\')
189
+ end
190
+
191
+ def self.ruby_path(aPath)
192
+ aPath.gsub('\\','/')
193
+ end
194
+
195
+ def self.is_uri?(aString)
196
+ /^[a-zA-Z0-9+_]+\:\/\// =~ aString ? true : false
197
+ end
198
+
199
+ def self.native_path(aPath)
200
+ is_windows? ? windows_path(aPath) : ruby_path(aPath)
201
+ end
202
+
203
+ def self.path_relative?(aPath)
204
+ return false if aPath[0,1]=='/'
205
+ return false if aPath =~ /^[a-zA-Z]:/
206
+ return true
207
+ end
208
+
209
+ def self.path_absolute?(aPath)
210
+ !path_relative(aPath)
211
+ end
212
+
213
+ def self.is_windows?
214
+ platform=='mswin32'
215
+ end
216
+
217
+ # takes a path and combines it with a root path (which defaults to Dir.pwd) unless it is absolute
218
+ # the final result is then expanded
219
+ def self.canonize_path(aPath,aRootPath=nil)
220
+ path = Pathname.new(aPath)
221
+ path = Pathname.new(aRootPath || Dir.pwd)+path if path.relative?
222
+ File.expand_path(path)
223
+ end
224
+
225
+ def self.get_files(aArray,aPath,aFullPath=true,aRootPath=nil,&block)
226
+ #puts "get_files: aPath='#{aPath}'"
227
+ if aRootPath
228
+ abssrcpath = path_combine(aRootPath,aPath)
229
+ else
230
+ abssrcpath = aRootPath = aPath
231
+ aPath = nil
232
+ end
233
+ return aArray if !File.exists?(abssrcpath)
234
+ #abssrcpath is real path to query
235
+ #aRootPath is highest level path
236
+ #aPath is current path relative to aRootPath
237
+ Dir.new(abssrcpath).to_a.each do |file|
238
+ next if ['.','..'].include? file
239
+ fullpath = File.join(abssrcpath,file)
240
+ resultpath = aFullPath ? fullpath : path_combine(aPath,file)
241
+ if !block_given? || yield(resultpath)
242
+ if FileTest.directory?(fullpath)
243
+ block_given? ? get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath,&block) : get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath)
244
+ else
245
+ aArray << resultpath
246
+ end
247
+ end
248
+ end
249
+ return aArray
250
+ end
251
+
252
+ def self.recursive_file_list(aPath,aFullPath=true,&block)
253
+ block_given? ? get_files([],aPath,aFullPath,nil,&block) : get_files([],aPath,aFullPath)
254
+ end
255
+
256
+ # returns true if aPath1 and aPath2 are the same path (doesn't query file system)
257
+ # both must be absolute or both relative
258
+ def self.path_same(aPath1,aPath2)
259
+ return nil unless path_relative?(aPath1) == path_relative?(aPath2)
260
+ remove_slash(aPath1) == remove_slash(aPath2)
261
+ end
262
+
263
+ # returns true if aPath is under aPathParent
264
+ # both must be absolute or both relative
265
+ def self.path_ancestor(aPathParent,aPath)
266
+ return nil unless path_relative?(aPathParent) == path_relative?(aPath)
267
+ aPath.index(append_slash(aPathParent))==0
268
+ end
269
+
270
+ # returns the lowest path containing all files (assumes aFiles contains only absolute paths)
271
+ def self.file_list_ancestor(aFiles)
272
+ files = aFiles.is_a?(Hash) ? aFiles.keys : aFiles
273
+ result = File.dirname(files.first)
274
+ files.each do |fp|
275
+ filedir = File.dirname(fp)
276
+ while path_same(result,filedir)==false && path_ancestor(result,filedir)==false
277
+ result = path_parent(result)
278
+ end
279
+ end
280
+ result
281
+ end
282
+
283
+ def self.path_match(aPath,aPatterns)
284
+ aPatterns = [aPatterns] unless aPatterns.is_a? Array
285
+ aPatterns.any? do |pat|
286
+ case pat
287
+ when String then aPath[0,pat.length] == pat
288
+ when Regexp then aPath =~ pat
289
+ else false
290
+ end
291
+ end
292
+ end
293
+
294
+ # for capistrano deployed paths
295
+ # makes "/var/www/logikal.stage/releases/20090911073620/cms/config.xml" into "/var/www/logikal.stage/current/cms/config.xml"
296
+ def self.neaten_cap_path(aPath)
297
+ aPath.sub(/(\/releases\/[0-9]+\/)/,'/current/')
298
+ end
299
+
300
+ # takes a hash and returns a single closed tag containing the hash pairs as attributes, correctly encoded
301
+ def self.hash_to_xml_tag(aName,aHash)
302
+ atts = ''
303
+ aHash.each do |k,v|
304
+ atts += ' ' + k.to_s + "=\"#{v.to_s.to_xs}\""
305
+ end
306
+ "<#{aName}#{atts}/>"
307
+ end
308
+
309
+ def self.filelist_from_patterns(aPatterns,aBasePath)
310
+ return [] unless aPatterns
311
+ aPatterns = [aPatterns] unless aPatterns.is_a? Array
312
+
313
+ aPatterns.map do |fp|
314
+ fp = File.expand_path(fp,aBasePath) # relative to rails root
315
+ fp = FileList[fp] if fp['*'] || fp['?']
316
+ fp
317
+ end.flatten
318
+ end
319
+
320
+ #:host
321
+ #:port
322
+ #:helodomain
323
+ #:user
324
+ #:password
325
+ #:from
326
+ #:from_alias
327
+ #:to
328
+ #:to_alias
329
+ #:subject
330
+ #:message
331
+ #:auth : 'plain', 'login', 'cram_md5'
332
+
333
+ # send an email via an SMTP server
334
+ def self.send_email(aArgs)
335
+ msg = <<END_OF_MESSAGE
336
+ From: #{aArgs[:from_alias]} <#{aArgs[:from]}>
337
+ To: #{aArgs[:to_alias]} <#{aArgs[:to]}>
338
+ Subject: #{aArgs[:subject]}
339
+
340
+ #{aArgs[:message]}
341
+ END_OF_MESSAGE
342
+
343
+ Net::SMTP.start(
344
+ aArgs[:host],
345
+ aArgs[:port],
346
+ aArgs[:helodomain],
347
+ aArgs[:user],
348
+ aArgs[:password],
349
+ aArgs[:auth]
350
+ ) do |smtp|
351
+ smtp.send_message msg, aArgs[:from], aArgs[:to]
352
+ end
353
+ end
354
+
355
+ end
356
+
357
+ # include this at the top of a class to protect it from baddies.
358
+ # eg.
359
+ # + nearly all ancestor public_instance_methods will be hidden
360
+ # + inspect will only return the class name
361
+ # + methods will return public methods
362
+ module SecureThisClass
363
+ def self.hack(aClass,aOptions={})
364
+ include_actions = (aOptions[:include] || aClass.public_instance_methods.clone)
365
+ exclude_actions = ['class','public_methods'] | (aOptions[:exclude] || [])
366
+ actions_to_hide = include_actions-exclude_actions
367
+ aClass.class_eval do
368
+ actions_to_hide.each { |m| protected m.to_sym }
369
+
370
+ def inspect
371
+ return self.class.name
372
+ end
373
+
374
+ def methods
375
+ public_methods
376
+ end
377
+ end
378
+ end
379
+ end
380
+
381
+
382
+ module ::Kernel
383
+ def secure_class(aOptions={})
384
+ SecureThisClass::hack(self,aOptions)
385
+ end
386
+ end
387
+
@@ -0,0 +1,39 @@
1
+ # This sorts out the issues of require'ing files in Ruby
2
+ # 1) on one line, you specify all the paths you need
3
+ # 2) Relative paths will be relative to the file you are in, absolute paths also supported
4
+ # 3) Paths will be expanded
5
+ # 4) Paths will only be added if they don't already exist
6
+ #
7
+ module ::Kernel
8
+
9
+ # returns full path given relative to $LOAD_PATH
10
+ def require_which(aFilepath)
11
+ aFilepath += '.rb'
12
+ $LOAD_PATH.each do |dir|
13
+ full_path = File.expand_path(File.join(dir,aFilepath))
14
+ return full_path if File.exist? full_path
15
+ end
16
+ return nil
17
+ end
18
+
19
+ def require_paths(*aArgs)
20
+ caller_dir = File.dirname(File.expand_path(caller.first.sub(/:[0-9]+.*/,'')))
21
+ aArgs.each do |aPath|
22
+ aPath = File.expand_path(aPath,caller_dir)
23
+ $LOAD_PATH << aPath unless $LOAD_PATH.include?(aPath)
24
+ end
25
+ end
26
+
27
+ def require_paths_first(*aArgs)
28
+ caller_dir = File.dirname(File.expand_path(caller.first.sub(/:[0-9]+.*/,'')))
29
+ paths = []
30
+ aArgs.each do |aPath|
31
+ aPath = File.expand_path(aPath,caller_dir)
32
+ paths << aPath
33
+ end
34
+ paths.each do |p|
35
+ $LOAD_PATH.insert(0,p)
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,80 @@
1
+ gem 'Platform'; require 'platform'
2
+ gem 'shairontoledo-popen4'; require 'popen4'
3
+
4
+ module POpen4
5
+
6
+ class ExecuteError < StandardError
7
+
8
+ attr_reader :result #,:stderr,:stdout,:exitcode,:pid
9
+
10
+ def initialize(aArg)
11
+ if aArg.is_a? Hash
12
+ msg = ([aArg[:stderr],aArg[:stdout],"Error #{aArg[:exitcode].to_s}"].find {|i| i && !i.empty?})
13
+ super(msg)
14
+ @result = aArg
15
+ else
16
+ super(aArg)
17
+ end
18
+ end
19
+
20
+ def inspect
21
+ "#{self.class.to_s}: #{@result.inspect}"
22
+ end
23
+
24
+ end
25
+
26
+ def self.pump_thread(aIn,aOut)
27
+ Thread.new do
28
+ loop { aOut.puts aIn.gets }
29
+ end
30
+ end
31
+
32
+ # Usage :
33
+ # result = POpen4::shell('somebinary') do |r| # block gives opportunity to adjust result, and avoid exception raised from non-zero exit codes
34
+ # if r[:exitcode]==254 # eg. say this binary returns 254 to mean something special but not an error
35
+ # r[:stdout] = 'some correct output'
36
+ # r[:stderr] = ''
37
+ # r[:exitcode] = 0
38
+ # end
39
+ # end
40
+ #
41
+ # OR
42
+ #
43
+ # result = POpen4::shell('somebinary');
44
+ # puts result[:stdout]
45
+ #
46
+ # Giving aStdOut,aStdErr causes the command output to be connected to the given stream, and that stream to not be given in the result hash
47
+ def self.shell(aCommand,aWorkingDir=nil,aTimeout=nil,aStdOut=nil,aStdErr=nil)
48
+ raise ExecuteError.new('aWorkingDir doesnt exist') unless !aWorkingDir || File.exists?(aWorkingDir)
49
+ orig_wd = Dir.getwd
50
+ result = {:command => aCommand, :dir => (aWorkingDir || orig_wd)}
51
+ status = nil
52
+ begin
53
+ Dir.chdir(aWorkingDir) if aWorkingDir
54
+ Timeout.timeout(aTimeout,ExecuteError) do # nil aTimeout will not time out
55
+ status = POpen4::popen4(aCommand) do |stdout, stderr, stdin, pid|
56
+ thrOut = aStdOut ? Thread.new { aStdOut.puts stdout.read } : nil
57
+ thrErr = aStdErr ? Thread.new { aStdErr.puts stderr.read } : nil
58
+ thrOut.join if thrOut
59
+ thrErr.join if thrErr
60
+
61
+ result[:stdout] = stdout.read unless aStdOut
62
+ result[:stderr] = stderr.read unless aStdErr
63
+ result[:pid] = pid
64
+ end
65
+ end
66
+ ensure
67
+ Dir.chdir(orig_wd)
68
+ end
69
+ result[:exitcode] = (status && status.exitstatus) || 1
70
+ yield(result) if block_given?
71
+ raise ExecuteError.new(result) if result[:exitcode] != 0
72
+ return result
73
+ end
74
+
75
+ def self.shell_out(aCommand,aWorkingDir=nil,aTimeout=nil,&block)
76
+ block_given? ? POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR,&block) : POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR)
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,66 @@
1
+ module StringUtils
2
+ def self.crop(aString,aLength,aEllipsis=true,aConvertNil=true)
3
+ return aConvertNil ? ' '*aLength : nil if !aString
4
+
5
+ increase = aLength-aString.length
6
+ return aString+' '*increase if increase>=0
7
+ return aEllipsis ? aString[0,aLength-3]+'...' : aString[0,aLength]
8
+ end
9
+
10
+ # aTemplate is a string containing tokens like ${SOME_TOKEN}
11
+ # aValues is a hash of token names eg. 'SOME_TOKEN' and their values to substitute
12
+ def self.render_template(aTemplate,aValues)
13
+ # get positions of tokens
14
+ result = aTemplate.gsub(/\$\{(.*?)\}/) do |s|
15
+ key = s[2..-2]
16
+ rep = (aValues[key] || s)
17
+ #puts "replacing #{s} with #{rep}"
18
+ rep
19
+ end
20
+ #puts "rendered :\n#{result}"
21
+ return result
22
+ end
23
+
24
+ def self.clean_number(aString)
25
+ aString.gsub(/[^0-9.-]/,'')
26
+ end
27
+
28
+ # supply a block with 2 parameters, and it will get called for each char as an integer
29
+ def self.each_unicode_char(aString)
30
+ len = 1
31
+ index = 0
32
+ char = 0
33
+ aString.each_byte do |b|
34
+ if index==0
35
+ len = 1
36
+ len = 2 if b & 0b11000000 != 0
37
+ len = 3 if b & 0b11100000 != 0
38
+ len = 4 if b & 0b11110000 != 0
39
+ char = 0
40
+ end
41
+
42
+ char |= b << index*8
43
+
44
+ yield(char,len) if index==len-1 # last byte; char is complete
45
+
46
+ index += 1
47
+ index = 0 if index >= len
48
+ end
49
+ end
50
+
51
+ # given ('abcdefg','c.*?e') returns ['ab','cde','fg'] so you can manipulate the head, match and tail seperately, and potentially rejoin
52
+ def self.split3(aString,aPattern,aOccurence=0)
53
+ matches = aString.scan_md(aPattern)
54
+ match = matches[aOccurence]
55
+ parts = [match.pre_match,match.to_s,match.post_match]
56
+
57
+ if !block_given? # return head,match,tail
58
+ parts
59
+ else # return string
60
+ parts[1] = yield *parts
61
+ parts.join
62
+ end
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,70 @@
1
+ # represents a mono-spaced text document with a given width and expandable height.
2
+ class TextDoc
3
+
4
+ attr_reader :width, :height, :lines
5
+
6
+ def logger
7
+ RAILS_DEFAULT_LOGGER
8
+ end
9
+
10
+ def initialize(aWidth=80,aHeight=66)
11
+ @width = aWidth
12
+ @height = aHeight
13
+
14
+ @lines = Array.new(@height)
15
+ line_str = ' '*@width
16
+ @lines.collect!{|line| line_str.clone }
17
+ end
18
+
19
+ def replace_string(aString,aCol,aSubString)
20
+ return aString if aSubString==nil || aSubString==''
21
+
22
+ aSubString = aSubString.to_s
23
+ start_col = aCol < 0 ? 0 : aCol
24
+ end_col = aCol+aSubString.length-1
25
+ end_col = @width-1 if end_col >= @width
26
+ source_len = end_col-start_col+1
27
+ return aString if source_len <= 0 || end_col < 0 || start_col >= @width
28
+ aString += ' '*((end_col+1) - aString.length) if aString.length < end_col+1
29
+ aString[start_col,source_len] = aSubString[start_col-aCol,end_col-start_col+1]
30
+ return aString
31
+ end
32
+
33
+ def replace(aCol,aLine,aString)
34
+ return if (aLine < 0) || (aLine>=@lines.length)
35
+ replace_string(@lines[aLine],aCol,aString)
36
+ end
37
+
38
+ def replace_block(aCol,aLine,aLines)
39
+ aLines = aLines.split(/\n/) if aLines.is_a?(String)
40
+ aLines = aLines.lines if aLines.is_a?(TextDoc)
41
+
42
+ aLines.each_index do |iSource|
43
+ replace(aCol,aLine+iSource,aLines[iSource])
44
+ end
45
+ end
46
+
47
+ def add_block(aLines,aCol=0)
48
+ aLines = aLines.split(/\n/) if aLines.is_a?(String)
49
+ aLines = aLines.lines if aLines.is_a?(TextDoc)
50
+ aLines.each_index do |iSource|
51
+ @lines << ' '*@width
52
+ replace(aCol,@lines.length-1,aLines[iSource])
53
+ end
54
+ end
55
+
56
+ def add_line(aLine=nil,aCol=0)
57
+ @lines << ' '*@width and return if !aLine
58
+ @lines << ' '*@width
59
+ replace(aCol,@lines.length-1,aLine)
60
+ end
61
+
62
+ def centre_bar(aChar = '-', indent = 6)
63
+ (' '*indent) + aChar*(@width-(indent*2)) + (' '*indent)
64
+ end
65
+
66
+ def to_s
67
+ return @lines.join("\n")
68
+ end
69
+ end
70
+