Commits

Michael Granger committed b5d49e0

- Merged changes from project-utils.

Comments (0)

Files changed (3)

 #!/usr/bin/ruby
 #
 #	RDoc Documentation Generation Script
-#	$Id: makedocs.rb,v 1.2 2003/09/11 04:51:26 deveiant Exp $
+#	$Id$
 #
-#	Copyright (c) 2001-2003 The FaerieMUD Consortium.
+#	Copyright (c) 2001-2005 The FaerieMUD Consortium.
 #
 #	This is free software. You may use, modify, and/or redistribute this
 #	software under the terms of the Perl Artistic License. (See
 }
 
 # Load modules
-require 'getoptlong'
+require 'optparse'
 require 'rdoc/rdoc'
 require 'utils'
 include UtilityFunctions
 
-def makeDocs( docsdir, template='css2', diagrams=false, upload=nil, ridocs=false )
+def makeDocs( docsdir, template='html', diagrams=false, upload=nil, ridocs=false )
 	debugMsg "docsdir = %p, template = %p, diagrams = %p, upload = %p, ridocs = %p" %
 		[docsdir, template, diagrams, upload, ridocs]
 
 	main = findRdocMain()
 	webcvs = findRdocCvsURL()
 
-	header "Making documentation in #{docsdir}."
-	header "Will upload to '#{upload}'\n" if upload
-	header "Will also create/install 'ri' source" if ridocs
 
 	flags = [
 		'--all',
 	flags += [ '--main', main ] if main
 	flags += [ '--webcvs', webcvs ] if webcvs
 
-	buildDocs( flags, docs )
-	uploadDocs( upload, docsdir ) if upload
-	buildRi( docs ) if ridocs
+	if ridocs
+		header "Will create/install 'ri' source" if ridocs
+		buildRi( docs )
+	else
+		header "Making documentation in #{docsdir}."
+		header "Will upload to '#{upload}'\n" if upload
+		buildDocs( flags, docs )
+		uploadDocs( upload, docsdir ) if upload
+	end
 end
 
 
 end
 
 def buildRi( docs )
-	message "Running 'rdoc #{flags.join(' ')} #{docs.join(' ')}'\n" if $VERBOSE
+	message "Running 'rdoc -R #{docs.join(' ')}'\n" if $VERBOSE
 	unless $DEBUG
 		begin
 			r = RDoc::RDoc.new
-			r.document([ '-i', 'docs', '-f', 'xml', 'lib', 'ext' ])
+			r.document( ['-R'] + docs )
 		rescue RDoc::RDocError => e
 			$stderr.puts e.message
 			exit(1)
 
 
 if $0 == __FILE__
-	opts = GetoptLong.new
-	opts.set_options(
-		[ '--debug',	'-d',	GetoptLong::NO_ARGUMENT ],
-		[ '--verbose',	'-v',	GetoptLong::NO_ARGUMENT ],
-		[ '--upload',	'-u',	GetoptLong::OPTIONAL_ARGUMENT ],
-		[ '--diagrams', '-D',	GetoptLong::NO_ARGUMENT ],
-		[ '--template',	'-T',	GetoptLong::REQUIRED_ARGUMENT ],
-		[ '--output',	'-o',	GetoptLong::REQUIRED_ARGUMENT ]
-		#[ '--ri',		'-r',	GetoptLong::NO_ARGUMENT ],
-	)
-
-	debug = false
-	verbose = false
 	upload = nil
 	diagrams = false
-	template = 'css2'
+	template = 'html'
 	docsdir = "docs/html"
 	rimode = false
 	
-	opts.each {|opt,val|
-		case opt
 
-		when '--debug'
-			debug = true
+	# Read command-line options
+	ARGV.options do |oparser|
+		oparser.banner = "Usage: #$0 [options]\n"
 
-		when '--verbose'
-			verbose = true
+		oparser.separator "RDoc options:"
+		oparser.on( "--diagrams", "-d", TrueClass, "Generate diagrams" ) do
+			diagrams = true
+		end
 
-		when '--upload'
+		oparser.on( "--output=DIR", "-o=DIR", String, "Set the output directory" ) do
+			docsdir = val
+		end
+
+ 		oparser.on( "--ri", "-R", TrueClass, "Generate content for 'ri' instead of HTML" ) do
+ 			rimode = true
+		end
+
+		oparser.separator ""
+		oparser.separator "Post-generation options:"
+
+		oparser.on( "--upload=[URI]", "-u=[URI]", String, "Upload to the given URI" ) do |val|
 			upload = val
 			upload = findRdocUpload() if val.nil? || val.empty?
+		end
 
-		when '--diagrams'
-			diagrams = true
+		oparser.separator ""
+		oparser.separator "Output options:"
 
-		when '--output'
-			docsdir = val
+		oparser.on( "--debug", "-d", TrueClass, "Output debugging information" ) do
+			$VERBOSE = true
+			debugMsg "Turned debugging on."
+		end
 
- 		when '--ri'
- 			rimode = true
+		oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) do
+			$VERBOSE = true
+			debugMsg "Turned verbose on."
+		end
 
+		# Handle the 'help' option
+		oparser.on( "--help", "-h", "Display this text." ) do
+			$stderr.puts oparser
+			exit!(0)
 		end
-	}
 
