Changeset 237
- Timestamp:
- 06/20/05 08:56:59 (3 years ago)
- Files:
-
- 1 modified
-
trunk/utils.rb (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/utils.rb
r233 r237 10 10 # 11 11 12 13 12 BEGIN { 13 require 'rbconfig' 14 require 'uri' 15 require 'find' 16 14 17 begin 15 18 require 'readline' … … 32 35 } 33 36 34 require 'rbconfig'35 include Config36 require 'pp'37 37 38 38 module UtilityFunctions 39 include Config 39 40 40 41 # The list of regexen that eliminate files from the MANIFEST … … 46 47 %r{docs/html}, 47 48 %r{docs/man}, 48 / ^TEMPLATE/,49 /\bTEMPLATE\.\w+\.tpl\b/, 49 50 /\.cvsignore/, 50 /\.s?o$/ 51 /\.s?o$/, 51 52 ] 52 53 … … 76 77 ErasePreviousLine = "\033[A\033[K" 77 78 79 ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' ) 80 # 81 # Distribution Manifest 82 # Created: #{Time::now.to_s} 83 # 84 85 EOF 78 86 79 87 ############### … … 83 91 # Create a string that contains the ANSI codes specified and return it 84 92 def ansiCode( *attributes ) 85 return '' unless /(?:vt10[03]|xterm(?:-color)?|linux )/i =~ ENV['TERM']93 return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM'] 86 94 attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';') 87 95 if attr.empty? … … 92 100 end 93 101 94 ### Return the given +prompt+ with the specified +attributes+ turned on and95 ### a reset at the end.96 def colored( prompt, *attributes )97 return ansiCode( *(attributes.flatten) ) + prompt + ansiCode( 'reset' )98 end99 100 101 102 # Test for the presence of the specified <tt>library</tt>, and output a 102 103 # message describing the test using <tt>nicename</tt>. If <tt>nicename</tt> 103 104 # is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default. 104 def testForLibrary( library, nicename=nil )105 def testForLibrary( library, nicename=nil, progress=false ) 105 106 nicename ||= library 106 message( "Testing for the #{nicename} library..." ) 107 message( "Testing for the #{nicename} library..." ) if progress 107 108 if $LOAD_PATH.detect {|dir| 108 109 File.exists?(File.join(dir,"#{library}.rb")) || 109 110 File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}")) 110 111 } 111 message( "found.\n" ) 112 message( "found.\n" ) if progress 112 113 return true 113 114 else 114 message( "not found.\n" ) 115 message( "not found.\n" ) if progress 115 116 return false 116 117 end … … 148 149 149 150 ### Output <tt>msg</tt> to STDERR and flush it. 150 def message( msg)151 $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset')151 def message( *msgs ) 152 $stderr.print( msgs.join("\n") ) 152 153 $stderr.flush 154 end 155 156 ### Output +msg+ to STDERR and flush it if $VERBOSE is true. 157 def verboseMsg( msg ) 158 msg.chomp! 159 message( msg + "\n" ) if $VERBOSE 153 160 end 154 161 … … 181 188 alias :writeLine :divider 182 189 190 183 191 ### Output the specified <tt>msg</tt> colored in ANSI red and exit with a 184 192 ### status of 1. … … 188 196 end 189 197 198 190 199 ### Output the specified <tt>promptString</tt> as a prompt (in green) and 191 200 ### return the user's input with leading and trailing spaces removed. If a 192 201 ### test is provided, the prompt will repeat until the test returns true. 193 202 ### An optional failure message can also be passed in. 194 def prompt( promptString, failure_msg="Try again." , &test )203 def prompt( promptString, failure_msg="Try again." ) # :yields: response 195 204 promptString.chomp! 196 response = readline( ansiCode('bold', 'green') + 197 "#{promptString}: " + ansiCode('reset') ).strip 198 until test.call(response) 199 errorMessage(failure_msg) 200 message("\n") 201 response = prompt( promptString ) 202 end if test 205 promptString << ":" unless /\W$/.match( promptString ) 206 response = nil 207 208 begin 209 response = readline( ansiCode('bold', 'green') + 210 "#{promptString} " + ansiCode('reset') ) || '' 211 response.strip! 212 if block_given? && ! yield( response ) 213 errorMessage( failure_msg + "\n\n" ) 214 response = nil 215 end 216 end until response 217 203 218 return response 204 219 end 220 205 221 206 222 ### Prompt the user with the given <tt>promptString</tt> via #prompt, … … 208 224 ### anything. If a test is provided, the prompt will repeat until the test 209 225 ### returns true. An optional failure message can also be passed in. 210 def promptWithDefault( promptString, default, failure_msg="Try again.", &test ) 211 response = prompt( "%s [%s]" % [ promptString, default ] ) 212 response = default if response.empty? 213 until test.call(response) 214 errorMessage(faiure_msg) 215 message("\n") 216 response = promptWithDefault( promptString, default ) 217 end if test 226 def promptWithDefault( promptString, default, failure_msg="Try again." ) 227 response = nil 228 229 begin 230 response = prompt( "%s [%s]" % [ promptString, default ] ) 231 response = default if response.empty? 232 233 if block_given? && ! yield( response ) 234 errorMessage( failure_msg + "\n\n" ) 235 response = nil 236 end 237 end until response 238 218 239 return response 219 240 end 241 242 243 $programs = {} 220 244 221 245 ### Search for the program specified by the given <tt>progname</tt> in the … … 223 247 ### no such program is in the path. 224 248 def findProgram( progname ) 225 ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| 226 file = File.join( d, progname ) 227 return file if File.executable?( file ) 228 } 229 return nil 230 end 231 232 ### Using the CVS log for the given <tt>file</tt> attempt to guess what the 233 ### next release version might be. This only works if releases are tagged 234 ### with tags like 'RELEASE_x_y'. 235 def extractNextVersionFromTags( file ) 236 message "Attempting to extract next release version from CVS tags for #{file}...\n" 237 raise RuntimeError, "No such file '#{file}'" unless File.exists?( file ) 238 cvsPath = findProgram( 'cvs' ) or 239 raise RuntimeError, "Cannot find the 'cvs' program. Aborting." 240 241 output = %x{#{cvsPath} log #{file}} 242 release = [ 0, 0 ] 243 output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match| 244 if $1.to_i > release[0] || $2.to_i > release[1] 245 release = [ $1.to_i, $2.to_i ] 246 replaceMessage( "Found %d.%02d...\n" % release ) 247 end 248 } 249 250 if release[1] >= 99 251 release[0] += 1 252 release[1] = 1 249 unless $programs.key?( progname ) 250 ENV['PATH'].split(File::PATH_SEPARATOR).each {|d| 251 file = File.join( d, progname ) 252 if File.executable?( file ) 253 $programs[ progname ] = file 254 break 255 end 256 } 257 end 258 259 return $programs[ progname ] 260 end 261 262 263 ### Search for the release version for the project in the specified 264 ### +directory+. 265 def extractVersion( directory='.' ) 266 release = nil 267 268 Dir::chdir( directory ) do 269 if File::directory?( "CVS" ) 270 verboseMsg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." ) 271 272 if (( cvs = findProgram('cvs') )) 273 revs = [] 274 output = %x{cvs log} 275 output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match| 276 rev = $1.split(/_/).collect {|s| Integer(s) rescue 0} 277 verboseMsg( "Found %s...\n" % rev.join('.') ) 278 revs << rev 279 } 280 281 release = revs.sort.last 282 end 283 284 elsif File::directory?( '.svn' ) 285 verboseMsg( "Project is versioned via Subversion" ) 286 287 if (( svn = findProgram('svn') )) 288 output = %x{svn pg project-version}.chomp 289 unless output.empty? 290 verboseMsg( "Using 'project-version' property: %p" % output ) 291 release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0} 292 end 293 end 294 end 295 end 296 297 return release 298 end 299 300 301 ### Find the current release version for the project in the specified 302 ### +directory+ and return its successor. 303 def extractNextVersion( directory='.' ) 304 version = extractVersion( directory ) || [0,0,0] 305 version.compact! 306 version[-1] += 1 307 308 return version 309 end 310 311 312 # Pattern for extracting the name of the project from a Subversion URL 313 SVNUrlPath = %r{ 314 .*/ # Skip all but the last bit 315 (\w+) # $1 = project name 316 / # Followed by / + 317 (?: 318 trunk | # 'trunk' 319 ( 320 branches | # ...or branches/branch-name 321 tags # ...or tags/tag-name 322 )/\w 323 ) 324 $ # bound to the end 325 }ix 326 327 ### Extract the project name (CVS Repository name) for the given +directory+. 328 def extractProjectName( directory='.' ) 329 name = nil 330 331 Dir::chdir( directory ) do 332 333 # CVS-controlled 334 if File::directory?( "CVS" ) 335 verboseMsg( "Project is versioned via CVS. Using repository name." ) 336 name = File.open( "CVS/Repository", "r").readline.chomp 337 name.sub!( %r{.*/}, '' ) 338 339 # Subversion-controlled 340 elsif File::directory?( '.svn' ) 341 verboseMsg( "Project is versioned via Subversion" ) 342 343 # If the machine has the svn tool, try to get the project name 344 if (( svn = findProgram( 'svn' ) )) 345 346 # First try an explicit property 347 output = shellCommand( svn, 'pg', 'project-name' ) 348 if !output.empty? 349 verboseMsg( "Using 'project-name' property: %p" % output ) 350 name = output.first.chomp 351 352 # If that doesn't work, try to figure it out from the URL 353 elsif (( uri = getSvnUri() )) 354 name = uri.path.sub( SVNUrlPath ) { $1 } 355 end 356 end 357 end 358 359 # Fall back to guessing based on the directory name 360 unless name 361 name = File::basename(File::dirname( File::expand_path(__FILE__) )) 362 end 363 end 364 365 return name 366 end 367 368 369 ### Extract the Subversion URL from the specified directory and return it as 370 ### a URI object. 371 def getSvnUri( directory='.' ) 372 uri = nil 373 374 Dir::chdir( directory ) do 375 output = %x{svn info} 376 debugMsg( "Using info: %p" % output ) 377 378 if /^URL: \s* ( .* )/xi.match( output ) 379 uri = URI::parse( $1 ) 380 end 381 end 382 383 return uri 384 end 385 386 387 ### (Re)make a manifest file in the specified +path+. 388 def makeManifest( path="MANIFEST" ) 389 if File::exists?( path ) 390 reply = promptWithDefault( "Replace current '#{path}'? [yN]", "n" ) 391 return false unless /^y/i.match( reply ) 392 393 verboseMsg "Replacing manifest at '#{path}'" 253 394 else 254 release[1] += 1 255 end 256 257 return "%d.%02d" % release 258 end 259 260 ### Extract the project name (CVS Repository name) for the given directory. 261 def extractProjectName 262 File.open( "CVS/Repository", "r").readline.chomp 263 end 395 verboseMsg "Creating new manifest at '#{path}'" 396 end 397 398 files = [] 399 verboseMsg( "Finding files...\n" ) 400 Find::find( Dir::pwd ) do |f| 401 Find::prune if File::directory?( f ) && 402 /^\./.match( File::basename(f) ) 403 verboseMsg( " found: #{f}\n" ) 404 files << f.sub( %r{^#{Dir::pwd}/?}, '' ) 405 end 406 files = vetManifest( files ) 407 408 verboseMsg( "Writing new manifest to #{path}..." ) 409 File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh| 410 ofh.puts( ManifestHeader ) 411 ofh.puts( files ) 412 end 413 verboseMsg( "done." ) 414 end 415 264 416 265 417 ### Read the specified <tt>manifestFile</tt>, which is a text file … … 268 420 ### shell glob pattern. 269 421 def readManifest( manifestFile="MANIFEST" ) 270 message"Building manifest..."422 verboseMsg "Building manifest..." 271 423 raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile 272 424 … … 279 431 filelist = [] 280 432 for pat in manifest 281 $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE433 verboseMsg "Adding files that match '#{pat}' to the file list" 282 434 filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)} 283 435 end 284 436 285 message"found #{filelist.length} files.\n"437 verboseMsg "found #{filelist.length} files.\n" 286 438 return filelist 287 439 end 440 288 441 289 442 ### Given a <tt>filelist</tt> like that returned by #readManifest, remove 290 443 ### the entries therein which match the Regexp objects in the given 291 444 ### <tt>antimanifest</tt> and return the resultant Array. 292 def vetManifest( filelist, antimanifest=AN ITMANIFEST )445 def vetManifest( filelist, antimanifest=ANTIMANIFEST ) 293 446 origLength = filelist.length 294 message"Vetting manifest..."447 verboseMsg "Vetting manifest..." 295 448 296 449 for regex in antimanifest 297 if $VERBOSE 298 message "\n\tPattern /#{regex.source}/ removed: " + 299 filelist.find_all {|file| regex.match(file)}.join(', ') 300 end 450 verboseMsg "\n\tPattern /#{regex.source}/ removed: " + 451 filelist.find_all {|file| regex.match(file)}.join(', ') 301 452 filelist.delete_if {|file| regex.match(file)} 302 453 end 303 454 304 message"removed #{origLength - filelist.length} files from the list.\n"455 verboseMsg "removed #{origLength - filelist.length} files from the list.\n" 305 456 return filelist 306 457 end 458 307 459 308 460 ### Combine a call to #readManifest with one to #vetManifest. … … 310 462 vetManifest( readManifest(manifestFile), antimanifest ) 311 463 end 464 312 465 313 466 ### Given a documentation <tt>catalogFile</tt>, extract the title, if … … 320 473 title = findCatalogKeyword( 'title', catalogFile ) 321 474 322 # If that doesn't work for some reason, try grabbing the name of the CVS 323 # repository the directory belongs to. 324 if title.nil? && File::directory?( "CVS" ) && 325 File::exists?( "CVS/Repository" ) 326 title = File::read( "CVS/Repository" ).chomp 327 end 328 329 # As a last resort, use the name of the project directory 330 if title.nil? 331 distdir = File::dirname( __FILE__ ) 332 distdir = File::dirname( distdir ) if /docs$/ =~ distdir 333 title = File::basename( distdir ) 334 end 475 # If that doesn't work for some reason, use the name of the project. 476 title = extractProjectName() 335 477 336 478 return title 337 479 end 480 338 481 339 482 ### Given a documentation <tt>catalogFile</tt>, extract the name of the file … … 384 527 385 528 if File::exists? catalogFile 386 message"Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile529 verboseMsg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile 387 530 File::foreach( catalogFile ) {|line| 388 531 debugMsg( "Examining line #{line.inspect}..." ) … … 403 546 startlist = [] 404 547 if File.exists? catalogFile 405 message"Using CATALOG file (%s).\n" % catalogFile548 verboseMsg "Using CATALOG file (%s).\n" % catalogFile 406 549 startlist = getVettedManifest( catalogFile ) 407 550 else 408 message"Using default MANIFEST\n"551 verboseMsg "Using default MANIFEST\n" 409 552 startlist = getVettedManifest() 410 553 end 411 554 412 message "Looking for RDoc comments in:\n" if $VERBOSE555 verboseMsg "Looking for RDoc comments in:\n" 413 556 startlist.select {|fn| 414 message " #{fn}: " if $VERBOSE557 verboseMsg " #{fn}: " 415 558 found = false 416 559 File::open( fn, "r" ) {|fh| … … 423 566 } 424 567 425 message( (found ? "yes" : "no") + "\n" ) if $VERBOSE568 verboseMsg( (found ? "yes" : "no") + "\n" ) 426 569 found 427 570 } … … 483 626 ### Try the specified code block, printing the given 484 627 def try( msg, bind=nil ) 485 result = nil 486 message "Trying #{msg}...\n" 487 628 result = '' 629 if msg =~ /^to\s/ 630 message = "Trying #{msg}..." 631 else 632 message = msg 633 end 634 488 635 begin 489 636 rval = nil … … 498 645 result = rval.to_yaml 499 646 else 500 result = ''501 647 PP.pp( rval, result ) 502 648 end 649 503 650 rescue Exception => err 504 nicetrace = err.backtrace.delete_if {|frame| 505 /in `(try|eval)'/ =~ frame 506 }.join("\n\t") 651 if err.backtrace 652 nicetrace = err.backtrace.delete_if {|frame| 653 /in `(try|eval)'/ =~ frame 654 }.join("\n\t") 655 else 656 nicetrace = "Exception had no backtrace" 657 end 658 507 659 result = err.message + "\n\t" + nicetrace 508 660 ensure … … 514 666 end 515 667 end 668 669 670 if __FILE__ == $0 671 # $DEBUG = true 672 include UtilityFunctions 673 674 projname = extractProjectName() 675 header "Project: #{projname}" 676 677 ver = extractVersion() || [0,0,1] 678 puts "Version: %s\n" % ver.join('.') 679 680 if File::directory?( "docs" ) 681 puts "Rdoc:", 682 " Title: " + findRdocTitle(), 683 " Main: " + findRdocMain(), 684 " Upload: " + findRdocUpload(), 685 " SCCS URL: " + findRdocCvsURL() 686 end 687 688 puts "Manifest:", 689 " " + getVettedManifest().join("\n ") 690 end
