yore 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +22 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +48 -0
- data/Rakefile +57 -0
- data/bin/yore +65 -0
- data/lib/ihl_ruby/enum.rb +50 -0
- data/lib/ihl_ruby/extend_base_classes.rb +313 -0
- data/lib/ihl_ruby/misc_utils.rb +454 -0
- data/lib/ihl_ruby/string_utils.rb +53 -0
- data/lib/ihl_ruby/xml_utils.rb +163 -0
- data/lib/yore/yore_core.rb +491 -0
- data/lib/yore.orig.rb +6 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/rewind_spec.rb +187 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_job_a.xml +19 -0
- data/spec/yore_spec.rb +199 -0
- data/tasks/rspec.rake +21 -0
- metadata +128 -0
@@ -0,0 +1,454 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'logger'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module MiscUtils
|
6
|
+
|
7
|
+
def self.logger
|
8
|
+
@logger || @logger = Logger.new(STDERR)
|
9
|
+
end
|
10
|
+
|
11
|
+
# applies the given block to key/value pairs included/excluded by the given parameters
|
12
|
+
# aSource must be a hash, and may contain hashes which will be recursively processed.
|
13
|
+
# aInclude/aExclude may be nil, an array of selected keys, or a hash containing arrays of keys for inner hashes of aSource
|
14
|
+
# When aInclude/aExclude are a hash, non-hash source values may be selected by passing a value of true. Hash source values
|
15
|
+
# will be selected as a whole (all keys) when true is passed.
|
16
|
+
# input = { 'a' => 1, 'b' => {'x' => 9, 'y' => 8}, 'c' => 3 }
|
17
|
+
# filter_multilevel_hash(input,['a','b'],{'b'=>['y']}) {|h,k,v| h[k] = true}
|
18
|
+
# input now = { 'a' => true, 'b' => {'x' => true, 'y' => 8}, 'c' => 3 }
|
19
|
+
def self.filter_multilevel_hash(aSource,aInclude=nil,aExclude=nil,&block)
|
20
|
+
aSource.each do |key,value|
|
21
|
+
next if aInclude.is_a?(Array) and !aInclude.include?(key) # skip if not in aInclude array
|
22
|
+
next if aExclude.is_a?(Array) and aExclude.include?(key) # skip if in aExclude array
|
23
|
+
next if aExclude.is_a?(Hash) and aExclude[key]==true # skip if in aExclude hash with value=true
|
24
|
+
next if aInclude.is_a?(Hash) and !aInclude.include?(key) # skip if not in aInclude hash at all
|
25
|
+
if value.is_a?(Hash) # value is hash so recursively apply filter
|
26
|
+
filter_multilevel_hash(
|
27
|
+
value,
|
28
|
+
aInclude.is_a?(Hash) && (f = aInclude[key]) ? f : nil, # pass include array if provided for key
|
29
|
+
aExclude.is_a?(Hash) && (f = aExclude[key]) ? f : nil, # pass exclude array if provided for key
|
30
|
+
&block
|
31
|
+
)
|
32
|
+
else
|
33
|
+
yield(aSource,key,value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns output string if succesful, or integer return code if not, or nil
|
39
|
+
def self.execute(aCommand,aWorkingDir=nil,aTimeout=nil,aTimeoutClass=nil)
|
40
|
+
return nil unless !aWorkingDir || File.exists?(aWorkingDir)
|
41
|
+
begin
|
42
|
+
orig_wd = Dir.getwd
|
43
|
+
pipe = nil
|
44
|
+
result = nil
|
45
|
+
Dir.chdir(aWorkingDir) if aWorkingDir
|
46
|
+
Timeout.timeout(aTimeout,aTimeoutClass || Timeout::Error) do # nil aTimeout will not time out
|
47
|
+
pipe = IO.popen(aCommand)
|
48
|
+
logger.debug "command PID:"+pipe.pid.to_s
|
49
|
+
result = pipe.read
|
50
|
+
end
|
51
|
+
ensure
|
52
|
+
pipe.close if pipe
|
53
|
+
Dir.chdir(orig_wd)
|
54
|
+
end
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.execute_string(aCmdString,aWorkingDir=nil)
|
59
|
+
result = nil
|
60
|
+
begin
|
61
|
+
orig_dir = Dir.pwd
|
62
|
+
Dir.chdir(aWorkingDir) if aWorkingDir
|
63
|
+
result = `#{aCmdString}`
|
64
|
+
ensure
|
65
|
+
Dir.chdir(orig_dir) if aWorkingDir
|
66
|
+
end
|
67
|
+
return result
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.temp_file(aExt=nil,aDir=nil)
|
71
|
+
aExt ||= '.tmp'
|
72
|
+
File.expand_path(("%08X" % rand(0x3FFFFFFF)) + aExt, aDir||Dir.tmpdir)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.make_temp_file(aName=nil,aDir=nil,aContent=nil)
|
76
|
+
filename = aName ? File.expand_path(aName,aDir || Dir.tmpdir) : temp_file(nil,aDir)
|
77
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
78
|
+
aContent ||= "content of "+filename
|
79
|
+
string_to_file(aContent,filename)
|
80
|
+
filename
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.make_temp_dir(aPrefix='')
|
84
|
+
new_dir = nil
|
85
|
+
begin
|
86
|
+
new_dir = File.join(Dir.tmpdir,aPrefix+("%08X" % rand(0x3FFFFFFF)))
|
87
|
+
end until new_dir && !File.exists?(new_dir)
|
88
|
+
Dir.mkdir new_dir
|
89
|
+
return new_dir
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.mkdir?(aPath,aPermissions)
|
93
|
+
if File.exists?(aPath)
|
94
|
+
File.chmod(aPermissions, aPath)
|
95
|
+
else
|
96
|
+
Dir.mkdir(aPath, aPermissions)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.string_to_file(aString,aFilename)
|
101
|
+
File.open(aFilename,'wb') {|file| file.puts aString }
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.string_from_file(aFilename)
|
105
|
+
result = nil
|
106
|
+
File.open(aFilename, "rb") { |f| result = f.read }
|
107
|
+
return result && result[0..-2] # quick hack to stop returning false \n at end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.sniff_seperator(aPath)
|
111
|
+
result = 0.upto(aPath.length-1) do |i|
|
112
|
+
char = aPath[i,1]
|
113
|
+
break char if char=='\\' || char=='/'
|
114
|
+
end
|
115
|
+
result = File::SEPARATOR if result==0
|
116
|
+
return result
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.append_slash(aPath,aSep=nil)
|
120
|
+
aSep = sniff_seperator(aPath) unless aSep
|
121
|
+
last_char = aPath[-1,1]
|
122
|
+
aPath += aSep unless last_char=='\\' || last_char=='/'
|
123
|
+
return aPath
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.remove_slash(aPath)
|
127
|
+
last_char = aPath[-1,1]
|
128
|
+
aPath = aPath[0..-2] if last_char=='\\' || last_char=='/'
|
129
|
+
return aPath
|
130
|
+
end
|
131
|
+
|
132
|
+
# Remove base dir from given path. Result will be relative to base dir and not have a leading or trailing slash
|
133
|
+
#'/a/b/c','/a' = 'b/c'
|
134
|
+
#'/a/b/c','/' = 'a/b/c'
|
135
|
+
#'/','/' = ''
|
136
|
+
def self.path_debase(aPath,aBase)
|
137
|
+
aBase = MiscUtils::append_slash(aBase)
|
138
|
+
aPath = MiscUtils::remove_slash(aPath) unless aPath=='/'
|
139
|
+
aPath[aBase.length,aPath.length-aBase.length]
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.path_rebase(aPath,aOldBase,aNewBase)
|
143
|
+
rel_path = path_debase(aPath,aOldBase)
|
144
|
+
append_slash(aNewBase)+rel_path
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.path_combine(aBasePath,aPath)
|
148
|
+
return aBasePath if !aPath
|
149
|
+
return aPath if !aBasePath
|
150
|
+
return path_relative?(aPath) ? File.join(aBasePath,aPath) : aPath
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.path_parent(aPath)
|
154
|
+
MiscUtils.append_slash(File.dirname(MiscUtils.remove_slash(File.expand_path(aPath))))
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.simple_dir_name(aPath)
|
158
|
+
File.basename(remove_slash(aPath))
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.path_parts(aPath)
|
162
|
+
sep = sniff_seperator(aPath)
|
163
|
+
aPath.split(sep)
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.file_extension(aFile,aExtended=true)
|
167
|
+
f = File.basename(aFile)
|
168
|
+
dot = aExtended ? f.index('.') : f.rindex('.')
|
169
|
+
return dot ? f[dot+1..-1] : f
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.file_no_extension(aFile,aExtended=true)
|
173
|
+
ext = file_extension(aFile,aExtended)
|
174
|
+
return aFile.chomp('.'+ext)
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.file_change_ext(aFile,aExt,aExtend=false)
|
178
|
+
file_no_extension(aFile,false)+(aExtend ? '.'+aExt+'.'+file_extension(aFile,false) : '.'+aExt)
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.platform
|
182
|
+
RUBY_PLATFORM.scan(/-(.+)$/).flatten.first
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.windows_path(aPath)
|
186
|
+
aPath.gsub('/','\\')
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.ruby_path(aPath)
|
190
|
+
aPath.gsub('\\','/')
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.is_uri?(aString)
|
194
|
+
/^[a-zA-Z0-9+_]+\:\/\// =~ aString ? true : false
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.native_path(aPath)
|
198
|
+
is_windows? ? windows_path(aPath) : ruby_path(aPath)
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.path_relative?(aPath)
|
202
|
+
return false if aPath[0,1]=='/'
|
203
|
+
return false if aPath =~ /^[a-zA-Z]:/
|
204
|
+
return true
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.path_absolute?(aPath)
|
208
|
+
!path_relative(aPath)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.is_windows?
|
212
|
+
platform=='mswin32'
|
213
|
+
end
|
214
|
+
|
215
|
+
# takes a path and combines it with a root path (which defaults to Dir.pwd) unless it is absolute
|
216
|
+
# the final result is then expanded
|
217
|
+
def self.canonize_path(aPath,aRootPath=nil)
|
218
|
+
path = Pathname.new(aPath)
|
219
|
+
path = Pathname.new(aRootPath || Dir.pwd)+path if path.relative?
|
220
|
+
File.expand_path(path)
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.get_files(aArray,aPath,aFullPath=true,aRootPath=nil,&block)
|
224
|
+
#puts "get_files: aPath='#{aPath}'"
|
225
|
+
if aRootPath
|
226
|
+
abssrcpath = path_combine(aRootPath,aPath)
|
227
|
+
else
|
228
|
+
abssrcpath = aRootPath = aPath
|
229
|
+
aPath = nil
|
230
|
+
end
|
231
|
+
#abssrcpath is real path to query
|
232
|
+
#aRootPath is highest level path
|
233
|
+
#aPath is current path relative to aRootPath
|
234
|
+
Dir.new(abssrcpath).to_a.each do |file|
|
235
|
+
next if ['.','..'].include? file
|
236
|
+
fullpath = File.join(abssrcpath,file)
|
237
|
+
resultpath = aFullPath ? fullpath : path_combine(aPath,file)
|
238
|
+
if !block_given? || yield(resultpath)
|
239
|
+
if FileTest.directory?(fullpath)
|
240
|
+
block_given? ? get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath,&block) : get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath)
|
241
|
+
else
|
242
|
+
aArray << resultpath
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
return aArray
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.recursive_file_list(aPath,aFullPath=true,&block)
|
250
|
+
block_given? ? get_files([],aPath,aFullPath,nil,&block) : get_files([],aPath,aFullPath)
|
251
|
+
end
|
252
|
+
|
253
|
+
# returns true if aPath1 and aPath2 are the same path (doesn't query file system)
|
254
|
+
# both must be absolute or both relative
|
255
|
+
def self.path_same(aPath1,aPath2)
|
256
|
+
return nil unless path_relative?(aPath1) == path_relative?(aPath2)
|
257
|
+
remove_slash(aPath1) == remove_slash(aPath2)
|
258
|
+
end
|
259
|
+
|
260
|
+
# returns true if aPath is under aPathParent
|
261
|
+
# both must be absolute or both relative
|
262
|
+
def self.path_ancestor(aPathParent,aPath)
|
263
|
+
return nil unless path_relative?(aPathParent) == path_relative?(aPath)
|
264
|
+
aPath.index(append_slash(aPathParent))==0
|
265
|
+
end
|
266
|
+
|
267
|
+
# returns the lowest path containing all files (assumes aFiles contains only absolute paths)
|
268
|
+
def self.file_list_ancestor(aFiles)
|
269
|
+
files = aFiles.is_a?(Hash) ? aFiles.keys : aFiles
|
270
|
+
result = File.dirname(files.first)
|
271
|
+
files.each do |fp|
|
272
|
+
filedir = File.dirname(fp)
|
273
|
+
while path_same(result,filedir)==false && path_ancestor(result,filedir)==false
|
274
|
+
result = path_parent(result)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
result
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.path_match(aPath,aPatterns)
|
281
|
+
aPatterns = [aPatterns] unless aPatterns.is_a? Array
|
282
|
+
aPatterns.any? do |pat|
|
283
|
+
case pat
|
284
|
+
when String then aPath[0,pat.length] == pat
|
285
|
+
when Regexp then aPath =~ pat
|
286
|
+
else false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
# takes a hash and returns a single closed tag containing the hash pairs as attributes, correctly encoded
|
293
|
+
def self.hash_to_xml_tag(aName,aHash)
|
294
|
+
atts = ''
|
295
|
+
aHash.each do |k,v|
|
296
|
+
atts += ' ' + k.to_s + "=\"#{v.to_s.to_xs}\""
|
297
|
+
end
|
298
|
+
"<#{aName}#{atts}/>"
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.filelist_from_patterns(aPatterns,aBasePath)
|
302
|
+
return [] unless aPatterns
|
303
|
+
aPatterns = [aPatterns] unless aPatterns.is_a? Array
|
304
|
+
|
305
|
+
aPatterns.map do |fp|
|
306
|
+
fp = File.expand_path(fp,aBasePath) # relative to rails root
|
307
|
+
fp = FileList[fp] if fp['*'] || fp['?']
|
308
|
+
fp
|
309
|
+
end.flatten
|
310
|
+
end
|
311
|
+
|
312
|
+
#:host
|
313
|
+
#:port
|
314
|
+
#:helodomain
|
315
|
+
#:user
|
316
|
+
#:password
|
317
|
+
#:from
|
318
|
+
#:from_alias
|
319
|
+
#:to
|
320
|
+
#:to_alias
|
321
|
+
#:subject
|
322
|
+
#:message
|
323
|
+
#:auth : 'plain', 'login', 'cram_md5'
|
324
|
+
|
325
|
+
# send an email via an SMTP server
|
326
|
+
def self.send_email(aArgs)
|
327
|
+
msg = <<END_OF_MESSAGE
|
328
|
+
From: #{aArgs[:from_alias]} <#{aArgs[:from]}>
|
329
|
+
To: #{aArgs[:to_alias]} <#{aArgs[:to]}>
|
330
|
+
Subject: #{aArgs[:subject]}
|
331
|
+
|
332
|
+
#{aArgs[:message]}
|
333
|
+
END_OF_MESSAGE
|
334
|
+
|
335
|
+
Net::SMTP.start(
|
336
|
+
aArgs[:host],
|
337
|
+
aArgs[:port],
|
338
|
+
aArgs[:helodomain],
|
339
|
+
aArgs[:user],
|
340
|
+
aArgs[:password],
|
341
|
+
aArgs[:auth]
|
342
|
+
) do |smtp|
|
343
|
+
smtp.send_message msg, aArgs[:from], aArgs[:to]
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
class Logger
|
350
|
+
attr_reader :logdev
|
351
|
+
end
|
352
|
+
|
353
|
+
module LogUtils
|
354
|
+
|
355
|
+
# eg.
|
356
|
+
# {
|
357
|
+
# 'destination' => 'STDERR|STDOUT|FILE',
|
358
|
+
# 'filename' => '/path/to/file.ext',
|
359
|
+
# 'level' => 'DEBUG|INFO|...',
|
360
|
+
#
|
361
|
+
# 'age' = 'daily|weekly|monthly',
|
362
|
+
# OR
|
363
|
+
# 'max_files' => 3,
|
364
|
+
# 'max_bytes' => 1024000
|
365
|
+
# }
|
366
|
+
def self.create_logger_from_config(aConfigHash)
|
367
|
+
if not aConfigHash
|
368
|
+
result = Logger.new(STDERR)
|
369
|
+
result.level = Logger::DEBUG
|
370
|
+
return result
|
371
|
+
end
|
372
|
+
|
373
|
+
result = nil
|
374
|
+
case aConfigHash['destination']
|
375
|
+
when 'STDERR' then
|
376
|
+
result = Logger.new(STDERR)
|
377
|
+
when 'STDOUT' then
|
378
|
+
result = Logger.new(STDOUT)
|
379
|
+
when 'FILE' then
|
380
|
+
result = aConfigHash['age'] ?
|
381
|
+
Logger.new(aConfigHash['filename'],aConfigHash['age']) :
|
382
|
+
Logger.new(
|
383
|
+
aConfigHash['filename'],
|
384
|
+
(aConfigHash['max_files'] || 3).to_i,
|
385
|
+
(aConfigHash['max_bytes'] || 1024000).to_i
|
386
|
+
)
|
387
|
+
else
|
388
|
+
result = Logger.new(STDERR)
|
389
|
+
end
|
390
|
+
puts valstr = "Logger::#{(aConfigHash['level'] || 'INFO').upcase}"
|
391
|
+
result.level = eval(valstr)
|
392
|
+
return result
|
393
|
+
end
|
394
|
+
|
395
|
+
# use this to trunc a log file to 0 bytes
|
396
|
+
def self.trunc(aFilename)
|
397
|
+
f = File.open(aFilename, "w")
|
398
|
+
f.close
|
399
|
+
end
|
400
|
+
|
401
|
+
class ReportFormatter < Logger::Formatter
|
402
|
+
def call(severity, time, progname, msg)
|
403
|
+
"|%s %1s %s\n" % [(time.strftime('%H%M%S.')<<"%03d" % (time.usec/1000)),severity[0..0],msg2str(msg)]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class Reporter < Logger
|
408
|
+
def initialize(logdev)
|
409
|
+
super(logdev)
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
413
|
+
|
414
|
+
def self.create_reporter(aFilename=nil)
|
415
|
+
aFilename ||= MiscUtils::temp_file()
|
416
|
+
result = Logger.new(aFilename)
|
417
|
+
result.formatter = ReportFormatter.new
|
418
|
+
result
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
|
423
|
+
|
424
|
+
# include this at the top of a class to protect it from baddies.
|
425
|
+
# eg.
|
426
|
+
# + nearly all ancestor public_instance_methods will be hidden
|
427
|
+
# + inspect will only return the class name
|
428
|
+
# + methods will return public methods
|
429
|
+
module SecureThisClass
|
430
|
+
def self.hack(aClass,aOptions={})
|
431
|
+
include_actions = (aOptions[:include] || aClass.public_instance_methods.clone)
|
432
|
+
exclude_actions = ['class','public_methods'] | (aOptions[:exclude] || [])
|
433
|
+
actions_to_hide = include_actions-exclude_actions
|
434
|
+
aClass.class_eval do
|
435
|
+
actions_to_hide.each { |m| protected m.to_sym }
|
436
|
+
|
437
|
+
def inspect
|
438
|
+
return self.class.name
|
439
|
+
end
|
440
|
+
|
441
|
+
def methods
|
442
|
+
public_methods
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
|
449
|
+
module ::Kernel
|
450
|
+
def secure_class(aOptions={})
|
451
|
+
SecureThisClass::hack(self,aOptions)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
@@ -0,0 +1,53 @@
|
|
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
|
+
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module XmlUtils
|
5
|
+
|
6
|
+
BASIC_HEADER = '<?xml version="1.0"?>'
|
7
|
+
|
8
|
+
def self.get_url_root(url)
|
9
|
+
xml = Net::HTTP.get(URI(url))
|
10
|
+
return nil if !xml
|
11
|
+
xdoc = REXML::Document.new(xml)
|
12
|
+
return nil if !xdoc
|
13
|
+
root = xdoc.root
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_xml_root(xml)
|
17
|
+
xdoc = REXML::Document.new(xml)
|
18
|
+
return nil if !xdoc
|
19
|
+
root = xdoc.root
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get_file_root(aFilename)
|
23
|
+
get_xml_root(MiscUtils.string_from_file(aFilename))
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.single_node(node,xpath,default=nil)
|
27
|
+
return default if node.nil? || xpath.nil? || xpath==''
|
28
|
+
val = REXML::XPath.first(node,xpath)
|
29
|
+
return val.nil? ? default : val
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.peek_node(node,xpath,default=nil)
|
33
|
+
return default if node.nil? || xpath.nil? || xpath==''
|
34
|
+
val = REXML::XPath.first(node,xpath)
|
35
|
+
return val.nil? ? default : val.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.peek_node_value(node,xpath,default=nil)
|
39
|
+
peek_node(node,xpath+'/text()',default)
|
40
|
+
end
|
41
|
+
|
42
|
+
# convert <root><a>one</a><b>two</b></root> to {'a' => 'one', 'b' => 'two'}
|
43
|
+
def self.hash_from_elements(aXMLRoot)
|
44
|
+
result = {}
|
45
|
+
aXMLRoot.elements.each{|e| result[e.name] = e.text}
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
|
49
|
+
# given '<tag a="1" b="2">textblah</tag>' returns {:name=>"tag", :text=>"textblah", "a"=>"1", "b"=>"2"}
|
50
|
+
def self.hash_from_tag(aTagString)
|
51
|
+
result = {}
|
52
|
+
tag = get_xml_root(aTagString)
|
53
|
+
return nil if !tag
|
54
|
+
tag.attributes.each_attribute {|attr| result[attr.expanded_name] = attr.value }
|
55
|
+
result[:name] = tag.name
|
56
|
+
result[:text] = tag.text if tag.text
|
57
|
+
return result
|
58
|
+
end
|
59
|
+
|
60
|
+
# given {:name=>"tag", :text=>"textblah", "a"=>"1", "b"=>"2"} returns '<tag a="1" b="2">textblah</tag>'
|
61
|
+
def self.tag_from_hash(aHash,aName=nil,aText=nil)
|
62
|
+
aName ||= aHash[:name]
|
63
|
+
aText ||= aHash[:text]
|
64
|
+
result = "<#{aName}"
|
65
|
+
aHash.each {|k,v| result += " #{k}=\"#{encode(v.to_s)}\"" if k.is_a? String}
|
66
|
+
result += aText ? " >#{aText}</#{aName}>" : " />"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.hash_to_xml(aHash,aRootName,aDocHeader=true)
|
70
|
+
xdoc = REXML::Document.new(BASIC_HEADER)
|
71
|
+
root = xdoc.add_element(aRootName)
|
72
|
+
aHash.each do |n,v|
|
73
|
+
root.add_element(n).add_text(v)
|
74
|
+
end
|
75
|
+
return xdoc
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.read_simple_items(aRoot,aParentXPath)
|
79
|
+
result = {}
|
80
|
+
REXML::XPath.each(aRoot, aParentXPath+'/Item') do |item|
|
81
|
+
result[item.attribute('Name').to_s] = item.text
|
82
|
+
end
|
83
|
+
return result
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.quick_write_simple_items(aHash,aParent)
|
87
|
+
return "<#{aParent} />\n" if !aHash || aHash.empty?
|
88
|
+
result = "<#{aParent}>\n"
|
89
|
+
aHash.each {|key,value| result += "\t<Item Name=\"#{key.to_s}\">#{value.to_s}</Item>\n" }
|
90
|
+
result += "<#{aParent}/>\n"
|
91
|
+
return result
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.read_config_values(aXmlConfig)
|
95
|
+
xmlRoot = get_file_root(aXmlConfig)
|
96
|
+
return read_simple_items(xmlRoot,'/Config/SimpleItems')
|
97
|
+
end
|
98
|
+
|
99
|
+
# Takes a node or xml string and writes it out formatted nicely.
|
100
|
+
# aOutput may be given eg. a stream or nil can be given to get a returned string
|
101
|
+
def self.format_nicely(aXml,aOutput=nil)
|
102
|
+
aXml = REXML::Document.new(aXml) unless aXml.is_a?(REXML::Element)
|
103
|
+
f = REXML::Formatters::Pretty.new(2,true)
|
104
|
+
f.compact = true
|
105
|
+
f.width = 120
|
106
|
+
aOutput ||= ''
|
107
|
+
f.write(aXml,aOutput)
|
108
|
+
return aOutput
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.encode(aString)
|
112
|
+
result = aString.clone;
|
113
|
+
result.gsub!('&','&')
|
114
|
+
result.gsub!('<','<')
|
115
|
+
result.gsub!('>','>')
|
116
|
+
result.gsub!('"','"')
|
117
|
+
result.gsub!("'",''')
|
118
|
+
result.gsub!(/[\x80-\xFF]/) {|c| "&#x#{'%X' % c[0]};"}
|
119
|
+
return result
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.hash_to_deflist(aHash,aBuilder=nil)
|
123
|
+
aBuilder ||= Builder::XmlMarkup.new(:indent => 2)
|
124
|
+
aBuilder.dl do
|
125
|
+
aHash.each do |k,v|
|
126
|
+
aBuilder.dt(k.to_s)
|
127
|
+
aBuilder.dd(v.to_s)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.data_to_table(aRowHashes,aCaption=nil,aColNames=nil,aBuilder=nil)
|
133
|
+
aBuilder ||= Builder::XmlMarkup.new(:indent => 2)
|
134
|
+
aBuilder.table do
|
135
|
+
if aCaption.is_a? String
|
136
|
+
aBuilder.caption(aCaption)
|
137
|
+
elsif aCaption.is_a? Hash
|
138
|
+
aBuilder.caption do
|
139
|
+
XmlUtils.hash_to_deflist(aCaption,aBuilder)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
aColNames ||= aRowHashes.first.keys
|
143
|
+
aBuilder.thead do
|
144
|
+
aBuilder.tr do
|
145
|
+
aColNames.each do |name|
|
146
|
+
aBuilder.td(name.to_s)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
aBuilder.tbody do
|
151
|
+
aRowHashes.each do |row|
|
152
|
+
aBuilder.tr do
|
153
|
+
aColNames.each do |name|
|
154
|
+
aBuilder.td(row[name].to_s)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|