-	$DEBUG = true if debug
-	$VERBOSE = true if verbose
+		oparser.parse!
+	end
 
 	makeDocs( docsdir, template, diagrams, upload, rimode )
 end
 #!/usr/bin/ruby
 #
 #	Distribution Maker Script
-#	$Id: makedist.rb,v 1.2 2003/07/09 16:15:20 deveiant Exp $
+#	$Id: makedist.rb 6 2004-12-02 07:57:32Z ged $
 #
-#	Copyright (c) 2001, 2002, The FaerieMUD Consortium.
+#	Copyright (c) 2001, 2002, 2004, The FaerieMUD Consortium.
 #
 #	This is free software. You may use, modify, and/or redistribute this
 #	software under the terms of the Perl Artistic License. (See
 #	http://language.perl.com/misc/Artistic.html)
 #
 
-require 'getoptlong'
-require 'ftools'
-require "./utils.rb"
+BEGIN {
+	basedir = File::dirname( File::expand_path(__FILE__) )
+	require "#{basedir}/utils.rb"
+}
 
-include UtilityFunctions
+require 'optparse'
+require 'fileutils'
+require 'rbconfig'
 
+include UtilityFunctions, FileUtils, Config
 
-### Configuration stuff
 
-Options = [
-	[ "--snapshot",	"-s",		GetoptLong::NO_ARGUMENT ],
-	[ "--verbose",  "-v",		GetoptLong::NO_ARGUMENT ],
-]
+# SVN Revision
+SVNRev = %q$Rev: 6 $
 
-### End of configuration
+# SVN Id
+SVNId = %q$Id: makedist.rb 6 2004-12-02 07:57:32Z ged $
 
