diff -r 1b096869b568 -r 1d3cfd4837a8 rake/helpers.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rake/helpers.rb Wed May 07 18:22:04 2008 +0000 @@ -0,0 +1,353 @@ +##################################################################### +### G L O B A L H E L P E R F U N C T I O N S +##################################################################### + +require 'pathname' +require 'readline' +require 'open3' + +# Set some ANSI escape code constants (Shamelessly stolen from Perl's +# Term::ANSIColor by Russ Allbery and Zenin +ANSI_ATTRIBUTES = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 +} + + +CLEAR_TO_EOL = "\e[K" +CLEAR_CURRENT_LINE = "\e[2K" + + +### Output a logging message +def log( *msg ) + output = colorize( msg.flatten.join(' '), 'cyan' ) + $deferr.puts( output ) +end + + +### Output a logging message if tracing is on +def trace( *msg ) + return unless $trace + output = colorize( msg.flatten.join(' '), 'yellow' ) + $deferr.puts( output ) +end + + +### Run the specified command +cmd+ with system(), failing if the execution +### fails. +def run( *cmd ) + cmd.flatten! + + log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} ) + if $dryrun + $deferr.puts "(dry run mode)" + else + system( *cmd ) + unless $?.success? + fail "Command failed: [%s]" % [cmd.join(' ')] + end + end +end + + +### Open a pipe to a process running the given +cmd+ and call the given block with it. +def pipeto( *cmd ) + $DEBUG = true + + cmd.flatten! + log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} ) + if $dryrun + $deferr.puts "(dry run mode)" + else + open( '|-', 'w+' ) do |io| + + # Parent + if io + yield( io ) + + # Child + else + exec( *cmd ) + fail "Command failed: [%s]" % [cmd.join(' ')] + end + end + end +end + + +### Download the file at +sourceuri+ via HTTP and write it to +targetfile+. +def download( sourceuri, targetfile ) + oldsync = $defout.sync + $defout.sync = true + require 'net/http' + require 'uri' + + targetpath = Pathname.new( targetfile ) + + log "Downloading %s to %s" % [sourceuri, targetfile] + targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh| + + url = URI.parse( sourceuri ) + downloaded = false + limit = 5 + + until downloaded or limit.zero? + Net::HTTP.start( url.host, url.port ) do |http| + req = Net::HTTP::Get.new( url.path ) + + http.request( req ) do |res| + if res.is_a?( Net::HTTPSuccess ) + log "Downloading..." + res.read_body do |buf| + ofh.print( buf ) + end + downloaded = true + puts "done." + + elsif res.is_a?( Net::HTTPRedirection ) + url = URI.parse( res['location'] ) + log "...following redirection to: %s" % [ url ] + limit -= 1 + sleep 0.2 + next + + else + res.error! + end + end + end + end + end + + return targetpath +ensure + $defout.sync = oldsync +end + + +### Return the fully-qualified path to the specified +program+ in the PATH. +def which( program ) + ENV['PATH'].split(/:/). + collect {|dir| Pathname.new(dir) + program }. + find {|path| path.exist? && path.executable? } +end + + +### Create a string that contains the ANSI codes specified and return it +def ansi_code( *attributes ) + attributes.flatten! + attributes.collect! {|at| at.to_s } + # $deferr.puts "Returning ansicode for TERM = %p: %p" % + # [ ENV['TERM'], attributes ] + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM'] + attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';') + + # $deferr.puts " attr is: %p" % [attributes] + if attributes.empty? + return '' + else + return "\e[%sm" % attributes + end +end + + +### Colorize the given +string+ with the specified +attributes+ and return it, handling +### line-endings, color reset, etc. +def colorize( *args ) + string = '' + + if block_given? + string = yield + else + string = args.shift + end + + ending = string[/(\s)$/] || '' + string = string.rstrip + + return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending +end + + +### Output the specified msg as an ANSI-colored error message +### (white on red). +def error_message( msg, details='' ) + $deferr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details +end +alias :error :error_message + + +### Highlight and embed a prompt control character in the given +string+ and return it. +def make_prompt_string( string ) + return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' } +end + + +### Output the specified prompt_string as a prompt (in green) and +### 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( prompt_string, failure_msg="Try again." ) # :yields: response + prompt_string.chomp! + prompt_string << ":" unless /\W$/.match( prompt_string ) + response = nil + + begin + prompt = make_prompt_string( prompt_string ) + response = Readline.readline( prompt ) || '' + response.strip! + if block_given? && ! yield( response ) + error_message( failure_msg + "\n\n" ) + response = nil + end + end while response.nil? + + return response +end + + +### Prompt the user with the given prompt_string via #prompt, +### substituting the given default if the user doesn't input +### 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 prompt_with_default( prompt_string, default, failure_msg="Try again." ) + response = nil + + begin + response = prompt( "%s [%s]" % [ prompt_string, default ] ) + response = default if response.empty? + + if block_given? && ! yield( response ) + error_message( failure_msg + "\n\n" ) + response = nil + end + end while response.nil? + + return response +end + + +### Display a description of a potentially-dangerous task, and prompt +### for confirmation. If the user answers with anything that begins +### with 'y', yield to the block, else raise with an error. +def ask_for_confirmation( description ) + puts description + + answer = prompt_with_default( "Continue?", 'n' ) do |input| + input =~ /^[yn]/i + end + + case answer + when /^y/i + yield + else + error "Aborted." + fail + end +end + + +### Search line-by-line in the specified +file+ for the given +regexp+, returning the +### first match, or nil if no match was found. If the +regexp+ has any capture groups, +### those will be returned in an Array, else the whole matching line is returned. +def find_pattern_in_file( regexp, file ) + rval = nil + + File.open( file, 'r' ).each do |line| + if (( match = regexp.match(line) )) + rval = match.captures.empty? ? match[0] : match.captures + break + end + end + + return rval +end + + +### Search line-by-line in the output of the specified +cmd+ for the given +regexp+, +### returning the first match, or nil if no match was found. If the +regexp+ has any +### capture groups, those will be returned in an Array, else the whole matching line +### is returned. +def find_pattern_in_pipe( regexp, *cmd ) + output = [] + + Open3.popen3( *cmd ) do |stdin, stdout, stderr| + stdin.close + + output << stdout.gets until stdout.eof? + output << stderr.gets until stderr.eof? + end + + result = output.find { |line| regexp.match(line) } + return $1 || result +end + + +### Extract all the non Rake-target arguments from ARGV and return them. +def get_target_args + args = ARGV.reject {|arg| Rake::Task.task_defined?(arg) } + return args +end + + + +require 'rubygems/dependency_installer' +require 'rubygems/source_index' +require 'rubygems/requirement' +require 'rubygems/doc_manager' + +### Install the specified +gems+ if they aren't already installed. +def install_gems( *gems ) + gems.flatten! + + defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({ + :generate_rdoc => true, + :generate_ri => true, + :install_dir => Gem.dir, + :format_executable => false, + :test => false, + :version => Gem::Requirement.default, + }) + + # Check for root + if Process.euid != 0 + $stderr.puts "This probably won't work, as you aren't root, but I'll try anyway" + end + + gemindex = Gem::SourceIndex.from_installed_gems + + gems.each do |gemname| + if (( specs = gemindex.search(gemname) )) && ! specs.empty? + log "Version %s of %s is already installed; skipping..." % + [ specs.first.version, specs.first.name ] + next + end + + log "Trying to install #{gemname.inspect}..." + installer = Gem::DependencyInstaller.new + installer.install( gemname ) + + installer.installed_gems.each do |spec| + log "Installed: %s" % [ spec.full_name ] + end + + end +end + +