class PuppetRepl::Cli

Attributes

html_mode[RW]
in_buffer[RW]
log_level[RW]
out_buffer[RW]
settings[RW]

Public Class Methods

new(options={}) click to toggle source
# File lib/puppet-repl/cli.rb, line 12
def initialize(options={})
  @log_level = 'notice'
  @out_buffer = options[:out_buffer] || $stdout
  @html_mode = options[:html_mode] || false
  @source_file = options[:source_file] || nil
  @source_line_num = options[:source_line] || nil
  @in_buffer = options[:in_buffer] || $stdin
  comp = Proc.new do |s|
    key_words.grep(/^#{Regexp.escape(s)}/)
  end
  Readline.completion_append_character = ""
  Readline.basic_word_break_characters = " "
  Readline.completion_proc = comp
  AwesomePrint.defaults = {
    :html => @html_mode,
    :sort_keys => true,
    :indent => 2
  }
  do_initialize
end
print_repl_desc() click to toggle source
start(options={:scope => nil}) click to toggle source

start reads from stdin or from a file if from stdin, the repl will process the input and exit if from a file, the repl will process the file and continue to prompt @param [Hash] puppet scope object

# File lib/puppet-repl/cli.rb, line 218
def self.start(options={:scope => nil})
  opts = Trollop::options do
    opt :play, "Url or file to load from", :required => false, :type => String
    opt :run_once, "Evaluate and quit", :required => false, :default => false
    opt :node_name, "Remote Node to grab facts from", :required => false, :type => String
    opt :quiet, "Do not display banner", :required => false, :default => false
  end
  options = opts.merge(options)
  puts print_repl_desc unless options[:quiet]
  repl_obj = PuppetRepl::Cli.new(options)
  repl_obj.remote_node_name = opts[:node_name] if opts[:node_name]
  repl_obj.initialize_from_scope(options[:scope])
  if options[:play]
    repl_obj.play_back(opts)
  # when the user supplied a file name without using the args (stdin)
  elsif ARGF.filename != "-"
    path = File.expand_path(ARGF.filename)
    repl_obj.play_back(:play => path)
  # when the user supplied a file content using stdin, aka. cat,pipe,echo or redirection
  elsif ARGF.filename == "-" and (not STDIN.tty? and not STDIN.closed?)
    input = ARGF.read
    repl_obj.handle_input(input)
  end
  # helper code to make tests exit the loop
  unless options[:run_once]
    repl_obj.read_loop
  end
end
start_without_stdin(options={:scope => nil}) click to toggle source

used to start a repl without attempting to read from stdin or @param [Hash] must contain at least the puppet scope object

# File lib/puppet-repl/cli.rb, line 201
def self.start_without_stdin(options={:scope => nil})
  puts print_repl_desc unless options[:quiet]
  repl_obj = PuppetRepl::Cli.new(options)
  repl_obj.remote_node_name = options[:node_name] if options[:node_name]
  repl_obj.initialize_from_scope(options[:scope])
  puts repl_obj.whereami if options[:source_file] and options[:source_line]
  if options[:play]
    repl_obj.play_back(options)
  elsif ! options[:run_once]
    repl_obj.read_loop
  end
end

Public Instance Methods

expand_resource_type(types) click to toggle source

ruturns a formatted array

# File lib/puppet-repl/cli.rb, line 67
def expand_resource_type(types)
  output = [types].flatten.map do |t|
    if t.class.to_s =~ /Puppet::Pops::Types/
      to_resource_declaration(t)
    else
      t
    end
  end
  output
end
handle_input(input) click to toggle source

this method handles all input and expects a string of text.

# File lib/puppet-repl/cli.rb, line 93
def handle_input(input)
    raise ArgumentError unless input.instance_of?(String)
    begin
      output = ''
      case input
      when /^play|^classification|^whereami|^facterdb_filter|^facts|^vars|^functions|^classes|^resources|^krt|^environment|^reset|^help/
        args = input.split(' ')
        command = args.shift.to_sym
        if self.respond_to?(command)
          output = self.send(command, args)
        end
        return out_buffer.puts output
      when /exit/
        exit 0
      when /^:set/
        output = handle_set(input)
      when '_'
        output = " => #{@last_item}"
      else
        result = puppet_eval(input)
        @last_item = result
        output = normalize_output(result)
        if output.nil?
          output = ""
        else
          output = output.ai
        end
      end
    rescue LoadError => e
      output = e.message.fatal
    rescue Errno::ETIMEDOUT => e
      output = e.message.fatal
    rescue ArgumentError => e
      output = e.message.fatal
    rescue Puppet::ResourceError => e
      output = e.message.fatal
    rescue Puppet::Error => e
      output = e.message.fatal
    rescue Puppet::ParseErrorWithIssue => e
      output = e.message.fatal
    rescue PuppetRepl::Exception::FatalError => e
      output = e.message.fatal
      out_buffer.puts output
      exit 1 # this can sometimes causes tests to fail
    rescue PuppetRepl::Exception::Error => e
      output = e.message.fatal
    end
    unless output.empty?
      out_buffer.print " => "
      out_buffer.puts output unless output.empty?
    end
end
key_words() click to toggle source

returns a cached list of key words

# File lib/puppet-repl/cli.rb, line 34
def key_words
  # because dollar signs don't work we can't display a $ sign in the keyword
  # list so its not explicitly clear what the keyword
  variables = scope.to_hash.keys
  # prepend a :: to topscope variables
  scoped_vars = variables.map { |k,v| scope.compiler.topscope.exist?(k) ? "$::#{k}" : "$#{k}" }
  # append a () to functions so we know they are functions
  funcs = function_map.keys.map { |k| "#{k.split('::').last}()"}
  (scoped_vars + funcs + static_responder_list).uniq.sort
end
multiline_input?(e) click to toggle source

tries to determine if the input is going to be a multiline input by reading the parser error message

# File lib/puppet-repl/cli.rb, line 161
def multiline_input?(e)
  case e.message
  when /Syntax error at end of file/i
    true
  else
    false
  end
end
normalize_output(result) click to toggle source
# File lib/puppet-repl/cli.rb, line 78
def normalize_output(result)
  if result.instance_of?(Array)
    output = expand_resource_type(result)
    if output.count == 1
      return output.first
    end
    return output
  elsif result.class.to_s =~ /Puppet::Pops::Types/
    return to_resource_declaration(result)
  end
  result
end
read_loop() click to toggle source

reads input from stdin, since readline requires a tty we cannot read from other sources as readline requires a file object we parse the string after each input to determine if the input is a multiline_input entry. If it is multiline we run through the loop again and concatenate the input

# File lib/puppet-repl/cli.rb, line 175
def read_loop
  line_number = 1
  full_buffer = ''
  while buf = Readline.readline("#{line_number}:>> ", true)
    begin
      full_buffer += buf
      # unless this is puppet code, otherwise skip repl keywords
      unless keyword_expression.match(buf)
        line_number = line_number.next
        parser.parse_string(full_buffer)
      end
    rescue Puppet::ParseErrorWithIssue => e
      if multiline_input?(e)
        out_buffer.print '  '
        full_buffer += "\n"
        next
      end
    end
    handle_input(full_buffer)
    full_buffer = ''
  end
end
to_resource_declaration(type) click to toggle source

looks up the type in the catalog by using the type and title and returns the resource in ral format

# File lib/puppet-repl/cli.rb, line 47
def to_resource_declaration(type)
  if type.respond_to?(:type_name) and type.respond_to?(:title)
    title = type.title
    type_name = type.type_name
  elsif type_result = /(\w+)\['?(\w+)'?\]/.match(type.to_s)
    # not all types have a type_name and title so we
    # output to a string and parse the results
    title = type_result[2]
    type_name = type_result[1]
  else
    return type
  end
  res = scope.catalog.resource(type_name, title)
  if res
    return res.to_ral
  end
  # don't return anything or returns nil if item is not in the catalog
end