Linguistics 1.0.3

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/utils.rb ADDED
@@ -0,0 +1,689 @@
1
+ #
2
+ # Install/distribution utility functions
3
+ # $Id: utils.rb 8 2005-07-13 12:35:15Z ged $
4
+ #
5
+ # Copyright (c) 2001-2005, The FaerieMUD Consortium.
6
+ #
7
+ # This is free software. You may use, modify, and/or redistribute this
8
+ # software under the terms of the Perl Artistic License. (See
9
+ # http://language.perl.com/misc/Artistic.html)
10
+ #
11
+
12
+ BEGIN {
13
+ require 'rbconfig'
14
+ require 'uri'
15
+ require 'find'
16
+ require 'pp'
17
+
18
+ begin
19
+ require 'readline'
20
+ include Readline
21
+ rescue LoadError => e
22
+ $stderr.puts "Faking readline..."
23
+ def readline( prompt )
24
+ $stderr.print prompt.chomp
25
+ return $stdin.gets.chomp
26
+ end
27
+ end
28
+
29
+ begin
30
+ require 'yaml'
31
+ $yaml = true
32
+ rescue LoadError => e
33
+ $stderr.puts "No YAML; try() will use PrettyPrint instead."
34
+ $yaml = false
35
+ end
36
+ }
37
+
38
+
39
+ module UtilityFunctions
40
+ include Config
41
+
42
+ # The list of regexen that eliminate files from the MANIFEST
43
+ ANTIMANIFEST = [
44
+ /makedist\.rb/,
45
+ /\bCVS\b/,
46
+ /~$/,
47
+ /^#/,
48
+ %r{docs/html},
49
+ %r{docs/man},
50
+ /\bTEMPLATE\.\w+\.tpl\b/,
51
+ /\.cvsignore/,
52
+ /\.s?o$/,
53
+ ]
54
+
55
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
56
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
57
+ AnsiAttributes = {
58
+ 'clear' => 0,
59
+ 'reset' => 0,
60
+ 'bold' => 1,
61
+ 'dark' => 2,
62
+ 'underline' => 4,
63
+ 'underscore' => 4,
64
+ 'blink' => 5,
65
+ 'reverse' => 7,
66
+ 'concealed' => 8,
67
+
68
+ 'black' => 30, 'on_black' => 40,
69
+ 'red' => 31, 'on_red' => 41,
70
+ 'green' => 32, 'on_green' => 42,
71
+ 'yellow' => 33, 'on_yellow' => 43,
72
+ 'blue' => 34, 'on_blue' => 44,
73
+ 'magenta' => 35, 'on_magenta' => 45,
74
+ 'cyan' => 36, 'on_cyan' => 46,
75
+ 'white' => 37, 'on_white' => 47
76
+ }
77
+
78
+ ErasePreviousLine = "\033[A\033[K"
79
+
80
+ ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' )
81
+ #
82
+ # Distribution Manifest
83
+ # Created: #{Time::now.to_s}
84
+ #
85
+
86
+ EOF
87
+
88
+ ###############
89
+ module_function
90
+ ###############
91
+
92
+ # Create a string that contains the ANSI codes specified and return it
93
+ def ansiCode( *attributes )
94
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
95
+ attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
96
+ if attr.empty?
97
+ return ''
98
+ else
99
+ return "\e[%sm" % attr
100
+ end
101
+ end
102
+
103
+ # Test for the presence of the specified <tt>library</tt>, and output a
104
+ # message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
105
+ # is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
106
+ def testForLibrary( library, nicename=nil, progress=false )
107
+ nicename ||= library
108
+ message( "Testing for the #{nicename} library..." ) if progress
109
+ if $LOAD_PATH.detect {|dir|
110
+ File.exists?(File.join(dir,"#{library}.rb")) ||
111
+ File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}"))
112
+ }
113
+ message( "found.\n" ) if progress
114
+ return true
115
+ else
116
+ message( "not found.\n" ) if progress
117
+ return false
118
+ end
119
+ end
120
+
121
+ # Test for the presence of the specified <tt>library</tt>, and output a
122
+ # message describing the problem using <tt>nicename</tt>. If
123
+ # <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
124
+ # to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
125
+ # specified, they are also use to build a message describing how to find the
126
+ # required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
127
+ # will cause the program to abort.
128
+ def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
129
+ nicename ||= library
130
+ unless testForLibrary( library, nicename )
131
+ msgs = [ "You are missing the required #{nicename} library.\n" ]
132
+ msgs << "RAA: #{raaUrl}\n" if raaUrl
133
+ msgs << "Download: #{downloadUrl}\n" if downloadUrl
134
+ if fatal
135
+ abort msgs.join('')
136
+ else
137
+ errorMessage msgs.join('')
138
+ end
139
+ end
140
+ return true
141
+ end
142
+
143
+ ### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
144
+ ### blue).
145
+ def header( msg )
146
+ msg.chomp!
147
+ $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
148
+ $stderr.flush
149
+ end
150
+
151
+ ### Output <tt>msg</tt> to STDERR and flush it.
152
+ def message( *msgs )
153
+ $stderr.print( msgs.join("\n") )
154
+ $stderr.flush
155
+ end
156
+
157
+ ### Output +msg+ to STDERR and flush it if $VERBOSE is true.
158
+ def verboseMsg( msg )
159
+ msg.chomp!
160
+ message( msg + "\n" ) if $VERBOSE
161
+ end
162
+
163
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
164
+ ### (white on red).
165
+ def errorMessage( msg )
166
+ message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
167
+ end
168
+
169
+ ### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
170
+ ### (yellow on blue).
171
+ def debugMsg( msg )
172
+ return unless $DEBUG
173
+ msg.chomp!
174
+ $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
175
+ $stderr.flush
176
+ end
177
+
178
+ ### Erase the previous line (if supported by your terminal) and output the
179
+ ### specified <tt>msg</tt> instead.
180
+ def replaceMessage( msg )
181
+ $stderr.print ErasePreviousLine
182
+ message( msg )
183
+ end
184
+
185
+ ### Output a divider made up of <tt>length</tt> hyphen characters.
186
+ def divider( length=75 )
187
+ $stderr.puts "\r" + ("-" * length )
188
+ end
189
+ alias :writeLine :divider
190
+
191
+
192
+ ### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
193
+ ### status of 1.
194
+ def abort( msg )
195
+ print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
196
+ Kernel.exit!( 1 )
197
+ end
198
+
199
+
200
+ ### Output the specified <tt>promptString</tt> as a prompt (in green) and
201
+ ### return the user's input with leading and trailing spaces removed. If a
202
+ ### test is provided, the prompt will repeat until the test returns true.
203
+ ### An optional failure message can also be passed in.
204
+ def prompt( promptString, failure_msg="Try again." ) # :yields: response
205
+ promptString.chomp!
206
+ response = nil
207
+
208
+ begin
209
+ response = readline( ansiCode('bold', 'green') +
210
+ "#{promptString}: " + ansiCode('reset') ).strip
211
+ if block_given? && ! yield( response )
212
+ errorMessage( failure_msg + "\n\n" )
213
+ response = nil
214
+ end
215
+ end until response
216
+
217
+ return response
218
+ end
219
+
220
+
221
+ ### Prompt the user with the given <tt>promptString</tt> via #prompt,
222
+ ### substituting the given <tt>default</tt> if the user doesn't input
223
+ ### anything. If a test is provided, the prompt will repeat until the test
224
+ ### returns true. An optional failure message can also be passed in.
225
+ def promptWithDefault( promptString, default, failure_msg="Try again." )
226
+ response = nil
227
+
228
+ begin
229
+ response = prompt( "%s [%s]" % [ promptString, default ] )
230
+ response = default if response.empty?
231
+
232
+ if block_given? && ! yield( response )
233
+ errorMessage( failure_msg + "\n\n" )
234
+ response = nil
235
+ end
236
+ end until response
237
+
238
+ return response
239
+ end
240
+
241
+
242
+ $programs = {}
243
+
244
+ ### Search for the program specified by the given <tt>progname</tt> in the
245
+ ### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
246
+ ### no such program is in the path.
247
+ def findProgram( progname )
248
+ unless $programs.key?( progname )
249
+ ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
250
+ file = File.join( d, progname )
251
+ if File.executable?( file )
252
+ $programs[ progname ] = file
253
+ break
254
+ end
255
+ }
256
+ end
257
+
258
+ return $programs[ progname ]
259
+ end
260
+
261
+
262
+ ### Search for the release version for the project in the specified
263
+ ### +directory+.
264
+ def extractVersion( directory='.' )
265
+ release = nil
266
+
267
+ Dir::chdir( directory ) do
268
+ if File::directory?( "CVS" )
269
+ verboseMsg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." )
270
+
271
+ if (( cvs = findProgram('cvs') ))
272
+ revs = []
273
+ output = %x{cvs log}
274
+ output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match|
275
+ rev = $1.split(/_/).collect {|s| Integer(s) rescue 0}
276
+ verboseMsg( "Found %s...\n" % rev.join('.') )
277
+ revs << rev
278
+ }
279
+
280
+ release = revs.sort.last
281
+ end
282
+
283
+ elsif File::directory?( '.svn' )
284
+ verboseMsg( "Project is versioned via Subversion" )
285
+
286
+ if (( svn = findProgram('svn') ))
287
+ output = %x{svn pg project-version}.chomp
288
+ unless output.empty?
289
+ verboseMsg( "Using 'project-version' property: %p" % output )
290
+ release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0}
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ return release
297
+ end
298
+
299
+
300
+ ### Find the current release version for the project in the specified
301
+ ### +directory+ and return its successor.
302
+ def extractNextVersion( directory='.' )
303
+ version = extractVersion( directory ) || [0,0,0]
304
+ version.compact!
305
+ version[-1] += 1
306
+
307
+ return version
308
+ end
309
+
310
+
311
+ # Pattern for extracting the name of the project from a Subversion URL
312
+ SVNUrlPath = %r{
313
+ .*/ # Skip all but the last bit
314
+ (\w+) # $1 = project name
315
+ / # Followed by / +
316
+ (?:
317
+ trunk | # 'trunk'
318
+ (
319
+ branches | # ...or branches/branch-name
320
+ tags # ...or tags/tag-name
321
+ )/\w
322
+ )
323
+ $ # bound to the end
324
+ }ix
325
+
326
+ ### Extract the project name (CVS Repository name) for the given +directory+.
327
+ def extractProjectName( directory='.' )
328
+ name = nil
329
+
330
+ Dir::chdir( directory ) do
331
+
332
+ # CVS-controlled
333
+ if File::directory?( "CVS" )
334
+ verboseMsg( "Project is versioned via CVS. Using repository name." )
335
+ name = File.open( "CVS/Repository", "r").readline.chomp
336
+ name.sub!( %r{.*/}, '' )
337
+
338
+ # Subversion-controlled
339
+ elsif File::directory?( '.svn' )
340
+ verboseMsg( "Project is versioned via Subversion" )
341
+
342
+ # If the machine has the svn tool, try to get the project name
343
+ if (( svn = findProgram( 'svn' ) ))
344
+
345
+ # First try an explicit property
346
+ output = shellCommand( svn, 'pg', 'project-name' )
347
+ if !output.empty?
348
+ verboseMsg( "Using 'project-name' property: %p" % output )
349
+ name = output.first.chomp
350
+
351
+ # If that doesn't work, try to figure it out from the URL
352
+ elsif (( uri = getSvnUri() ))
353
+ name = uri.path.sub( SVNUrlPath ) { $1 }
354
+ end
355
+ end
356
+ end
357
+
358
+ # Fall back to guessing based on the directory name
359
+ unless name
360
+ name = File::basename(File::dirname( File::expand_path(__FILE__) ))
361
+ end
362
+ end
363
+
364
+ return name
365
+ end
366
+
367
+
368
+ ### Extract the Subversion URL from the specified directory and return it as
369
+ ### a URI object.
370
+ def getSvnUri( directory='.' )
371
+ uri = nil
372
+
373
+ Dir::chdir( directory ) do
374
+ output = %x{svn info}
375
+ debugMsg( "Using info: %p" % output )
376
+
377
+ if /^URL: \s* ( .* )/xi.match( output )
378
+ uri = URI::parse( $1 )
379
+ end
380
+ end
381
+
382
+ return uri
383
+ end
384
+
385
+
386
+ ### (Re)make a manifest file in the specified +path+.
387
+ def makeManifest( path="MANIFEST" )
388
+ if File::exists?( path )
389
+ reply = promptWithDefault( "Replace current '#{path}'? [yN]", "n" )
390
+ return false unless /^y/i.match( reply )
391
+
392
+ verboseMsg "Replacing manifest at '#{path}'"
393
+ else
394
+ verboseMsg "Creating new manifest at '#{path}'"
395
+ end
396
+
397
+ files = []
398
+ verboseMsg( "Finding files...\n" )
399
+ Find::find( Dir::pwd ) do |f|
400
+ Find::prune if File::directory?( f ) &&
401
+ /^\./.match( File::basename(f) )
402
+ verboseMsg( " found: #{f}\n" )
403
+ files << f.sub( %r{^#{Dir::pwd}/?}, '' )
404
+ end
405
+ files = vetManifest( files )
406
+
407
+ verboseMsg( "Writing new manifest to #{path}..." )
408
+ File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
409
+ ofh.puts( ManifestHeader )
410
+ ofh.puts( files )
411
+ end
412
+ verboseMsg( "done." )
413
+ end
414
+
415
+
416
+ ### Read the specified <tt>manifestFile</tt>, which is a text file
417
+ ### describing which files to package up for a distribution. The manifest
418
+ ### should consist of one or more lines, each containing one filename or
419
+ ### shell glob pattern.
420
+ def readManifest( manifestFile="MANIFEST" )
421
+ verboseMsg "Building manifest..."
422
+ raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
423
+
424
+ manifest = IO::readlines( manifestFile ).collect {|line|
425
+ line.chomp
426
+ }.select {|line|
427
+ line !~ /^(\s*(#.*)?)?$/
428
+ }
429
+
430
+ filelist = []
431
+ for pat in manifest
432
+ verboseMsg "Adding files that match '#{pat}' to the file list"
433
+ filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
434
+ end
435
+
436
+ verboseMsg "found #{filelist.length} files.\n"
437
+ return filelist
438
+ end
439
+
440
+
441
+ ### Given a <tt>filelist</tt> like that returned by #readManifest, remove
442
+ ### the entries therein which match the Regexp objects in the given
443
+ ### <tt>antimanifest</tt> and return the resultant Array.
444
+ def vetManifest( filelist, antimanifest=ANTIMANIFEST )
445
+ origLength = filelist.length
446
+ verboseMsg "Vetting manifest..."
447
+
448
+ for regex in antimanifest
449
+ verboseMsg "\n\tPattern /#{regex.source}/ removed: " +
450
+ filelist.find_all {|file| regex.match(file)}.join(', ')
451
+ filelist.delete_if {|file| regex.match(file)}
452
+ end
453
+
454
+ verboseMsg "removed #{origLength - filelist.length} files from the list.\n"
455
+ return filelist
456
+ end
457
+
458
+
459
+ ### Combine a call to #readManifest with one to #vetManifest.
460
+ def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
461
+ vetManifest( readManifest(manifestFile), antimanifest )
462
+ end
463
+
464
+
465
+ ### Given a documentation <tt>catalogFile</tt>, extract the title, if
466
+ ### available, and return it. Otherwise generate a title from the name of
467
+ ### the CVS module.
468
+ def findRdocTitle( catalogFile="docs/CATALOG" )
469
+
470
+ # Try extracting it from the CATALOG file from a line that looks like:
471
+ # Title: Foo Bar Module
472
+ title = findCatalogKeyword( 'title', catalogFile )
473
+
474
+ # If that doesn't work for some reason, use the name of the project.
475
+ title = extractProjectName()
476
+
477
+ return title
478
+ end
479
+
480
+
481
+ ### Given a documentation <tt>catalogFile</tt>, extract the name of the file
482
+ ### to use as the initally displayed page. If extraction fails, the
483
+ ### +default+ will be used if it exists. Returns +nil+ if there is no main
484
+ ### file to be found.
485
+ def findRdocMain( catalogFile="docs/CATALOG", default="README" )
486
+
487
+ # Try extracting it from the CATALOG file from a line that looks like:
488
+ # Main: Foo Bar Module
489
+ main = findCatalogKeyword( 'main', catalogFile )
490
+
491
+ # Try to make some educated guesses if that doesn't work
492
+ if main.nil?
493
+ basedir = File::dirname( __FILE__ )
494
+ basedir = File::dirname( basedir ) if /docs$/ =~ basedir
495
+
496
+ if File::exists?( File::join(basedir, default) )
497
+ main = default
498
+ end
499
+ end
500
+
501
+ return main
502
+ end
503
+
504
+
505
+ ### Given a documentation <tt>catalogFile</tt>, extract an upload URL for
506
+ ### RDoc.
507
+ def findRdocUpload( catalogFile="docs/CATALOG" )
508
+ findCatalogKeyword( 'upload', catalogFile )
509
+ end
510
+
511
+
512
+ ### Given a documentation <tt>catalogFile</tt>, extract a CVS web frontend
513
+ ### URL for RDoc.
514
+ def findRdocCvsURL( catalogFile="docs/CATALOG" )
515
+ findCatalogKeyword( 'webcvs', catalogFile )
516
+ end
517
+
518
+
519
+ ### Given a documentation <tt>catalogFile</tt>, try extracting the given
520
+ ### +keyword+'s value from it. Keywords are lines that look like:
521
+ ### # <keyword>: <value>
522
+ ### Returns +nil+ if the catalog file was unreadable or didn't contain the
523
+ ### specified +keyword+.
524
+ def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" )
525
+ val = nil
526
+
527
+ if File::exists? catalogFile
528
+ verboseMsg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
529
+ File::foreach( catalogFile ) {|line|
530
+ debugMsg( "Examining line #{line.inspect}..." )
531
+ val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line
532
+ }
533
+ end
534
+
535
+ return val
536
+ end
537
+
538
+
539
+ ### Given a documentation <tt>catalogFile</tt>, which is in the same format
540
+ ### as that described by #readManifest, read and expand it, and then return
541
+ ### a list of those files which appear to have RDoc documentation in
542
+ ### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
543
+ ### file is used instead.
544
+ def findRdocableFiles( catalogFile="docs/CATALOG" )
545
+ startlist = []
546
+ if File.exists? catalogFile
547
+ verboseMsg "Using CATALOG file (%s).\n" % catalogFile
548
+ startlist = getVettedManifest( catalogFile )
549
+ else
550
+ verboseMsg "Using default MANIFEST\n"
551
+ startlist = getVettedManifest()
552
+ end
553
+
554
+ verboseMsg "Looking for RDoc comments in:\n"
555
+ startlist.select {|fn|
556
+ verboseMsg " #{fn}: "
557
+ found = false
558
+ File::open( fn, "r" ) {|fh|
559
+ fh.each {|line|
560
+ if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
561
+ found = true
562
+ break
563
+ end
564
+ }
565
+ }
566
+
567
+ verboseMsg( (found ? "yes" : "no") + "\n" )
568
+ found
569
+ }
570
+ end
571
+
572
+ ### Open a file and filter each of its lines through the given block a
573
+ ### <tt>line</tt> at a time. The return value of the block is used as the
574
+ ### new line, or omitted if the block returns <tt>nil</tt> or
575
+ ### <tt>false</tt>.
576
+ def editInPlace( file, testMode=false ) # :yields: line
577
+ raise "No block specified for editing operation" unless block_given?
578
+
579
+ tempName = "#{file}.#{$$}"
580
+ File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
581
+ File::open( file, File::RDONLY ) {|fh|
582
+ fh.each {|line|
583
+ newline = yield( line ) or next
584
+ tempfile.print( newline )
585
+ $deferr.puts "%p -> %p" % [ line, newline ] if
586
+ line != newline
587
+ }
588
+ }
589
+ }
590
+
591
+ if testMode
592
+ File::unlink( tempName )
593
+ else
594
+ File::rename( tempName, file )
595
+ end
596
+ end
597
+
598
+ ### Execute the specified shell <tt>command</tt>, read the results, and
599
+ ### return them. Like a %x{} that returns an Array instead of a String.
600
+ def shellCommand( *command )
601
+ raise "Empty command" if command.empty?
602
+
603
+ cmdpipe = IO::popen( command.join(' '), 'r' )
604
+ return cmdpipe.readlines
605
+ end
606
+
607
+ ### Execute a block with $VERBOSE set to +false+, restoring it to its
608
+ ### previous value before returning.
609
+ def verboseOff
610
+ raise LocalJumpError, "No block given" unless block_given?
611
+
612
+ thrcrit = Thread.critical
613
+ oldverbose = $VERBOSE
614
+ begin
615
+ Thread.critical = true
616
+ $VERBOSE = false
617
+ yield
618
+ ensure
619
+ $VERBOSE = oldverbose
620
+ Thread.critical = false
621
+ end
622
+ end
623
+
624
+
625
+ ### Try the specified code block, printing the given
626
+ def try( msg, bind=nil )
627
+ result = ''
628
+ if msg =~ /^to\s/
629
+ message = "Trying #{msg}..."
630
+ else
631
+ message = msg
632
+ end
633
+
634
+ begin
635
+ rval = nil
636
+ if block_given?
637
+ rval = yield
638
+ else
639
+ file, line = caller(1)[0].split(/:/,2)
640
+ rval = eval( msg, bind, file, line.to_i )
641
+ end
642
+
643
+ if $yaml
644
+ result = rval.to_yaml
645
+ else
646
+ PP.pp( rval, result )
647
+ end
648
+
649
+ rescue Exception => err
650
+ if err.backtrace
651
+ nicetrace = err.backtrace.delete_if {|frame|
652
+ /in `(try|eval)'/ =~ frame
653
+ }.join("\n\t")
654
+ else
655
+ nicetrace = "Exception had no backtrace"
656
+ end
657
+
658
+ result = err.message + "\n\t" + nicetrace
659
+ ensure
660
+ divider
661
+ message result + "\n"
662
+ divider
663
+ $deferr.puts
664
+ end
665
+ end
666
+ end
667
+
668
+
669
+ if __FILE__ == $0
670
+ # $DEBUG = true
671
+ include UtilityFunctions
672
+
673
+ projname = extractProjectName()
674
+ header "Project: #{projname}"
675
+
676
+ ver = extractVersion() || [0,0,1]
677
+ puts "Version: %s\n" % ver.join('.')
678
+
679
+ if File::directory?( "docs" )
680
+ puts "Rdoc:",
681
+ " Title: " + findRdocTitle(),
682
+ " Main: " + findRdocMain(),
683
+ " Upload: " + findRdocUpload(),
684
+ " SCCS URL: " + findRdocCvsURL()
685
+ end
686
+
687
+ puts "Manifest:",
688
+ " " + getVettedManifest().join("\n ")
689
+ end