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