Module: UtilityFunctions

Defined in:
lib/carat-dev/misc/utils.rb

Overview

BEGIN

begin
    require 'readline'
    include Readline
rescue LoadError => e
    $stderr.puts "Faking readline..."
    def readline( prompt )
        $stderr.print prompt.chomp
        return $stdin.gets.chomp
    end
end

begin
    require 'yaml'
    $yaml = true
rescue LoadError => e
    $stderr.puts "No YAML; try() will use .inspect instead."
    $yaml = false
end

Defined Under Namespace

Modules: RubyLoader

Constant Summary collapse

ANTIMANIFEST =

The list of regexen that eliminate files from the MANIFEST

[
    /makedist\.rb/,
    /\bCVS\b/,
    /~$/,
    /^#/,
    %r{docs/html},
    %r{docs/man},
    /^TEMPLATE/,
    /\.cvsignore/,
    /\.s?o$/
]
AnsiAttributes =

Set some ANSI escape code constants (Shamelessly stolen from Perl’s Term::ANSIColor by Russ Allbery <[email protected]> and Zenin <[email protected]>

{
    '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
}
ErasePreviousLine =
"\033[A\033[K"

Class Method Summary collapse

Class Method Details

.abort(msg) ⇒ Object

Output the specified msg colored in ANSI red and exit with a status of 1.



193
194
195
196
# File 'lib/carat-dev/misc/utils.rb', line 193

def abort( msg )
  print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
  Kernel.exit!( 1 )
end

.ansiCode(*attributes) ⇒ Object

Create a string that contains the ANSI codes specified and return it



100
101
102
103
104
105
106
107
108
# File 'lib/carat-dev/misc/utils.rb', line 100

def ansiCode( *attributes )
  return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM']
  attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
  if attr.empty? 
    return ''
  else
    return "\e[%sm" % attr
  end
end

.debugMsg(msg) ⇒ Object

Output the specified msg as an ANSI-colored debugging message (yellow on blue).



171
172
173
174
175
176
# File 'lib/carat-dev/misc/utils.rb', line 171

def debugMsg( msg )
  return unless $DEBUG
  msg.chomp!
  $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
  $stderr.flush
end

.divider(length = 75) ⇒ Object Also known as: writeLine

Output a divider made up of length hyphen characters.



186
187
188
# File 'lib/carat-dev/misc/utils.rb', line 186

def divider( length=75 )
  puts "\r" + ("-" * length )
end

.editInPlace(file) ⇒ Object

Open a file and filter each of its lines through the given block a line at a time. The return value of the block is used as the new line, or omitted if the block returns nil or false.



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/carat-dev/misc/utils.rb', line 346

def editInPlace( file ) # :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 )
            }
        }

        tempfile.seek(0)

        File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile|
            newfile.print( tempfile.read )
        }
    }
end

.ensure_version(ver) ⇒ Object

Ensures that the version number of the installed ruby is greater than or equal to the version string supplied. Credit to Robert Klemme for posting this on ruby-talk.

ensure_version "1.7.0"
ensure_version "1.8.0"
ensure_version "1.8.1"


452
453
454
455
456
# File 'lib/carat-dev/misc/utils.rb', line 452

def ensure_version( ver )
  if ( RUBY_VERSION.split(/\./).map{|x|x.to_i} <=> ver.split(/\./).map{|x|x.to_i} ) < 0
    throw "Version error: should be #{ver} but is #{RUBY_VERSION}"
  end
end

.errorMessage(msg) ⇒ Object

Output the specified msg as an ANSI-colored error message (white on red).



165
166
167
# File 'lib/carat-dev/misc/utils.rb', line 165

def errorMessage( msg )
  message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
end

.extractNextVersionFromTags(file) ⇒ Object

Using the CVS log for the given file attempt to guess what the next release version might be. This only works if releases are tagged with tags like ‘RELEASE_x_y’.

Raises:

  • (RuntimeError)


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/carat-dev/misc/utils.rb', line 231

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 )
        end
    }

    if release[1] >= 99
        release[0] += 1
        release[1] = 1
    else
        release[1] += 1
    end

    return "%d.%02d" % release
end

.extractProjectNameObject

