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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +23 -0
- data/README.rdoc +46 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/buzzcore.gemspec +77 -0
- data/buzzcore.vpj +93 -0
- data/buzzcore.vpw +93 -0
- data/lib/buzzcore/config.rb +239 -0
- data/lib/buzzcore/database_utils.rb +86 -0
- data/lib/buzzcore/enum.rb +50 -0
- data/lib/buzzcore/extend_base_classes.rb +328 -0
- data/lib/buzzcore/html_utils.rb +29 -0
- data/lib/buzzcore/logging.rb +159 -0
- data/lib/buzzcore/misc_utils.rb +387 -0
- data/lib/buzzcore/require_paths.rb +39 -0
- data/lib/buzzcore/shell_extras.rb +80 -0
- data/lib/buzzcore/string_utils.rb +66 -0
- data/lib/buzzcore/text_doc.rb +70 -0
- data/lib/buzzcore/thread_utils.rb +709 -0
- data/lib/buzzcore/xml_utils.rb +203 -0
- data/lib/buzzcore.rb +2 -0
- data/lib/buzzcore_dev.rb +6 -0
- data/test/buzzcore_test.rb +7 -0
- data/test/config_test.rb +201 -0
- data/test/credentials_test.rb +71 -0
- data/test/shell_test.rb +54 -0
- data/test/test_helper.rb +10 -0
- metadata +95 -0
@@ -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
|
+
|