-
-# Version information
-Version = /([\d\.]+)/.match( %q$Revision: 1.2 $ )[1]
-Rcsid = %q$Id: makedist.rb,v 1.2 2003/07/09 16:15:20 deveiant Exp $
+# SVN URL
+SVNURL = %q$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/project-utils/trunk/makedist.rb $
 
 $Programs = {
 	'tar'	=> nil,
-	'rm'	=> nil,
 	'zip'	=> nil,
 	'cvs'	=> nil,
+	'svn'	=> nil,
 }
 
 Distros = [
 	# Tar+gzipped
 	{
 		'type'		=> 'Tar+Gzipped',
-		'makeProc'	=> Proc.new {|distName|
+		'makeProc'	=> lambda {|distName|
 			gzArchiveName = "%s.tar.gz" % distName
-			if FileTest.exists?( gzArchiveName )
+			if File::exists?( gzArchiveName )
 				message "Removing old archive #{gzArchiveName}..."
 				File.delete( gzArchiveName )
 			end
 	# Tar+bzipped
 	{
 		'type'		=> 'Tar+Bzipped',
-		'makeProc'	=> Proc.new {|distName|
+		'makeProc'	=> lambda {|distName|
 			bzArchiveName = "%s.tar.bz2" % distName
-			if FileTest.exists?( bzArchiveName )
+			if File::exists?( bzArchiveName )
 				message "Removing old archive #{bzArchiveName}..."
 				File.delete( bzArchiveName )
 			end
-			system( $Programs['tar'], '-cjf', bzArchiveName, distName ) or abort( "tar failed: #{$?}" )
+			system( $Programs['tar'], '-cjf', bzArchiveName, distName ) or
+				abort( "tar failed: #{$?}" )
 		}
 	},
 
+	# Zipped
 	{
 		'type'		=> 'Zipped',
-		'makeProc'	=> Proc.new {|distName|
+		'makeProc'	=> lambda {|distName|
 			zipArchiveName = "%s.zip" % distName
-			if FileTest.exists?( zipArchiveName )
+			if File::exists?( zipArchiveName )
 				message "Removing old archive #{zipArchiveName}..."
 				File.delete( zipArchiveName )
 			end
-			system( $Programs['zip'], '-lrq9', zipArchiveName, distName ) or abort( "zip failed: #{$?}" )
+			system( $Programs['zip'], '-lrq9', zipArchiveName, distName ) or
+				abort( "zip failed: #{$?}" )
 		}
 	},
+
+	# Gem
+	{
+		'type'		=> 'RubyGem',
+		'makeProc'	=> lambda {|distName|
+			gemName = "%s.gem" % distName
+			if File::exists?( ".gemspec" )
+				if File::exists?( gemName )
+					message "Removing old gem #{gemName}..."
+					File::delete( gemName )
+				end
+
+				system( CONFIG['RUBY_INSTALL_NAME'], ".gemspec" ) or
+					abort( "Gem create failed: #{$?}" )
+			else
+				message "Skipping Gem: no .gemspec"
+			end
+		}
+	}
 ]
 
 
 # Set interrupt handler to restore tty before exiting
-stty_save = `stty -g`.chomp
-trap("INT") { system "stty", stty_save; exit }
+#stty_save = `stty -g`.chomp
+#trap("INT") { system "stty", stty_save; exit }
 
 ### Main function
 def main
 	filelist = []
 	snapshot = false
+	wantsTag = true
+	wantsPrompt = true
 
 	# Read command-line options
-	opts = GetoptLong::new( *Options )
-	opts.each do |opt, arg|
-		case opt
+	ARGV.options do |oparser|
+		oparser.banner = "Usage: #$0 [options] [VERSION]\n"
 
-		when '--snapshot'
+		oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) do
+			$VERBOSE = true
+			debugMsg "Turned verbose on."
+		end
+
+		oparser.on( "--snapshot", "-s", TrueClass,
+			"Make a snapshot distribution instead of a versioned release" ) do
 			snapshot = true
+			debugMsg "Making snapshot instead of release."
+		end
 
-		when '--verbose'
-			$VERBOSE = true
+		oparser.on( "--no-tag", "-n", TrueClass, "Don't tag the release." ) do
+			wantsTag = false
+		end
 
-		else
-			abort( "No such option '#{opt}'" )
+		oparser.on( "--yes", "-y", TrueClass,
+			"Accept all the defaults instead of prompting." ) do
+			wantsPrompt = false
 		end
-			
+
+		# Handle the 'help' option
+		oparser.on( "--help", "-h", "Display this text." ) do
+			$stderr.puts oparser
+			exit!(0)
+		end
+
+		oparser.parse!
 	end
 
-	project = File::read( "CVS/Repository" ).chomp.sub( %r{.*/}, '' )
-	header "%s Distribution Maker" % project
+	userversion = ARGV.shift
 
+	# Find the project name
+	header "Distribution Maker"
+	project = extractProjectName()
+	if wantsPrompt && project.nil?
+		project = prompt( "Project name?" )
+	end
+	abort( "No project name" ) unless project && !project.empty?
+	message( "Making distribution archives for %s\n" % project )
+
+	# Look for programs to use
 	message "Finding necessary programs...\n\n"
 	for prog in $Programs.keys
 		$Programs[ prog ] = findProgram( prog ) or
 	end
 	message( "All required programs found.\n" )
 
+	# Fetch the MANIFEST
 	filelist = getVettedManifest()
 
+	# Prompt for version/snapshot date
 	version = distName = nil
 	if snapshot
-		version = promptWithDefault( "Snapshot version", Time::now.strftime('%Y%m%d') )
+		verboseMsg( "Making a snapshot distname." )
+
+		if userversion
+			version = userversion
+		else
+			version = Time::now.strftime('%Y%m%d')
+			version = promptWithDefault( "Snapshot version", version ) if wantsPrompt
+		end
+
+		verboseMsg( "Using version %p" % [version] )
 		distName = "%s-%s" % [ project, version ]
+		tag = "SNAPSHOT_%s" % version
 	else
-		releaseVersion = extractNextVersionFromTags( filelist[0] )
-		version = promptWithDefault( "Distribution version", releaseVersion )
+		verboseMsg( "Making a release distname." )
+
+		if userversion
+			version = userversion
+		else
+			version = extractNextVersion().join('.')
+			version = promptWithDefault( "Distribution version", version ) if wantsPrompt
+		end
+
+		verboseMsg( "Using version %p" % [version] )
 		distName = "%s-%s" % [ project, version ]
+		tag = "RELEASE_%s" % version.gsub( /\./, '_' )
+	end
+	verboseMsg( "Distname = %p" % [distName] )
 
-		tag = "RELEASE_%s" % sprintf('%0.2f', version).gsub(/\./, '_') 
+	# Tag if desired
+	if wantsTag
+		verboseMsg( "Tagging." )
+
 		tagFlag = promptWithDefault( "Tag '%s' with %s" % [ project, tag ], 'y' )
-
-		if tagFlag =~ /^y/i
-			$stderr.puts "Running #{$Programs['cvs']} -q tag #{tag}"
-			system $Programs['cvs'], '-q', 'tag', tag
+		if /^y/i.match( tagFlag )
+			if File::directory?( "CVS" )
+				message "Running #{$Programs['cvs']} -q tag #{tag}\n"
+				system $Programs['cvs'], '-q', 'tag', tag
+			elsif File::directory?( ".svn" )
+				uri = getSvnUri()
+				taguri = uri + "tags/#{tag}"
+				message "SVN tag URI: %s\n" % [ taguri ]
+				system( $Programs['svn'], 'cp', uri.to_s, taguri.to_s )
+			else
+				errorMessage "No supported version control system. Skipping tag."
+			end
 		end
 	end
 
-	message "Making distribution directory #{distName}..."
+	# Make the distdir
+	message "Making distribution directory #{distName}...\n"
 	Dir.mkdir( distName ) unless FileTest.directory?( distName )
 	for file in filelist
 		File.makedirs( File.dirname(File.join(distName,file)) )
 		File.link( file, File.join(distName,file) )
 	end
 
+	# Make an archive file for each known kind
 	for distro in Distros
 		message "Making #{distro['type']} distribution..."
 		distro['makeProc'].call( distName )
 		message "done.\n"
 	end
 
-	if $Programs['rm']
-		message "removing dist build directory..."
-		system( $Programs['rm'], '-rf', distName )
-		message "done.\n\n"
-	else
-		message "Cannot clean dist build directory: no 'rm' program was found."
-	end
+	# Remove the distdir
+	message "removing dist build directory..."
+	rm_rf distName, :verbose => $VERBOSE
+	message "done.\n\n"
 end
 
 main	
 #
 #	Install/distribution utility functions
-#	$Id: utils.rb,v 1.3 2003/09/11 04:50:13 deveiant Exp $
+#	$Id: utils.rb 8 2005-07-13 12:35:15Z ged $
 #
-#	Copyright (c) 2001-2003, The FaerieMUD Consortium.
+#	Copyright (c) 2001-2005, The FaerieMUD Consortium.
 #
 #	This is free software. You may use, modify, and/or redistribute this
 #	software under the terms of the Perl Artistic License. (See
 #	http://language.perl.com/misc/Artistic.html)
 #
 
+BEGIN {
+	require 'rbconfig'
+	require 'uri'
+	require 'find'
+	require 'pp'
 
-BEGIN {
 	begin
 		require 'readline'
 		include Readline
 		require 'yaml'
 		$yaml = true
 	rescue LoadError => e
-		$stderr.puts "No YAML; try() will use .inspect instead."
+		$stderr.puts "No YAML; try() will use PrettyPrint instead."
 		$yaml = false
 	end
 }
 
+
 module UtilityFunctions
+	include Config
 
 	# The list of regexen that eliminate files from the MANIFEST
 	ANTIMANIFEST = [
 		/^#/,
 		%r{docs/html},
 		%r{docs/man},
-		/^TEMPLATE/,
+		/\bTEMPLATE\.\w+\.tpl\b/,
 		/\.cvsignore/,
-		/\.s?o$/
+		/\.s?o$/,
 	]
 
 	# Set some ANSI escape code constants (Shamelessly stolen from Perl's
 
 	ErasePreviousLine = "\033[A\033[K"
 
+	ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' )
+		#
+		# Distribution Manifest
+		# Created: #{Time::now.to_s}
+		# 
+
+	EOF
 
 	###############
 	module_function
 
 	# Create a string that contains the ANSI codes specified and return it
 	def ansiCode( *attributes )
-		return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM']
+		return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
 		attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
 		if attr.empty? 
 			return ''
 	# Test for the presence of the specified <tt>library</tt>, and output a
 	# message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
 	# is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
-	def testForLibrary( library, nicename=nil )
+	def testForLibrary( library, nicename=nil, progress=false )
 		nicename ||= library
-		message( "Testing for the #{nicename} library..." )
-		if $:.detect {|dir| File.exists?(File.join(dir,"#{library}.rb")) || File.exists?(File.join(dir,"#{library}.so"))}
-			message( "found.\n" )
+		message( "Testing for the #{nicename} library..." ) if progress
+		if $LOAD_PATH.detect {|dir|
+				File.exists?(File.join(dir,"#{library}.rb")) ||
+				File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}"))
+			}
+			message( "found.\n" ) if progress
 			return true
 		else
-			message( "not found.\n" )
+			message( "not found.\n" ) if progress
 			return false
 		end
 	end
 	end
 
 	### Output <tt>msg</tt> to STDERR and flush it.
-	def message( msg )
-		$stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' )
+	def message( *msgs )
+		$stderr.print( msgs.join("\n") )
 		$stderr.flush
 	end
 
+	### Output +msg+ to STDERR and flush it if $VERBOSE is true.
+	def verboseMsg( msg )
+		msg.chomp!
+		message( msg + "\n" ) if $VERBOSE
+	end
+
 	### Output the specified <tt>msg</tt> as an ANSI-colored error message
 	### (white on red).
 	def errorMessage( msg )
 	### Erase the previous line (if supported by your terminal) and output the
 	### specified <tt>msg</tt> instead.
 	def replaceMessage( msg )
-		print ErasePreviousLine
+		$stderr.print ErasePreviousLine
 		message( msg )
 	end
 
 	### Output a divider made up of <tt>length</tt> hyphen characters.
 	def divider( length=75 )
-		puts "\r" + ("-" * length )
+		$stderr.puts "\r" + ("-" * length )
 	end
 	alias :writeLine :divider
 
+
 	### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
 	### status of 1.
 	def abort( msg )
 		Kernel.exit!( 1 )
 	end
 
+
 	### Output the specified <tt>promptString</tt> as a prompt (in green) and
-	### return the user's input with leading and trailing spaces removed.
-	def prompt( promptString )
+	### return the user's input with leading and trailing spaces removed.  If a
+	### test is provided, the prompt will repeat until the test returns true.
+	### An optional failure message can also be passed in.
+	def prompt( promptString, failure_msg="Try again." ) # :yields: response
 		promptString.chomp!
-		return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip
+		response = nil
+
+		begin
+			response = readline( ansiCode('bold', 'green') +
+				"#{promptString}: " + ansiCode('reset') ).strip
+			if block_given? && ! yield( response ) 
+				errorMessage( failure_msg + "\n\n" )
+				response = nil
+			end
+		end until response
+
+		return response
 	end
 
+
 	### Prompt the user with the given <tt>promptString</tt> via #prompt,
 	### substituting the given <tt>default</tt> if the user doesn't input
-	### anything.
-	def promptWithDefault( promptString, default )
-		response = prompt( "%s [%s]" % [ promptString, default ] )
-		if response.empty?
-			return default
-		else
-			return response
-		end
+	### anything.  If a test is provided, the prompt will repeat until the test
+	### returns true.  An optional failure message can also be passed in.
+	def promptWithDefault( promptString, default, failure_msg="Try again." )
+		response = nil
+		
+		begin
+			response = prompt( "%s [%s]" % [ promptString, default ] )
+			response = default if response.empty?
+
+			if block_given? && ! yield( response ) 
+				errorMessage( failure_msg + "\n\n" )
+				response = nil
+			end
+		end until response
+
+		return response
 	end
 
+
+	$programs = {}
+
 	### Search for the program specified by the given <tt>progname</tt> in the
 	### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
 	### no such program is in the path.
 	def findProgram( progname )
-		ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
-			file = File.join( d, progname )
-			return file if File.executable?( file )
-		}
-		return nil
+		unless $programs.key?( progname )
+			ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
+				file = File.join( d, progname )
+				if File.executable?( file )
+					$programs[ progname ] = file 
+					break
+				end
+			}
+		end
+
+		return $programs[ progname ]
 	end
 
-	### Using the CVS log for the given <tt>file</tt> attempt to guess what the
-	### next release version might be. This only works if releases are tagged
-	### with tags like 'RELEASE_x_y'.
-	def extractNextVersionFromTags( file )
-		message "Attempting to extract next release version from CVS tags for #{file}...\n"
-		raise RuntimeError, "No such file '#{file}'" unless File.exists?( file )
-		cvsPath = findProgram( 'cvs' ) or
-			raise RuntimeError, "Cannot find the 'cvs' program. Aborting."
 
-		output = %x{#{cvsPath} log #{file}}
-		release = [ 0, 0 ]
-		output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match|
-			if $1.to_i > release[0] || $2.to_i > release[1]
-				release = [ $1.to_i, $2.to_i ]
-				replaceMessage( "Found %d.%02d...\n" % release )
+	### Search for the release version for the project in the specified
+	### +directory+.
+	def extractVersion( directory='.' )
+		release = nil
+
+		Dir::chdir( directory ) do
+			if File::directory?( "CVS" )
+				verboseMsg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." )
+
+				if (( cvs = findProgram('cvs') ))
+					revs = []
+					output = %x{cvs log}
+					output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match|
+						rev = $1.split(/_/).collect {|s| Integer(s) rescue 0}
+						verboseMsg( "Found %s...\n" % rev.join('.') )
+						revs << rev
+					}
+
+					release = revs.sort.last
+				end
+
+			elsif File::directory?( '.svn' )
+				verboseMsg( "Project is versioned via Subversion" )
+
+				if (( svn = findProgram('svn') ))
+					output = %x{svn pg project-version}.chomp
+					unless output.empty?
+						verboseMsg( "Using 'project-version' property: %p" % output )
+						release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0}
+					end
+				end
 			end
-		}
-
-		if release[1] >= 99
-			release[0] += 1
-			release[1] = 1
-		else
-			release[1] += 1
 		end
 
