class Molder::CLI

Attributes

instance[RW]
argv[RW]
command[RW]
config[RW]
kernel[R]
options[RW]
original_argv[RW]
stderr[R]
stdin[R]
stdout[R]

Public Class Methods

new(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel) click to toggle source
# File lib/molder/cli.rb, line 16
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
  @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel

  self.options                 = Hashie::Mash.new
  self.options[:max_processes] = Etc.nprocessors - 2
  self.argv                    = argv.dup
  self.original_argv           = argv.dup
  self.class.instance          = self
end

Public Instance Methods

execute!() click to toggle source
# File lib/molder/cli.rb, line 26
def execute!

  self.argv << '-h' if argv.empty?

  parser.parse!(self.argv)

  if options.help
    @kernel.exit(0)
    return
  end

  exit_code = begin
    $stderr = @stderr
    $stdin  = @stdin
    $stdout = @stdout

    parse_args!

    App.new(config: config, options: options, command_name: command).execute!

    0
  rescue StandardError => e
    report_error(exception: e)
    1
  rescue SystemExit => e
    e.status
  ensure
    $stderr = STDERR
    $stdin  = STDIN
    $stdout = STDOUT
  end
  @kernel.exit(exit_code)
end

Private Instance Methods

index_expression_to_array(value = nil) click to toggle source
# File lib/molder/cli.rb, line 220
def index_expression_to_array(value = nil)
  return nil if value.nil?
  value.include?('..') ? eval("(#{value}).to_a") : eval("[#{value}]")
end
parse_args!() click to toggle source
# File lib/molder/cli.rb, line 62
def parse_args!
  pre_parse!

  if options.indexes
    override = {}
    options.names.each_pair do |name, values|
      if values.nil?
        override[name] = option.indexes
      end
    end
    options.names.merge!(override)
  end

  self.config = if options.config
                  if File.exist?(options.config)
                    Configuration.load(options.config)
                  else
                    report_error(message: "file #{options.config} does not exist.")
                  end
                else
                  Configuration.default
                end
end
parse_name(t) click to toggle source
# File lib/molder/cli.rb, line 214
def parse_name(t)
  name, values = t.split('[')
  values.gsub!(/\]/, '') if values
  [name, index_expression_to_array(values)]
end
parse_templates(arg) click to toggle source
# File lib/molder/cli.rb, line 205
def parse_templates(arg)
  options[:names] ||= Hashie::Mash.new
  templates       = arg.split('/')
  templates.each do |t|
    name, indexes         = parse_name(t)
    options[:names][name] = indexes
  end
end
parser() click to toggle source
# File lib/molder/cli.rb, line 97
    def parser
      OptionParser.new(nil, 35) do |opts|
        opts.separator 'OPTIONS:'.bold.yellow

        opts.on('-c', '--config [file]',
                'Main YAML configuration file', ' ') { |config| options[:config] = config }

        opts.on('-t', '--template [n1/n2/..]',
                'Names of the templates to use', ' ') do |value|
          options[:names] ||= Hashie::Mash.new
          value.split('/').each { |arg| parse_templates(arg) }
        end

        opts.on('-i', '--index [range/array]',
                'Numbers to use in generating commands',
                'Can be a comma-separated list of values,',
                'or a range, eg "1..5"', ' ') do |value|
          options[:indexes] = index_expression_to_array(value)
        end

        opts.on('-a', '--attrs [k1=v1/k2=v2/...]',
                'Provide additional attributes, or ',
                'override existing ones. Can be used ',
                'more than once on a command line', ' ') do |value|
          h = {}
          value.split('/').each do |pair|
            key, value = pair.split('=')
            h[key]     = value
          end
          options[:override] ||= {}
          options[:override].merge!(h)
        end

        opts.on('-m', '--max-processes [number]',
                'Limit number of concurrent running processes',
                'The default is the number of CPU cores', ' '
                ) { |value| options[:max_processes] = value.to_i }

        opts.on('-b', '--allow-blanks',
                'Instead of throwing error when attribute',
                'is nil, replace it with a blank', ' ') { |_value| options[:blank] = true }

        opts.on('-l', '--log-dir [dir]',
                'Folder where STDOUT of the commands is saved') { |value| options[:log_dir] = value }

        opts.on('-n', '--dry-run',
                'Don\'t actually run commands, just print') { |_value| options[:dry_run] = true }

        opts.on('-v', '--verbose',
                'More verbose output') { |_value| options[:verbose] = true }

        opts.on('-d', '--debug',
                'Show error stack trace if available') { |_value| options[:backtrace] = true }

        opts.on('-V', '--version',
                'Show version') do
          @stdout.puts Molder::VERSION
          options[:help] = true
        end

        opts.on('-h', '--help',
                'Show help', ' ') do
          @stdout.puts opts
          options[:help] = true
        end

      end.tap do |p|
        p.banner = <<-eof
        
#{'DESCRIPTION'.bold.yellow}
    Molder is a template-based command generator and runner for cases where 
    you need to generate many similar and yet somewhat different commands, 
    defined in the YAML template. Please visit #{'https://github.com/kigster/molder'.bold.blue.underlined} 
    for a detailed explanation of the config file structure.

    Note, that the default config is #{Molder::Configuration::DEFAULT_CONFIG.bold.green}. 

#{'USAGE'.bold.yellow}
    #{'# shorthand usage - combine multiple templates with a slash:'.bold.black}
    #{'molder [-c config.yml] command template1[n1..n2]/...  [options]'.bold.green}

    #{'# alternatively, use -t and -i CLI options:'.bold.black}
    #{'molder [-c config.yml] command -t template -i index   [options]'.green.bold}

#{'EXAMPLES'.bold.yellow}
    #{'# The following commands assume YAML file is in the default location:'.bold.black}
    #{'molder provision web[1,3,5]'.bold.blue}

    #{'# -n flag means dry run — so instead of running commands, just print them:'.bold.black}
    #{'molder provision web[1..4]/job[1..4] -n'.bold.blue}

    #{'# Here we supply (or override) attributes "environment" and "flavor":'.bold.black}
    #{'molder provision web[1..4]/job[1..4] -n -a environment=production \
                                            -a flavor=c5.8xlarge'.bold.blue}

        eof
      end
    end
pre_parse!() click to toggle source
# File lib/molder/cli.rb, line 86
def pre_parse!
  if argv[0] && !argv[0].start_with?('-')
    self.command = argv.shift
  end

  if self.argv[0] && !self.argv[0].start_with?('-')
    options[:names] = Hashie::Mash.new
    self.argv.shift.split('/').each { |arg| parse_templates(arg) }
  end
end
report_error(message: nil, exception: nil) click to toggle source
# File lib/molder/cli.rb, line 196
def report_error(message: nil, exception: nil)
  if options[:backtrace] && exception.backtrace
    @stderr.puts exception.backtrace.reverse.join("\n").yellow.italic
  end
  @stderr.puts ' • ERROR • '.white.on.red + " #{exception.to_s.bold.red}" if exception
  @stderr.puts ' • ERROR • '.white.on.red + " #{message.bold.red}" if message
  @kernel.exit(1)
end