module FalkorLib::Common

@abstract Recipe for all my toolbox and versatile Ruby functions I’m using everywhere. You’ll typically want to include the ‘FalkorLib::Common` module to bring the corresponding definitions into yoru scope.

@example:

require 'falkorlib'
include FalkorLib::Common

info 'exemple of information text'
really_continue?
run %{ echo 'this is an executed command' }

Falkor.config.debug = true
run %{ echo 'this is a simulated command that *will not* be executed' }
error "that's an error text, let's exit with status code 1"

Public Instance Methods

ask(question, default_answer = '') click to toggle source

Ask a question

# File lib/falkorlib/common.rb, line 87
def ask(question, default_answer = '')
  return default_answer if FalkorLib.config[:no_interaction]
  print "#{question} "
  print "[Default: #{default_answer}]" unless default_answer == ''
  print ": "
  STDOUT.flush
  answer = STDIN.gets.chomp
  (answer.empty?) ? default_answer : answer
end
bold(str) click to toggle source
Default printing functions ###

Print a text in bold

# File lib/falkorlib/common.rb, line 40
def bold(str)
  (COLOR == true) ? Term::ANSIColor.bold(str) : str
end
command?(name) click to toggle source

Check for the presence of a given command

# File lib/falkorlib/common.rb, line 110
def command?(name)
  `which #{name}`
  $?.success?
end
cyan(str) click to toggle source

Print a text in cyan

# File lib/falkorlib/common.rb, line 55
def cyan(str)
  (COLOR == true) ? Term::ANSIColor.cyan(str) : str
end
error(str) click to toggle source

Print an error message and abort

# File lib/falkorlib/common.rb, line 71
def error(str)
  #abort red("*** ERROR *** " + str)
  $stderr.puts red("*** ERROR *** " + str)
  exit 1
end
exec_or_exit(cmd) click to toggle source

Execute a given command - exit if status != 0

# File lib/falkorlib/common.rb, line 151
def exec_or_exit(cmd)
  status = execute(cmd)
  if (status.to_i.nonzero?)
    error("The command '#{cmd}' failed with exit status #{status.to_i}")
  end
  status
end
execute(cmd) click to toggle source

Simpler version that use the system call

# File lib/falkorlib/common.rb, line 135
def execute(cmd)
  puts bold("[Running] #{cmd.gsub(/^\s*/, ' ')}")
  system(cmd)
  $?.exitstatus
end
execute_in_dir(path, cmd) click to toggle source

Execute in a given directory

