lenc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,632 @@
1
+ require 'set'
2
+ require 'fileutils'
3
+
4
+ # Various utility and debug convenience functions.
5
+ #
6
+
7
+ # Convenience method to perform 'require_relative' on a set of files
8
+ #
9
+ # @param fileListStr space-delimited file/path items, without .rb extensions
10
+ # @param subdir optional path to files relative to this file
11
+ #
12
+ def req(fileListStr,subdir = nil)
13
+ fileListStr.split(' ').each do |x|
14
+ if subdir
15
+ x = File.join(subdir,x)
16
+ end
17
+ x += '.rb'
18
+ require_relative(x)
19
+ end
20
+ end
21
+
22
+ # Shorthand for printf(...)
23
+ # @param args passed to printf
24
+ def pr(*args)
25
+ printf(*args)
26
+ end
27
+
28
+
29
+ # Convert an object to a human-readable string,
30
+ # or <nil>
31
+ #
32
+ def d(arg)
33
+ arg.nil? ? "<nil>" : arg.inspect
34
+ end
35
+
36
+
37
+ # Convert an object to a human-readable string,
38
+ # by calling a type-appropriate function: da, dh, or just d.
39
+ # @param arg object
40
+ # @param indent optional indentation for pretty printing; if result
41
+ # spans multiple lines, each line should be indented by this amount
42
+ #
43
+ def d2(arg, indent = 0)
44
+ return da(arg, indent) if arg.is_a? Array
45
+ return dh(arg, indent) if arg.is_a? Hash
46
+ return df(arg) if arg.class == FalseClass || arg.class == TrueClass
47
+ return d(arg)
48
+ end
49
+
50
+ # Convert an object to a human-readable string, prefixed with its type
51
+ #
52
+ def dt(arg)
53
+ if arg.nil?
54
+ return "<nil>"
55
+ end
56
+ s = arg.class.to_s
57
+ s << ':'
58
+ s << arg.inspect
59
+ s
60
+ end
61
+
62
+ # Append a particular number of spaces to a string
63
+ def add_sp(s, indent = 0)
64
+ s << ' ' * indent
65
+ end
66
+
67
+ # Pretty-print an array,
68
+ # one element to a line
69
+ # @param indent indentation of each line, in spaces
70
+ def da(array, indent = 0)
71
+ return d(array) if !array
72
+ s = 'Array ['
73
+ indent += 2
74
+ array.each do |x|
75
+ s << "\n"
76
+ add_sp(s,indent)
77
+ s2 = d2(x, indent + 2)
78
+ s << s2
79
+ end
80
+ s << " ]"
81
+ s
82
+ end
83
+
84
+ # Pretty-print a hash,
85
+ # one element to a line
86
+ # @param indent indentation of each line, in spaces
87
+ def dh(hash, indent = 0)
88
+ return d(hash) if !hash
89
+ s = 'Hash {'
90
+ indent += 2
91
+ hash.each_pair do |key,val|
92
+ s2 = d(key)
93
+ s3 = d2(val, indent + 4)
94
+ s << "\n "
95
+ add_sp(s,indent)
96
+ s << s2.chomp << " => " << s3.chomp
97
+ end
98
+ s << " }"
99
+ s
100
+ end
101
+
102
+ # Generate debug description of a boolean value
103
+ # @param flag value to interpret as a boolean; prints 'T' iff not nil
104
+ # @param label optional label
105
+ def df(flag, label=nil)
106
+ s = ''
107
+ if label
108
+ s << label << ':'
109
+ end
110
+ s << (flag ? "T" : "F")
111
+ s << ' '
112
+ s
113
+ end
114
+
115
+
116
+
117
+ # Assert that a value is true. Should be considered a
118
+ # very temporary, debug-only option; it is slow and
119
+ # generates a warning that it is being called.
120
+ # @param cond condition
121
+ # @param msg generates additional message using printf(), if these arguments exist
122
+ def assert!(cond, *msg)
123
+ one_time_alert("warning",0,"Checking assertion")
124
+ if not cond
125
+ str = (msg.size == 0) ? "assertion error" : sprintf(*msg)
126
+ raise Exception, str
127
+ end
128
+ end
129
+
130
+
131
+ ## Set test directory. If nil, sets to home directory + "__test__"
132
+ #def setTestDir(d = nil)
133
+ # $testDir = d || File.join(Dir.home,"__test__")
134
+ #end
135
+
136
+ ## Get a path within the test directory;
137
+ ## create test directory if it doesn't exist.
138
+ ##
139
+ ## relPath : if nil, returns the test directory; else
140
+ ## returns the test directory joined to this one
141
+ ##
142
+ #def withinTestDir(relPath = nil)
143
+ # if !$testDir
144
+ # raise IllegalStateException, "No test directory has been defined"
145
+ # end
146
+ # if !File.directory?($testDir)
147
+ # Dir::mkdir($testDir)
148
+ # end
149
+ # relPath ? File.join($testDir,relPath) : $testDir
150
+ #end
151
+
152
+ ## Convert a .dot file (string) to a PDF file "__mygraph__nnn.pdf"
153
+ ## in the test directory.
154
+ ##
155
+ ## It does this by making a system call to the 'dot' utility.
156
+ ##
157
+ #def dotToPDF(dotFile, name = "")
158
+ # gr = dotFile
159
+ # dotPath = withinTestDir(".__mygraph__.dot")
160
+ # write_text_file(dotPath,gr)
161
+ # destName = withinTestDir( "__mygraph__"+name+".pdf")
162
+ # system("dot -Tpdf "+dotPath+" -o "+destName)
163
+ #end
164
+
165
+ ## Extensions to the Enumerable module
166
+ ##
167
+ #module Enumerable
168
+ # # Calculate a value for each item, and return the item with the
169
+ # # highest value, its index, and the value.
170
+ # # @yieldparam function to calculate value of an object, given that object as a parameter
171
+ # # @return the triple [object, index, value] reflecting the maximum value, or
172
+ # # nil if there were no items
173
+ # def max_with_index
174
+ #
175
+ # best = nil
176
+ #
177
+ # each_with_index do |obj,ind|
178
+ # sc = yield(obj)
179
+ # if !best || best[2] < sc
180
+ # best = [obj,ind,sc]
181
+ # end
182
+ # end
183
+ # best
184
+ # end
185
+ #end
186
+
187
+
188
+ # Get a nice, concise description of the file and line
189
+ # of some caller within the stack.
190
+ #
191
+ # @param nSkip the number of items deep in the call stack to look
192
+ #
193
+ def get_caller_location(nSkip = 2)
194
+
195
+ filename = nil
196
+ linenumber = nil
197
+
198
+ if nSkip >= 0 && nSkip < caller.size
199
+ fi = caller[nSkip]
200
+
201
+ i = fi.index(':')
202
+ j = nil
203
+ if i
204
+ j = fi.index(':',i+1)
205
+ end
206
+ if j
207
+ pth = fi[0,i].split('/')
208
+ if pth.size
209
+ filename = pth[-1]
210
+ end
211
+ linenumber = fi[i+1,j-i-1]
212
+ end
213
+ end
214
+ if filename && linenumber
215
+ loc = filename + " ("+linenumber+")"
216
+ else
217
+ loc = "(UNKNOWN LOCATION)"
218
+ end
219
+ loc
220
+ end
221
+
222
+ # Set of alert strings that have already been reported
223
+ # (to avoid printing anything on subsequent invocations)
224
+ #
225
+ $AlertStrings = Set.new
226
+
227
+ # Print a message if it hasn't yet been printed,
228
+ # which includes the caller's location
229
+ #
230
+ # @param typeString e.g., "warning", "unimplemented"
231
+ # @param nSkip the number of levels deep that the caller is in the stack
232
+ # @param args if present, calls sprintf(...) with these to append to the message
233
+ #
234
+ def one_time_alert(typeString, nSkip, *args)
235
+ loc = get_caller_location(nSkip + 2)
236
+ s = "*** "+typeString+" " + loc
237
+ if args && args.size
238
+ s2 = sprintf(args[0], *args[1..-1])
239
+ msg = s + ": " + s2
240
+ else
241
+ msg = s
242
+ end
243
+
244
+ if $AlertStrings.add?(msg)
245
+ puts msg
246
+ end
247
+ end
248
+
249
+ # Print a 'warning' alert, one time only
250
+ # @param args if present, calls printf() with these
251
+ def warn(*args)
252
+ one_time_alert("warning",0, *args)
253
+ end
254
+
255
+ # Print an 'unimplemented' alert, one time only
256
+ # @param args if present, calls printf() with these
257
+ def unimp(*args)
258
+ one_time_alert("unimplemented", 0, *args)
259
+ end
260
+
261
+ # Write a string to a text file
262
+ #
263
+ def write_text_file(path, contents)
264
+ File.open(path, "wb") {|f| f.write(contents) }
265
+ end
266
+
267
+ # Read a file's contents, return as a string
268
+ #
269
+ def read_text_file(path)
270
+ contents = nil
271
+ File.open(path,"rb") {|f| contents = f.read }
272
+ contents
273
+ end
274
+
275
+ # Method that takes a code block as an argument to
276
+ # achieve the same functionality as Java/C++'s
277
+ # do {
278
+ # ...
279
+ # ... possibly with 'break' to jump to the end ...
280
+ # } while (false);
281
+ #
282
+ def block
283
+ yield
284
+ end
285
+
286
+ # Exception class for objects in illegal states
287
+ #
288
+ class IllegalStateException < Exception
289
+ end
290
+
291
+
292
+ def to_hex(value, num_digits=4)
293
+ s = sprintf("%x", value)
294
+ s.rjust(num_digits,'0')
295
+ end
296
+
297
+ def hex_dump(byte_array_or_string, title=nil, offset=0, length= -1, bytes_per_row=16, with_text=true)
298
+ ss = hex_dump_to_string(byte_array_or_string, title, offset, length, bytes_per_row, with_text)
299
+ puts ss
300
+ end
301
+
302
+ def hex_dump_to_string(byte_array_or_string, title=nil, offset=0, length= -1, bytes_per_row=16, with_text=true)
303
+
304
+ byte_array = byte_array_or_string
305
+ if byte_array.is_a? String
306
+ byte_array = byte_array.bytes.to_a
307
+ end
308
+
309
+ ss = ''
310
+
311
+ if title
312
+ ss << title << ":\n"
313
+ end
314
+
315
+ if length < 0
316
+ length = byte_array.size - offset
317
+ end
318
+
319
+ length = [length, byte_array.size - offset].min
320
+
321
+ max_addr = offset + length - 1
322
+ num_digits = 4
323
+ while (1 << (4 * num_digits)) <= max_addr
324
+ num_digits += 1
325
+ end
326
+
327
+ while true
328
+ ss << to_hex(offset, num_digits)
329
+ ss << ': '
330
+
331
+ chunk = [length, bytes_per_row].min
332
+ bytes_per_row.times do |i|
333
+ if i % 4 == 0
334
+ ss << ' '
335
+ end
336
+
337
+ if i < chunk
338
+ v = byte_array[offset + i]
339
+ ss << ((v != 0) ? to_hex(v,2) : '..')
340
+ ss << ' '
341
+ else
342
+ ss << ' '
343
+ end
344
+
345
+ end
346
+
347
+
348
+ if with_text
349
+ ss << ' |'
350
+ bytes_per_row.times do |i|
351
+ if i < chunk
352
+ v = byte_array[offset + i]
353
+ ss << ((v >= 32 && v < 127) ? v : '_')
354
+ end
355
+ end
356
+ ss << '|'
357
+ end
358
+ ss << "\n"
359
+
360
+ length -= chunk
361
+ offset += chunk
362
+ break if length <= 0
363
+ end
364
+
365
+ ss << "\n"
366
+ ss
367
+ end
368
+
369
+ $prevTime = nil
370
+
371
+ # Calculate time elapsed, in seconds, from last call to this function;
372
+ # if it's never been called, returns zero
373
+ def elapsed
374
+ curr = Time.now.to_f
375
+ elap = 0
376
+ if $prevTime
377
+ elap = curr - $prevTime
378
+ end
379
+ $prevTime = curr
380
+ elap
381
+ end
382
+
383
+ # Construct a string from an array of bytes
384
+ # @param byte_array array of bytes, or string (in which case it
385
+ # returns it unchanged)
386
+ #
387
+ def bytes_to_str(byte_array)
388
+ return byte_array if byte_array.is_a? String
389
+
390
+ byte_array.pack('C*')
391
+ end
392
+
393
+ # Construct an array of bytes from a string
394
+ # @param str string, or array of bytes (in which case it
395
+ # returns it unchanged)
396
+ #
397
+ def str_to_bytes(str)
398
+ return str if str.is_a? Array
399
+ str.bytes
400
+ end
401
+
402
+ # Get directory entries, excluding '.' and '..'
403
+ #
404
+ def dir_entries(path)
405
+ ents = Dir.entries(path)
406
+ ents.reject!{|entry| entry == '.' || entry == '..'}
407
+ end
408
+
409
+ # Convenience method for setting 'db' true within methods,
410
+ # and to print a one-time warning if so.
411
+ # @param val value to set db to; it is convenient to disable
412
+ # debug printing quickly by adding a zero, e.g., 'warndb 0'
413
+ #
414
+ def warndb(val = true)
415
+ if !val || val == 0
416
+ return false
417
+ end
418
+ one_time_alert("warning",1, "Debug printing enabled")
419
+ true
420
+ end
421
+
422
+
423
+ def int_to_bytes(x)
424
+ [(x >> 24) & 0xff, (x >> 16) & 0xff, (x >> 8) & 0xff, x & 0xff]
425
+ end
426
+
427
+ def short_to_bytes(x)
428
+ [(x >> 8) & 0xff, x & 0xff]
429
+ end
430
+
431
+ # Decode a short from an array of bytes (big-endian).
432
+ # @param ba array of bytes
433
+ # @param offset offset of first (most significant) byte
434
+ #
435
+ def short_from_bytes(ba, offset=0)
436
+ (ba[offset] << 8) | ba[offset + 1]
437
+ end
438
+
439
+ # Decode an int from an array of bytes (big-endian).
440
+ # @param ba array of bytes
441
+ # @param offset offset of first (most significant) byte
442
+ #
443
+ def int_from_bytes(ba, offset=0)
444
+ (((((ba[offset] << 8) | ba[offset + 1]) << 8) | \
445
+ ba[offset + 2]) << 8) | ba[offset + 3]
446
+ end
447
+
448
+
449
+ # Delete a file or directory, if it exists.
450
+ # Caution! If directory, deletes all files and subdirectories.
451
+ def remove_file_or_dir(pth)
452
+ if File.directory?(pth)
453
+ FileUtils.remove_dir(pth)
454
+ elsif File.file?(pth)
455
+ FileUtils.remove_file(pth)
456
+ end
457
+ end
458
+
459
+ # Transform string to 8-bit ASCII (i.e., just treat each byte as-is)
460
+ #
461
+ def to_ascii8(str)
462
+ str.force_encoding("ASCII-8BIT")
463
+ end
464
+
465
+ # Verify that a string is encoded as ASCII-8BIT
466
+ def simple_str(s)
467
+ if s.encoding.name != 'ASCII-8BIT' && s.encoding.name != 'UTF-8'
468
+ pr("string [%s]\n encoding is %s,\n expected ASCII-8BIT\n",s,s.encoding.name)
469
+ assert!(false)
470
+ end
471
+ end
472
+
473
+ # Truncate or pad string so it has a particular size
474
+ #
475
+ # @param s input string
476
+ # @param size
477
+ # @param pad padding character to use if string needs to grow
478
+ # @return modified string
479
+ #
480
+ def str_sized(s, size, pad="\0")
481
+ s[0...size].ljust(size,pad)
482
+ end
483
+
484
+ # Determine if running on the Windows operating system.
485
+ # Note: there is some debate about the best way to do this.
486
+ #
487
+ def windows?
488
+ if !defined? $__windows__
489
+ $__windows__ = (RUBY_PLATFORM =~ /mswin/)
490
+ end
491
+ $__windows__
492
+ end
493
+
494
+ # I didn't end up needing this:
495
+ #
496
+ #module Kernel
497
+ # def suppress_warnings
498
+ # original_verbosity = $VERBOSE
499
+ # $VERBOSE = nil
500
+ # result = yield
501
+ # $VERBOSE = original_verbosity
502
+ # return result
503
+ # end
504
+ #end
505
+
506
+ # Convenience method to detect if a script is being run
507
+ # e.g. as a 'main' method (for debug purposes only).
508
+ # If so, it changes the current directory to the
509
+ # directory containing the script.
510
+ #
511
+ # @param file pass __FILE__ in here
512
+ # @return true if so
513
+ #
514
+ def main?(file)
515
+
516
+ scr = $0
517
+
518
+ # The test/unit framework seems to be adding a suffix ": xxx#xxx.."
519
+ # to the .rb filename, so adjust in this case
520
+ i = scr.index(".rb: ")
521
+ if i
522
+ scr = scr[0...i+3]
523
+ end
524
+
525
+ if (ret = (file == scr))
526
+ Dir.chdir(File.dirname(file))
527
+ end
528
+ ret
529
+ end
530
+
531
+ if defined? Test::Unit
532
+
533
+ # Framework for suite testing
534
+ #
535
+ # If test suite functionality is desired within a script,
536
+ # then require 'test/unit' before requiring 'tools.rb'.
537
+ # This will cause the following class, MyTestSuite, to be defined.
538
+ #
539
+ # The user's test script can define subclasses of this,
540
+ # and declare test methods with the name 'test_xxxx', where
541
+ # xxxx is lexicographically between 01 and zz.
542
+ #
543
+ # There are two levels of setup/teardown called : suite level, and
544
+ # method level. For example, if the user's test class performs two tests:
545
+ #
546
+ # def test_b ... end
547
+ # def test_c ... end
548
+ #
549
+ # Then the test framework will make these calls:
550
+ #
551
+ # suite_setup
552
+ #
553
+ # method_setup
554
+ # test_b
555
+ # method_teardown
556
+ #
557
+ # method_setup
558
+ # test_c
559
+ # method_teardown
560
+ #
561
+ # suite_teardown
562
+ #
563
+ # Notes
564
+ # -----
565
+ # 1) The usual setup / teardown methods should NOT be overridden; instead,
566
+ # use the method_xxx alternatives.
567
+ #
568
+ # 2) The base class implementations of method_/suite_xxx do nothing.
569
+ #
570
+ # 3) The number of test cases reported may be higher than you expect, since
571
+ # there are test methods defined by the TestSuite class to acheive this
572
+ # functionality.
573
+ #
574
+ # 4) Avoid naming test methods that fall outside of test_01 ... test_zz.
575
+ #
576
+ class MyTestSuite < Test::Unit::TestCase
577
+
578
+ # This is named to be the FIRST test called. It
579
+ # will do suite-level setup, and nothing else.
580
+ def test_00_setup
581
+ @@suiteSetup = true
582
+ suite_setup()
583
+ end
584
+
585
+ # This is named to be the LAST test called. It
586
+ # will do suite-level teardown, and nothing else.
587
+ def test_zzzzzz_teardown
588
+ suite_teardown()
589
+ @@suiteSetup = false
590
+ end
591
+
592
+ # True if called within suite-level setup/teardown window
593
+ def _suite_active?
594
+ !(@__name__ == "test_00_setup" || @__name__ == "test_zzzzzz_teardown")
595
+ end
596
+
597
+ def setup
598
+ if _suite_active?
599
+ # If only a specific test was requested, the
600
+ # suite setup may not have run... if not, do it now.
601
+ if !defined? @@suiteSetup
602
+ suite_setup
603
+ end
604
+ return
605
+ end
606
+ method_setup
607
+ end
608
+
609
+ def teardown
610
+ if _suite_active?
611
+ if !defined? @@suiteSetup
612
+ suite_teardown
613
+ end
614
+ return
615
+ end
616
+ method_teardown
617
+ end
618
+
619
+ def suite_setup
620
+ end
621
+
622
+ def suite_teardown
623
+ end
624
+
625
+ def method_setup
626
+ end
627
+
628
+ def method_teardown
629
+ end
630
+ end
631
+ end
632
+