Module: FalkorLib::Common Abstract

Included in:
FalkorLib::CLI::App, FalkorLib::CLI::Link, FalkorLib::CLI::Make
Defined in:
lib/falkorlib/common.rb

Overview

This module is 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"

Class Method Summary collapse

Class Method Details

.ask(question, default_answer = '') ⇒ Object

Ask a question



88
89
90
91
92
93
94
95
96
# File 'lib/falkorlib/common.rb', line 88

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) ⇒ Object

Default printing functions ###

Print a text in bold



41
42
43
# File 'lib/falkorlib/common.rb', line 41

def bold(str)
  (COLOR == true) ? Term::ANSIColor.bold(str) : str
end

.command?(name) ⇒ Boolean

Check for the presence of a given command

Returns:

  • (Boolean)


111
112
113
114
# File 'lib/falkorlib/common.rb', line 111

def command?(name)
  `which #{name}`
  $?.success?
end

.cyan(str) ⇒ Object

Print a text in cyan



56
57
58
# File 'lib/falkorlib/common.rb', line 56

def cyan(str)
  (COLOR == true) ? Term::ANSIColor.cyan(str) : str
end

.error(str) ⇒ Object

Print an error message and abort



72
73
74
75
76
# File 'lib/falkorlib/common.rb', line 72

def error(str)
  #abort red("*** ERROR *** " + str)
  $stderr.puts red("*** ERROR *** " + str)
  exit 1
end

.exec_or_exit(cmd) ⇒ Object

Execute a given command - exit if status != 0



152
153
154
155
156
157
158
# File 'lib/falkorlib/common.rb', line 152

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) ⇒ Object

Simpler version that use the system call



136
137
138
139
140
# File 'lib/falkorlib/common.rb', line 136

def execute(cmd)
  puts bold("[Running] #{cmd.gsub(/^\s*/, ' ')}")
  system(cmd)
  $?.exitstatus
end

.execute_in_dir(path, cmd) ⇒ Object

Execute in a given directory



143
144
145
146
147
148
149
# File 'lib/falkorlib/common.rb', line 143

def execute_in_dir(path, cmd)
  exit_status = 0
  Dir.chdir(path) do
    exit_status = run %( #{cmd} )
  end
  exit_status
end

.green(str) ⇒ Object

Print a text in green



46
47
48
# File 'lib/falkorlib/common.rb', line 46

def green(str)
  (COLOR == true) ? Term::ANSIColor.green(str) : str
end

.info(str) ⇒ Object

Print an info message



61
62
63
# File 'lib/falkorlib/common.rb', line 61

def info(str)
  puts green("[INFO] " + str)
end

.init_from_template(templatedir, rootdir, config = {}, options = { :erb_exclude => [], :no_interaction => false }) ⇒ Object

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


325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/falkorlib/common.rb', line 325

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 = '') ⇒ Object

RVM init



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/falkorlib/common.rb', line 453

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 = {}) ⇒ Object

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

Raises:

  • (SystemExit)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/falkorlib/common.rb', line 182

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) ⇒ Object

Return the yaml content as a Hash object



274
275
276
277
278
279
280
281
282
283
# File 'lib/falkorlib/common.rb', line 274

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) ⇒ Object

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



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/falkorlib/common.rb', line 117

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 = {}) ⇒ Object

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


480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/falkorlib/common.rb', line 480

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_implementedObject

simple helper text to mention a non-implemented feature



79
80
81
# File 'lib/falkorlib/common.rb', line 79

def not_implemented
  error("NOT YET IMPLEMENTED")
end

.really_continue?(default_answer = 'Yes') ⇒ Boolean

Ask whether or not to really continue

Returns:

  • (Boolean)


99
100
101
102
103
104
# File 'lib/falkorlib/common.rb', line 99

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) ⇒ Object

Print a text in red



51
52
53
# File 'lib/falkorlib/common.rb', line 51

def red(str)
  (COLOR == true) ? Term::ANSIColor.red(str) : str
end

.run(cmds) ⇒ Object

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



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/falkorlib/common.rb', line 162

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) ⇒ Object

Display a indexed list to select an i

Raises:

  • (SystemExit)


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/falkorlib/common.rb', line 227

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) ⇒ Object

Display a indexed list to select multiple indexes



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/falkorlib/common.rb', line 246

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 }) ⇒ Object

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



394
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
422
423
424
425
426
427
# File 'lib/falkorlib/common.rb', line 394

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 = {}) ⇒ Object

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


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/falkorlib/common.rb', line 289

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) ⇒ Object

Print an warning message



66
67
68
# File 'lib/falkorlib/common.rb', line 66

def warning(str)
  puts cyan("/!\\ WARNING: " + str)
end

.write_from_erb_template(erbfile, outfile, config = {}, options = { :no_interaction => false }) ⇒ Object

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


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/falkorlib/common.rb', line 364

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 => '' }) ⇒ Object

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


436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/falkorlib/common.rb', line 436

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