yore 0.0.1
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/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
|
+
|