buzzcore 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|