buzzcore 0.2.5

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.
@@ -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
+