Extract the project name (CVS Repository name) for the given directory.



257
258
259
# File 'lib/carat-dev/misc/utils.rb', line 257

def extractProjectName
    File.open( "CVS/Repository", "r").readline.chomp
end

.findProgram(progname) ⇒ Object

Search for the program specified by the given progname in the user’s PATH, and return the full path to it, or nil if no such program is in the path.



220
221
222
223
224
225
226
# File 'lib/carat-dev/misc/utils.rb', line 220

def findProgram( progname )
  ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
    file = File.join( d, progname )
    return file if File.executable?( file )
  }
  return nil
end

.findRdocableFiles(catalogFile = "docs/CATALOG") ⇒ Object

Given a documentation catalogFile, which is in the same format as that described by #readManifest, read and expand it, and then return a list of those files which appear to have RDoc documentation in them. If catalogFile is nil or does not exist, the MANIFEST file is used instead.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/carat-dev/misc/utils.rb', line 314

def findRdocableFiles( catalogFile="docs/CATALOG" )
    startlist = []
    if File.exists? catalogFile
        message "Using CATALOG file (%s).\n" % catalogFile
        startlist = getVettedManifest( catalogFile )
    else
        message "Using default MANIFEST\n"
        startlist = getVettedManifest()
    end

    message "Looking for RDoc comments in:\n" if $VERBOSE
    startlist.select {|fn|
        message "  #{fn}: " if $VERBOSE
        found = false
        File::open( fn, "r" ) {|fh|
            fh.each {|line|
                if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
                    found = true
                    break
                end
            }
        }

        message( (found ? "yes" : "no") + "\n" ) if $VERBOSE
        found
    }
end

.getVettedManifest(manifestFile = "MANIFEST", antimanifest = ANTIMANIFEST) ⇒ Object

Combine a call to #readManifest with one to #vetManifest.



305
306
307
# File 'lib/carat-dev/misc/utils.rb', line 305

def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
    vetManifest( readManifest(manifestFile), antimanifest )
end

.header(msg) ⇒ Object

Output msg as a ANSI-colored program/section header (white on blue).



151
152
153
154
155
# File 'lib/carat-dev/misc/utils.rb', line 151

def header( msg )
  msg.chomp!
  $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
  $stderr.flush
end

.message(msg) ⇒ Object

Output msg to STDERR and flush it.



158
159
160
161
# File 'lib/carat-dev/misc/utils.rb', line 158

def message( msg )
  $stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' )
  $stderr.flush
end

.prompt(promptString) ⇒ Object

Output the specified promptString as a prompt (in green) and return the user’s input with leading and trailing spaces removed.



200
201
202
203
# File 'lib/carat-dev/misc/utils.rb', line 200

def prompt( promptString )
  promptString.chomp!
  return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip
end

.promptWithDefault(promptString, default) ⇒ Object

Prompt the user with the given promptString via #prompt, substituting the given default if the user doesn’t input anything.



208
209
210
211
212
213
214
215
# File 'lib/carat-dev/misc/utils.rb', line 208

def promptWithDefault( promptString, default )
  response = prompt( "%s [%s]" % [ promptString, default ] )
  if response.empty?
    return default
  else
    return response
  end
end

.readManifest(manifestFile = "MANIFEST") ⇒ Object

Read the specified manifestFile, 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.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/carat-dev/misc/utils.rb', line 265

