Module: FalkorLib::Common Abstract

Included in:
FalkorLib::CLI::App, FalkorLib::CLI::Link
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



87
88
89
90
91
92
93
94
95
# 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
    return answer.empty?() ? default_answer : answer
end

.bold(str) ⇒ Object

Default printing functions ###

Print a text in bold



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

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

.command?(name) ⇒ Boolean

Check for the presence of a given command

Returns:

  • (Boolean)


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

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

.cyan(str) ⇒ Object

Print a text in cyan



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

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

.error(str) ⇒ Object

Print an error message and abort



71
72
73
74
75
# 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) ⇒ Object

Execute a given command - exit if status != 0



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

def exec_or_exit(cmd)
    status = execute(cmd)
    if (status.to_i != 0)
        error("The command '#{cmd}' failed with exit status #{status.to_i}")
    end
    status
end

.execute(cmd) ⇒ Object

Simpler version that use the system call



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

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

.execute_in_dir(path, cmd) ⇒ Object

Execute in a given directory



142
143
144
145
146
147
148
# 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) ⇒ Object

Print a text in green



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

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

.info(str) ⇒ Object

Print an info message



60
61
62
# 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 }) ⇒ 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


301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/falkorlib/common.rb', line 301

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?
    run %{ mkdir -p #{rootdir} } unless File.directory?( rootdir )
    run %{ rsync --exclude '*.erb' -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.to_s}/#{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



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/falkorlib/common.rb', line 428

def init_rvm(rootdir = Dir.pwd, gemset = '')
    rvm_files = {
                 :version => File.join(rootdir, '.ruby-version'),
                 :gemset  => File.join(rootdir, '.ruby-gemset')
                }
    unless File.exists?( "#{rvm_files[:version]}")
        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.exists?( "#{rvm_files[:gemset]}")
        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)


181
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
# 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}"].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.new('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



250
251
252
253
254
255
256
257
258
259
# File 'lib/falkorlib/common.rb', line 250

def load_config(file)
    unless File.exists?(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



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# 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 = {}) ⇒ 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


456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/falkorlib/common.rb', line 456

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 =~ /^\// or dir == '.')
    if (options[:relative] or 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
    return path
end

.not_implementedObject

simple helper text to mention a non-implemented feature



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

def not_implemented()
    error("NOT YET IMPLEMENTED")
end

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

Ask whether or not to really continue

Returns:

  • (Boolean)


98
99
100
101
102
103
# 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) ⇒ Object

Print a text in red



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

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

.run(cmds) ⇒ Object

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



161
162
163
164
165
166
167
168
169
170
171
172
# 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}") unless FalkorLib.config.debug
        exit_status = $?
    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)


226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# 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.kind_of?(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}")
    raise SystemExit.new('exiting selection') if answer == '0'
    raise RangeError.new('Undefined index')   if Integer(answer) >= l.length
    raw_l[Integer(answer)]
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



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/falkorlib/common.rb', line 369

def show_diff_and_write(content, outfile, options = {
                                                     :no_interaction     => false,
                                                     :json_pretty_format => false,
                                                     :no_commit      => false,
                                                    })
    if File.exists?( 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}", "w+") do |f|
        f.write content
    end
    if FalkorLib::Git.init?(File.dirname(outfile)) and ! 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
    return 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


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

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 Also known as: warn

Print an warning message



65
66
67
# 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, }) ⇒ 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


340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/falkorlib/common.rb', line 340

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.exists? (erb)
            warning "Unable to find the template ERBfile '#{erb}'"
            really_continue? unless options[:no_interaction]
            next
        end 
        content += ERB.new(File.read("#{erb}"), nil, '<>').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


411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/falkorlib/common.rb', line 411

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.exists? ( 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