-		return "%d.%02d" % release
+		return release
 	end
 
-	### Extract the project name (CVS Repository name) for the given directory.
-	def extractProjectName
-		File.open( "CVS/Repository", "r").readline.chomp
+
+	### Find the current release version for the project in the specified
+	### +directory+ and return its successor.
+	def extractNextVersion( directory='.' )
+		version = extractVersion( directory ) || [0,0,0]
+		version.compact!
+		version[-1] += 1
+
+		return version
 	end
 
+
+	# Pattern for extracting the name of the project from a Subversion URL
+	SVNUrlPath = %r{
+		.*/						# Skip all but the last bit
+		(\w+)					# $1 = project name
+		/						# Followed by / +
+		(?:
+			trunk |				# 'trunk'
+			(
+				branches |		# ...or branches/branch-name
+				tags			# ...or tags/tag-name
+			)/\w	
+		)
+		$						# bound to the end
+	}ix
+
+	### Extract the project name (CVS Repository name) for the given +directory+.
+	def extractProjectName( directory='.' )
+		name = nil
+
+		Dir::chdir( directory ) do
+
+			# CVS-controlled
+			if File::directory?( "CVS" )
+				verboseMsg( "Project is versioned via CVS. Using repository name." )
+				name = File.open( "CVS/Repository", "r").readline.chomp
+				name.sub!( %r{.*/}, '' )
+
+			# Subversion-controlled
+			elsif File::directory?( '.svn' )
+				verboseMsg( "Project is versioned via Subversion" )
+
+				# If the machine has the svn tool, try to get the project name
+				if (( svn = findProgram( 'svn' ) ))
+
+					# First try an explicit property
+					output = shellCommand( svn, 'pg', 'project-name' )
+					if !output.empty?
+						verboseMsg( "Using 'project-name' property: %p" % output )
+						name = output.first.chomp
+
+					# If that doesn't work, try to figure it out from the URL
+					elsif (( uri = getSvnUri() ))
+						name = uri.path.sub( SVNUrlPath ) { $1 }
+					end
+				end
+			end
+
+			# Fall back to guessing based on the directory name
+			unless name
+				name = File::basename(File::dirname( File::expand_path(__FILE__) ))
+			end
+		end
+
+		return name
+	end
+
+
+	### Extract the Subversion URL from the specified directory and return it as
+	### a URI object.
+	def getSvnUri( directory='.' )
+		uri = nil
+
+		Dir::chdir( directory ) do
+			output = %x{svn info}
+			debugMsg( "Using info: %p" % output )
+
+			if /^URL: \s* ( .* )/xi.match( output )
+				uri = URI::parse( $1 )
+			end
+		end
+
+		return uri
+	end
+
+
+	### (Re)make a manifest file in the specified +path+.
+	def makeManifest( path="MANIFEST" )
+		if File::exists?( path )
+			reply = promptWithDefault( "Replace current '#{path}'? [yN]", "n" )
+			return false unless /^y/i.match( reply )
+
+			verboseMsg "Replacing manifest at '#{path}'"
+		else
+			verboseMsg "Creating new manifest at '#{path}'"
+		end
+
+		files = []
+		verboseMsg( "Finding files...\n" )
+		Find::find( Dir::pwd ) do |f|
+			Find::prune if File::directory?( f ) &&
+				/^\./.match( File::basename(f) )
+			verboseMsg( "  found: #{f}\n" )
+			files << f.sub( %r{^#{Dir::pwd}/?}, '' )
+		end
+		files = vetManifest( files )
+		
+		verboseMsg( "Writing new manifest to #{path}..." )
+		File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
+			ofh.puts( ManifestHeader )
+			ofh.puts( files )
+		end
+		verboseMsg( "done." )
+	end
+
+
 	### Read the specified <tt>manifestFile</tt>, which is a text file
 	### describing which files to package up for a distribution. The manifest
 	### should consist of one or more lines, each containing one filename or
 	### shell glob pattern.
 	def readManifest( manifestFile="MANIFEST" )
-		message "Building manifest..."
+		verboseMsg "Building manifest..."
 		raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
 
 		manifest = IO::readlines( manifestFile ).collect {|line|
 
 		filelist = []
 		for pat in manifest
-			$stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE
+			verboseMsg "Adding files that match '#{pat}' to the file list"
 			filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
 		end
 
-		message "found #{filelist.length} files.\n"
+		verboseMsg "found #{filelist.length} files.\n"
 		return filelist
 	end
 
+
 	### Given a <tt>filelist</tt> like that returned by #readManifest, remove
 	### the entries therein which match the Regexp objects in the given
 	### <tt>antimanifest</tt> and return the resultant Array.
-	def vetManifest( filelist, antimanifest=ANITMANIFEST )
+	def vetManifest( filelist, antimanifest=ANTIMANIFEST )
 		origLength = filelist.length
-		message "Vetting manifest..."
+		verboseMsg "Vetting manifest..."
 
 		for regex in antimanifest
-			if $VERBOSE
-				message "\n\tPattern /#{regex.source}/ removed: " +
-					filelist.find_all {|file| regex.match(file)}.join(', ')
-			end
+			verboseMsg "\n\tPattern /#{regex.source}/ removed: " +
+				filelist.find_all {|file| regex.match(file)}.join(', ')
 			filelist.delete_if {|file| regex.match(file)}
 		end
 
-		message "removed #{origLength - filelist.length} files from the list.\n"
+		verboseMsg "removed #{origLength - filelist.length} files from the list.\n"
 		return filelist
 	end
 
+
 	### Combine a call to #readManifest with one to #vetManifest.
 	def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
 		vetManifest( readManifest(manifestFile), antimanifest )
 	end
 
+
 	### Given a documentation <tt>catalogFile</tt>, extract the title, if
 	### available, and return it. Otherwise generate a title from the name of
 	### the CVS module.
 		# Title: Foo Bar Module
 		title = findCatalogKeyword( 'title', catalogFile )
 
-		# If that doesn't work for some reason, try grabbing the name of the CVS
-		# repository the directory belongs to.
-		if title.nil? && File::directory?( "CVS" ) &&
-				File::exists?( "CVS/Repository" )
-			title = File::read( "CVS/Repository" ).chomp
-		end
-
-		# As a last resort, use the name of the project directory
-		if title.nil?
-			distdir = File::dirname( __FILE__ )
-			distdir = File::dirname( distdir ) if /docs$/ =~ distdir
-			title = File::basename( distdir )
-		end
+		# If that doesn't work for some reason, use the name of the project.
+		title = extractProjectName()
 
 		return title
 	end
 
+
 	### Given a documentation <tt>catalogFile</tt>, extract the name of the file
 	### to use as the initally displayed page. If extraction fails, the
 	### +default+ will be used if it exists. Returns +nil+ if there is no main
 		val = nil
 
 		if File::exists? catalogFile
-			message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
+			verboseMsg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
 			File::foreach( catalogFile ) {|line|
 				debugMsg( "Examining line #{line.inspect}..." )
 				val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line
 	def findRdocableFiles( catalogFile="docs/CATALOG" )
 		startlist = []
 		if File.exists? catalogFile
-			message "Using CATALOG file (%s).\n" % catalogFile
+			verboseMsg "Using CATALOG file (%s).\n" % catalogFile
 			startlist = getVettedManifest( catalogFile )
 		else
-			message "Using default MANIFEST\n"
+			verboseMsg "Using default MANIFEST\n"
 			startlist = getVettedManifest()
 		end
 
-		message "Looking for RDoc comments in:\n" if $VERBOSE
+		verboseMsg "Looking for RDoc comments in:\n"
 		startlist.select {|fn|
-			message "  #{fn}: " if $VERBOSE
+			verboseMsg "  #{fn}: "
 			found = false
 			File::open( fn, "r" ) {|fh|
 				fh.each {|line|
 				}
 			}
 
-			message( (found ? "yes" : "no") + "\n" ) if $VERBOSE
+			verboseMsg( (found ? "yes" : "no") + "\n" )
 			found
 		}
 	end
 	### <tt>line</tt> at a time. The return value of the block is used as the
 	### new line, or omitted if the block returns <tt>nil</tt> or
 	### <tt>false</tt>.
-	def editInPlace( file ) # :yields: line
+	def editInPlace( file, testMode=false ) # :yields: line
 		raise "No block specified for editing operation" unless block_given?
 
 		tempName = "#{file}.#{$$}"
 		File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
-			File::unlink( tempName )
 			File::open( file, File::RDONLY ) {|fh|
 				fh.each {|line|
 					newline = yield( line ) or next
 					tempfile.print( newline )
+					$deferr.puts "%p -> %p" % [ line, newline ] if
+						line != newline
 				}
 			}
+		}
 
-			tempfile.seek(0)
-
-			File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile|
-				newfile.print( tempfile.read )
-			}
-		}
+		if testMode
+			File::unlink( tempName )
+		else
+			File::rename( tempName, file )
+		end
 	end
 
 	### Execute the specified shell <tt>command</tt>, read the results, and
 
 	### Try the specified code block, printing the given 
 	def try( msg, bind=nil )
-		result = nil
-		message "Trying #{msg}..."
-
+		result = ''
+		if msg =~ /^to\s/
+			message = "Trying #{msg}..."
+		else
+			message = msg
+		end
+			
 		begin
 			rval = nil
 			if block_given?
 			if $yaml
 				result = rval.to_yaml
 			else
-				result = rval.inspect
+				PP.pp( rval, result )
 			end
+
 		rescue Exception => err
-			nicetrace = err.backtrace.delete_if {|frame|
-				/in `(try|eval)'/ =~ frame
-			}.join("\n\t")
+			if err.backtrace
+				nicetrace = err.backtrace.delete_if {|frame|
+					/in `(try|eval)'/ =~ frame
+				}.join("\n\t")
+			else
+				nicetrace = "Exception had no backtrace"
+			end
+
 			result = err.message + "\n\t" + nicetrace
 		ensure
-			puts result
+			divider
+			message result + "\n"
+			divider
+			$deferr.puts
 		end
 	end
 end
+
+
+if __FILE__ == $0
+	# $DEBUG = true
+	include UtilityFunctions
+
+	projname = extractProjectName()
+	header "Project: #{projname}"
+
+	ver = extractVersion() || [0,0,1]
+	puts "Version: %s\n" % ver.join('.')
+
+	if File::directory?( "docs" )
+		puts "Rdoc:",
+			"  Title: " + findRdocTitle(),
+			"  Main: " + findRdocMain(),
+			"  Upload: " + findRdocUpload(),
+			"  SCCS URL: " + findRdocCvsURL()
+	end
+
+	puts "Manifest:",
+		"  " + getVettedManifest().join("\n  ")
+end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.