def readManifest( manifestFile="MANIFEST" )
    message "Building manifest..."
    raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile

    manifest = IO::readlines( manifestFile ).collect{ |line|
      line.chomp
    }.select{ |line|
      line !~ /^(\s*(#.*)?)?$/
    }

    filelist = []
    for pat in manifest
        $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE
        filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
    end

    message "found #{filelist.length} files.\n"
    return filelist
end

.replaceMessage(msg) ⇒ Object

Erase the previous line (if supported by your terminal) and output the specified msg instead.



180
181
182
183
# File 'lib/carat-dev/misc/utils.rb', line 180

def replaceMessage( msg )
  print ErasePreviousLine
  message( msg )
end

.shellCommand(*command) ⇒ Object

Execute the specified shell command, read the results, and return them. Like a %x{} that returns an Array instead of a String.



369
370
371
372
373
374
# File 'lib/carat-dev/misc/utils.rb', line 369

def shellCommand( *command )
    raise "Empty command" if command.empty?

    cmdpipe = IO::popen( command.join(' '), 'r' )
    return cmdpipe.readlines
end

.terse_block(&block) ⇒ Object

Run a block, with $VERBOSE set to false. No other threads will run while this block is being executed.



440
441
442
# File 'lib/carat-dev/misc/utils.rb', line 440

def terse_block(&block)
  verbose_block(false, &block)
end

.testForLibrary(library, nicename = nil) ⇒ Object

Test for the presence of the specified library, and output a message describing the test using nicename. If nicename is nil, the value in library is used to build a default.



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/carat-dev/misc/utils.rb', line 113

def testForLibrary( library, nicename=nil )
  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" )
    return true
  else
    message( "not found.\n" )
    return false
  end
end

.testForRequiredLibrary(library, nicename = nil, raaUrl = nil, downloadUrl = nil, fatal = true) ⇒ Object

Test for the presence of the specified library, and output a message describing the problem using nicename. If nicename is nil, the value in library is used to build a default. If raaUrl and/or downloadUrl are specified, they are also use to build a message describing how to find the required library. If fatal is true, a missing library will cause the program to abort.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/carat-dev/misc/utils.rb', line 134

def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
  nicename ||= library
  unless testForLibrary( library, nicename )
    msgs = [ "You are missing the required #{nicename} library.\n" ]
    msgs << "RAA: #{raaUrl}\n" if raaUrl
    msgs << "Download: #{downloadUrl}\n" if downloadUrl
    if fatal
      abort msgs.join('')
    else
      errorMessage msgs.join('')
    end
  end
  return true
end

.try(msg, bind = nil) ⇒ Object

Try the specified code block, printing the given



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/carat-dev/misc/utils.rb', line 395

def try( msg, bind=nil )  #:yield:
  result = nil
  message "Trying #{msg}..."

  begin
      rval = nil
      if block_given?
          rval = yield
      else
          file, line = caller(1)[0].split(/:/,2)
          rval = eval( msg, bind, file, line.to_i )
      end

      if $yaml
          result = rval.to_yaml
      else
          result = rval.inspect
      end
  rescue Exception => err
      nicetrace = err.backtrace.delete_if {|frame|
          /in `(try|eval)'/ =~ frame
      }.join("\n\t")
      result = err.message + "\n\t" + nicetrace
  ensure
      puts result
  end
end

.verbose_block(v = true, &block) ⇒ Object

Run a block, with $VERBOSE set to v. No other threads will run while this block is being executed.



426
427
428
429
430
431
432
433
434
435
436
# File 'lib/carat-dev/misc/utils.rb', line 426

def verbose_block(v = true, &block)
  Thread.critical = true
  tmp = $VERBOSE
  $VERBOSE = v
  begin
    yield
  ensure
    $VERBOSE = tmp
    Thread.critical = false
  end
end

.verboseOffObject

Execute a block with $VERBOSE set to false, restoring it to its previous value before returning.

Raises:

  • (LocalJumpError)


378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/carat-dev/misc/utils.rb', line 378

def verboseOff
    raise LocalJumpError, "No block given" unless block_given?

    thrcrit = Thread.critical
    oldverbose = $VERBOSE
    begin
        Thread.critical = true
        $VERBOSE = false
        yield
    ensure
        $VERBOSE = oldverbose
        Thread.critical = false
    end
end

.vetManifest(filelist, antimanifest = ANITMANIFEST) ⇒ Object

Given a filelist like that returned by #readManifest, remove the entries therein which match the Regexp objects in the given antimanifest and return the resultant Array.



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/carat-dev/misc/utils.rb', line 288

def vetManifest( filelist, antimanifest=ANITMANIFEST )
    origLength = filelist.length
    message "Vetting manifest..."

    for regex in antimanifest
        if $VERBOSE
            message "\n\tPattern /#{regex.source}/ removed: " +
                filelist.find_all {|file| regex.match(file)}.join(', ')
        end
        filelist.delete_if {|file| regex.match(file)}
    end

    message "removed #{origLength - filelist.length} files from the list.\n"
    return filelist
end