# File lib/falkorlib/common.rb, line 142
def execute_in_dir(path, cmd)
  exit_status = 0
  Dir.chdir(path) do
    exit_status = run %( #{cmd} )
  end
  exit_status
end
green(str) click to toggle source

Print a text in green

# File lib/falkorlib/common.rb, line 45
def green(str)
  (COLOR == true) ? Term::ANSIColor.green(str) : str
end
info(str) click to toggle source

Print an info message

# File lib/falkorlib/common.rb, line 60
def info(str)
  puts green("[INFO] " + str)
end
init_from_template(templatedir, rootdir, config = {}, options = { :erb_exclude => [], :no_interaction => false }) click to toggle source

Bootstrap the destination directory ‘rootdir` using the template directory `templatedir`. the hash table `config` hosts the elements to feed ERB files which should have the extension .erb. The initialization is performed as follows:

  • a rsync process is initiated to duplicate the directory structure and the symlinks, and exclude .erb files

  • each erb files (thus with extension .erb) is interpreted, the corresponding file is generated without the .erb extension

Supported options:

:erb_exclude [array of strings]: pattern(s) to exclude from erb file
                                 interpretation and thus to copy 'as is'
:no_interaction [boolean]: do not interact
# File lib/falkorlib/common.rb, line 324
def init_from_template(templatedir, rootdir, config = {},
                       options = {
                         :erb_exclude    => [],
                         :no_interaction => false
                       })
  error "Unable to find the template directory" unless File.directory?(templatedir)
  warning "about to initialize/update the directory #{rootdir}"
  really_continue? unless options[:no_interaction]
  run %( mkdir -p #{rootdir} ) unless File.directory?( rootdir )
  run %( rsync --exclude '*.erb' --exclude '.texinfo*' -avzu #{templatedir}/ #{rootdir}/ )
  Dir["#{templatedir}/**/*.erb"].each do |erbfile|
    relative_outdir = Pathname.new( File.realpath( File.dirname(erbfile) )).relative_path_from Pathname.new(templatedir)
    filename = File.basename(erbfile, '.erb')
    outdir   = File.realpath( File.join(rootdir, relative_outdir.to_s) )
    outfile  = File.join(outdir, filename)
    unless options[:erb_exclude].nil?
      exclude_entry = false
      options[:erb_exclude].each do |pattern|
        exclude_entry |= erbfile =~ /#{pattern}/
      end
      if exclude_entry
        info "copying non-interpreted ERB file"
        # copy this file since it has been probably excluded from teh rsync process
        run %( cp #{erbfile} #{outdir}/ )
        next
      end
    end
    # Let's go
    info "updating '#{relative_outdir}/#{filename}'"
    puts "  using ERB template '#{erbfile}'"
    write_from_erb_template(erbfile, outfile, config, options)
  end
end
init_rvm(rootdir = Dir.pwd, gemset = '') click to toggle source

RVM init

# File lib/falkorlib/common.rb, line 452
def init_rvm(rootdir = Dir.pwd, gemset = '')
  rvm_files = {
    :version => File.join(rootdir, '.ruby-version'),
    :gemset  => File.join(rootdir, '.ruby-gemset')
  }
  unless File.exist?( (rvm_files[:version]).to_s)
    v = select_from(FalkorLib.config[:rvm][:rubies],
                    "Select RVM ruby to configure for this directory",
                    3)
    File.open( rvm_files[:version], 'w') do |f|
      f.puts v
    end
  end
  unless File.exist?( (rvm_files[:gemset]).to_s)
    g = (gemset.empty?) ? ask("Enter RVM gemset name for this directory", File.basename(rootdir)) : gemset
    File.open( rvm_files[:gemset], 'w') do |f|
      f.puts g
    end
  end
end
list_items(glob_pattern, options = {}) click to toggle source
List items from a glob pattern to ask for a unique choice

Supported options:

:only_files      [boolean]: list only files in the glob
:only_dirs       [boolean]: list only directories in the glob
:pattern_include [array of strings]: pattern(s) to include for listing
:pattern_exclude [array of strings]: pattern(s) to exclude for listing
:text            [string]: text to put
# File lib/falkorlib/common.rb, line 181
def list_items(glob_pattern, options = {})
  list  = { 0 => 'Exit' }
  index = 1
  raw_list = { 0 => 'Exit' }

  Dir[glob_pattern.to_s].each do |elem|
    #puts "=> element '#{elem}' - dir = #{File.directory?(elem)}; file = #{File.file?(elem)}"
    next if (!options[:only_files].nil?) && options[:only_files] && File.directory?(elem)
    next if (!options[:only_dirs].nil?)  && options[:only_dirs]  && File.file?(elem)
    entry = File.basename(elem)
    # unless options[:pattern_include].nil?
    #     select_entry = false
    #     options[:pattern_include].each do |pattern|
    #         #puts "considering pattern '#{pattern}' on entry '#{entry}'"
    #         select_entry |= entry =~ /#{pattern}/
    #     end
    #     next unless select_entry
    # end
    unless options[:pattern_exclude].nil?
      select_entry = false
      options[:pattern_exclude].each do |pattern|
        #puts "considering pattern '#{pattern}' on entry '#{entry}'"
        select_entry |= entry =~ /#{pattern}/
      end
      next if select_entry
    end
    #puts "selected entry = '#{entry}'"
    list[index]     = entry
    raw_list[index] = elem
    index += 1
  end
  text        = (options[:text].nil?)    ? "select the index" : options[:text]
  default_idx = (options[:default].nil?) ? 0 : options[:default]
  raise SystemExit, 'Empty list' if index == 1
  #ap list
  #ap raw_list
  # puts list.to_yaml
  # answer = ask("=> #{text}", "#{default_idx}")
  # raise SystemExit.new('exiting selection') if answer == '0'
  # raise RangeError.new('Undefined index')   if Integer(answer) >= list.length
  # raw_list[Integer(answer)]
  select_from(list, text, default_idx, raw_list)
end
load_config(file) click to toggle source

Return the yaml content as a Hash object

# File lib/falkorlib/common.rb, line 273
def load_config(file)
  unless File.exist?(file)
    raise FalkorLib::Error, "Unable to find the YAML file '#{file}'"
  end
  loaded = YAML.load_file(file)
  unless loaded.is_a?(Hash)
    raise FalkorLib::Error, "Corrupted or invalid YAML file '#{file}'"
  end
  loaded
end
nice_execute(cmd) click to toggle source

Execute a given command, return exit code and print nicely stdout and stderr

# File lib/falkorlib/common.rb, line 116
def nice_execute(cmd)
  puts bold("[Running] #{cmd.gsub(/^\s*/, ' ')}")
  stdout, stderr, exit_status = Open3.capture3( cmd )
  unless stdout.empty?
    stdout.each_line do |line|
      print "** [out] #{line}"
      $stdout.flush
    end
  end
  unless stderr.empty?
    stderr.each_line do |line|
      $stderr.print red("** [err] #{line}")
      $stderr.flush
    end
  end
  exit_status
end
normalized_path(dir = Dir.pwd, options = {}) click to toggle source
normalize_path ######

Normalize a path and return the absolute path foreseen Ex: ‘.’ return Dir.pwd Supported options:

* :relative   [boolean] return relative path to the root dir
# File lib/falkorlib/common.rb, line 479
def normalized_path(dir = Dir.pwd, options = {})
  rootdir = (FalkorLib::Git.init?(dir)) ? FalkorLib::Git.rootdir(dir) : dir
  path = dir
  path = Dir.pwd if dir == '.'
  path = File.join(Dir.pwd, dir) unless (dir =~ /^\// || (dir == '.'))
  if (options[:relative] || options[:relative_to])
    root = (options[:relative_to]) ? options[:relative_to] : rootdir
    relative_path_to_root = Pathname.new( File.realpath(path) ).relative_path_from Pathname.new(root)
    path = relative_path_to_root.to_s
  end
  path
end
not_implemented() click to toggle source

simple helper text to mention a non-implemented feature

# File lib/falkorlib/common.rb, line 78
def not_implemented
  error("NOT YET IMPLEMENTED")
end
really_continue?(default_answer = 'Yes') click to toggle source

Ask whether or not to really continue

# File lib/falkorlib/common.rb, line 98
def really_continue?(default_answer = 'Yes')
  return if FalkorLib.config[:no_interaction]
  pattern = (default_answer =~ /yes/i) ? '(Y|n)' : '(y|N)'
  answer = ask( cyan("=> Do you really want to continue #{pattern}?"), default_answer)
  exit 0 if answer =~ /n.*/i
end
red(str) click to toggle source

Print a text in red

# File lib/falkorlib/common.rb, line 50
def red(str)
  (COLOR == true) ? Term::ANSIColor.red(str) : str
end
run(cmds) click to toggle source

“Nice” way to present run commands Ex: run %{ hostname -f }

# File lib/falkorlib/common.rb, line 161
def run(cmds)
  exit_status = 0
  puts bold("[Running]\n#{cmds.gsub(/^\s*/, '   ')}")
  $stdout.flush
  #puts cmds.split(/\n */).inspect
  cmds.split(/\n */).each do |cmd|
    next if cmd.empty?
    system(cmd.to_s) unless FalkorLib.config.debug
    exit_status = $?.exitstatus
  end
  exit_status
end
select_from(list, text = 'Select the index', default_idx = 0, raw_list = list) click to toggle source

Display a indexed list to select an i

# File lib/falkorlib/common.rb, line 226
def select_from(list, text = 'Select the index', default_idx = 0, raw_list = list)
  error "list and raw_list differs in size" if list.size != raw_list.size
  l     = list
  raw_l = raw_list
  if list.is_a?(Array)
    l = raw_l = { 0 => 'Exit' }
    list.each_with_index do |e, idx|
      l[idx + 1] = e
      raw_l[idx + 1] = raw_list[idx]
    end
  end
  puts l.to_yaml
  answer = ask("=> #{text}", default_idx.to_s)
  raise SystemExit, 'exiting selection' if answer == '0'
  raise RangeError, 'Undefined index'   if Integer(answer) >= l.length
  raw_l[Integer(answer)]
end
select_multiple_from(list, text = 'Select the index', default_idx = 1, raw_list = list) click to toggle source

Display a indexed list to select multiple indexes

# File lib/falkorlib/common.rb, line 245
def select_multiple_from(list, text = 'Select the index', default_idx = 1, raw_list = list)
  error "list and raw_list differs in size" if list.size != raw_list.size
  l     = list
  raw_l = raw_list
  if list.is_a?(Array)
    l = raw_l = { 0 => 'Exit', 1 => 'End of selection' }
    list.each_with_index do |e, idx|
      l[idx + 2] = e
      raw_l[idx + 2] = raw_list[idx]
    end
  end
  puts l.to_yaml
  choices = Array.new
  answer  = 0
  begin
    choices.push(raw_l[Integer(answer)]) if Integer(answer) > 1
    answer = ask("=> #{text}", default_idx.to_s)
    raise SystemExit, 'exiting selection' if answer == '0'
    raise RangeError, 'Undefined index'   if Integer(answer) >= l.length
  end while Integer(answer) != 1
choices
end
show_diff_and_write(content, outfile, options = { :no_interaction => false, :json_pretty_format => false, :no_commit => false }) click to toggle source
Show the difference between a `content` string and an destination file (using Diff algorithm).

Obviosuly, if the outfile does not exists, no difference is proposed. Supported options:

:no_interaction [boolean]:     do not interact
:json_pretty_format [boolean]: write a json content, in pretty format
:no_commit [boolean]:          do not (offer to) commit the changes

return 0 if nothing happened, 1 if a write has been done

# File lib/falkorlib/common.rb, line 393
def show_diff_and_write(content, outfile, options = {
  :no_interaction => false,
  :json_pretty_format => false,
  :no_commit => false
})
  if File.exist?( outfile )
    ref = File.read( outfile )
    if options[:json_pretty_format]
      ref = JSON.pretty_generate(JSON.parse( IO.read( outfile ) ))
    end
    if ref == content
      warn "Nothing to update"
      return 0
    end
    warn "the file '#{outfile}' already exists and will be overwritten."
    warn "Expected difference: \n------"
    Diffy::Diff.default_format = :color
    puts Diffy::Diff.new(ref, content, :context => 1)
  else
    watch = (options[:no_interaction]) ? 'no' : ask( cyan("  ==> Do you want to see the generated file before commiting the writing (y|N)"), 'No')
    puts content if watch =~ /y.*/i
  end
  proceed = (options[:no_interaction]) ? 'yes' : ask( cyan("  ==> proceed with the writing (Y|n)"), 'Yes')
  return 0 if proceed =~ /n.*/i
  info("=> writing #{outfile}")
  File.open(outfile.to_s, "w+") do |f|
    f.write content
  end
  if FalkorLib::Git.init?(File.dirname(outfile)) && !options[:no_commit]
    do_commit = (options[:no_interaction]) ? 'yes' : ask( cyan("  ==> commit the changes (Y|n)"), 'Yes')
    FalkorLib::Git.add(outfile, "update content of '#{File.basename(outfile)}'") if do_commit =~ /y.*/i
  end
  1
end
store_config(filepath, hash, options = {}) click to toggle source

Store the Hash object as a Yaml file Supported options:

:header         [string]: additional info to place in the header of the (stored) file
:no_interaction [boolean]: do not interact
# File lib/falkorlib/common.rb, line 288
def store_config(filepath, hash, options = {})
  content  = "# " + File.basename(filepath) + "\n"
  content += "# /!\\ DO NOT EDIT THIS FILE: it has been automatically generated\n"
  if options[:header]
    options[:header].split("\n").each { |line| content += "# #{line}" }
  end
  content += hash.to_yaml
  show_diff_and_write(content, filepath, options)
  # File.open( filepath, 'w') do |f|
  #     f.print "# ", File.basename(filepath), "\n"
  #     f.puts "# /!\\ DO NOT EDIT THIS FILE: it has been automatically generated"
  #     if options[:header]
  #         options[:header].split("\n").each do |line|
  #             f.puts "# #{line}"
  #         end
  #     end
  #     f.puts hash.to_yaml
  # end
end
warning(str) click to toggle source

Print an warning message

# File lib/falkorlib/common.rb, line 65
def warning(str)
  puts cyan("/!\\ WARNING: " + str)
end
write_from_erb_template(erbfile, outfile, config = {}, options = { :no_interaction => false }) click to toggle source

ERB generation of the file ‘outfile` using the source template file `erbfile` Supported options:

:no_interaction [boolean]: do not interact
:srcdir         [string]: source dir for all considered ERB files
# File lib/falkorlib/common.rb, line 363
def write_from_erb_template(erbfile, outfile, config = {},
                            options = {
                              :no_interaction => false
                            })
  erbfiles = (erbfile.is_a?(Array)) ? erbfile : [ erbfile ]
  content = ""
  erbfiles.each do |f|
    erb = (options[:srcdir].nil?) ? f : File.join(options[:srcdir], f)
    unless File.exist?(erb)
      warning "Unable to find the template ERBfile '#{erb}'"
      really_continue? unless options[:no_interaction]
      next
    end
    #puts config.to_yaml
    content += ERB.new(File.read(erb.to_s), trim_mode: '<>').result(binding)
  end
  # error "Unable to find the template file #{erbfile}" unless File.exists? (erbfile )
  # template = File.read("#{erbfile}")
  # output   = ERB.new(template, nil, '<>')
  # content  = output.result(binding)
  show_diff_and_write(content, outfile, options)
end
write_from_template(src, dstdir, options = { :no_interaction => false, :no_commit => false, :srcdir => '', :outfile => '' }) click to toggle source
Blind copy of a source file `src` into its destination directory `dstdir`

Supported options:

:no_interaction [boolean]: do not interact
:srcdir [string]: source directory, make the `src` file relative to that directory
:outfile [string]: alter the outfile name (File.basename(src) by default)
:no_commit [boolean]:          do not (offer to) commit the changes
# File lib/falkorlib/common.rb, line 435
def write_from_template(src, dstdir, options = {
  :no_interaction => false,
  :no_commit      => false,
  :srcdir         => '',
  :outfile        => ''
})
  srcfile = (options[:srcdir].nil?) ? src : File.join(options[:srcdir], src)
  error "Unable to find the source file #{srcfile}" unless File.exist?( srcfile )
  error "The destination directory '#{dstdir}' do not exist" unless File.directory?( dstdir )
  dstfile = (options[:outfile].nil?) ? File.basename(srcfile) : options[:outfile]
  outfile = File.join(dstdir, dstfile)
  content = File.read( srcfile )
  show_diff_and_write(content, outfile, options)
end