module Morpheus::Cli::OptionTypes

Public Class Methods

azure_marketplace_prompt(option_type, options={}, api_client=nil, api_params={}) click to toggle source

file_content_prompt() prompts for source (local,repository,url) and then content or repo or. returns a Hash like {sourceType:“local”,content:“yadda”,contentPath:null,contentRef:null}

# File lib/morpheus/cli/option_types.rb, line 1357
def self.azure_marketplace_prompt(option_type, options={}, api_client=nil, api_params={})
  cloud_id = api_params[:zoneId] || api_params[:cloudId] || api_params["zoneId"] || api_params["cloudId"]
  if cloud_id.nil?
    Morpheus::Logging::DarkPrinter.puts "Failed to load azure marketplace offers without a zoneId" if Morpheus::Logging.debug?
    return nil
  end
  # lets go!
  rtn = {}
  publisher_value, offer_value, sku_value, version_value = nil, nil, nil, nil

  # Marketplace Publisher & Offer
  marketplace_api_params = {'zoneId' => cloud_id}
  v_prompt = nil
  # API endpoints moved from /api/options to /api/options/azure...
  begin
    v_prompt = prompt([{'fieldName' => 'marketplaceOffer', 'fieldLabel' => 'Azure Marketplace Offer', 'type' => 'typeahead', 'optionSourceType' => 'azure', 'optionSource' => 'searchAzureMarketplace', 'required' => true, 'description' => "Select Azure Marketplace Offer."}], options,api_client, marketplace_api_params)
  rescue => ex
    Morpheus::Logging::DarkPrinter.puts "Failed to load azure marketplace offers, trying older endpoint" if Morpheus::Logging.debug?
    v_prompt = prompt([{'fieldName' => 'marketplaceOffer', 'fieldLabel' => 'Azure Marketplace Offer', 'type' => 'typeahead', 'optionSource' => 'searchAzureMarketplace', 'required' => true, 'description' => "Select Azure Marketplace Offer."}], options,api_client, marketplace_api_params)
  end
  # offer_value = v_prompt['marketplaceOffer']
  # actually need both offer and publisher of these to query correctly..sigh
  marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
  offer_value = marketplace_option['offer']
  publisher_value = marketplace_option['publisher']

  # SKU & VERSION
  if options && options['marketplaceSku'] && options['marketplaceVersion']
    # the value to match on is actually sku|version
    options['marketplaceSku'] = options['marketplaceSku'] + '|' + options['marketplaceVersion']
  end
  sku_api_params = {'zoneId' => cloud_id, publisher: publisher_value, offer: offer_value}
  begin
    v_prompt = prompt([{'fieldName' => 'marketplaceSku', 'fieldLabel' => 'Azure Marketplace SKU', 'type' => 'select', 'optionSourceType' => 'azure', 'optionSource' => 'searchAzureMarketplaceSkus', 'required' => true, 'description' => "Select Azure Marketplace SKU and Version, the format is SKU|Version"}], options,api_client, sku_api_params)
  rescue => ex
    Morpheus::Logging::DarkPrinter.puts "Failed to load azure marketplace offers, trying older endpoint" if Morpheus::Logging.debug?
    v_prompt = prompt([{'fieldName' => 'marketplaceSku', 'fieldLabel' => 'Azure Marketplace SKU', 'type' => 'select', 'optionSource' => 'searchAzureMarketplaceSkus', 'required' => true, 'description' => "Select Azure Marketplace SKU and Version, the format is SKU|Version"}], options,api_client, sku_api_params)
  end
  # marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
  # sku_value = marketplace_option['sku']
  # version_value = marketplace_option['version']
  sku_value = v_prompt['marketplaceSku']
  if sku_value && sku_value.include?("|")
    sku_value, version_value = sku_value.split("|")
  end
  # rtn['publisher'] = publisher_value
  # rtn['offer'] = offer_value
  # rtn['sku'] = sku_value
  # rtn['version'] = version_value
  # return rtn
  # instance provisioning expects these parameters...
  return {'marketplacePublisher' => publisher_value, 'marketplaceOffer' => offer_value, 'marketplaceSku' => sku_value, 'marketplaceVersion' => version_value}
end
checkbox_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1057
def self.checkbox_prompt(option_type)
  value_found = false
  value = nil
  has_default = option_type['defaultValue'] != nil
  default_on = has_default ? ['on', 'true', 'yes', '1'].include?(option_type['defaultValue'].to_s.downcase) : false
  while !value_found do
    print "#{option_type['fieldLabel']} (on/off)#{has_default ? ' ['+(default_on ? 'on' : 'off')+']' : ''}: "
    input = $stdin.gets.chomp!
    if input == '?'
      help_prompt(option_type)
      next
    end
    check_value = input.downcase.strip
    if check_value == 'yes' || check_value == 'y' || check_value == 'on' || check_value == 'true' || check_value == '1'
      value_found = true
      value = 'on'
    elsif check_value == 'no' || check_value == 'n' || check_value == 'off' || check_value == 'true' || check_value == '0'
      value_found = true
      value = 'off'
    elsif input == '' && has_default
      value_found = true
      value = default_on ? 'on' : 'off'
    elsif input != ""
      puts "Invalid Option... Please try again."
      next
    end
    if value.nil? && option_type['required']
      puts "Invalid Option... Please try again."
      next
    end
    if value.nil? && !option_type['required']
      value_found = true
    end
  end
  return value
end
confirm(message,options={}) click to toggle source

include Morpheus::Cli::PrintHelper

# File lib/morpheus/cli/option_types.rb, line 10
def self.confirm(message,options={})
  if options[:yes] == true
    return true
  end
  default_value = options[:default]
  value_found = false
  while value_found == false do
    # if default_value.nil?
    #   print "#{message} (yes/no): "
    # else
    #   print "#{message} (yes/no) [#{!!default_value ? 'yes' : 'no'}]: "
    # end
    # input = $stdin.gets.chomp!

    # should use Readline.readline to probably
    Readline.completion_append_character = ""
    Readline.basic_word_break_characters = ''
    Readline.completion_proc = nil
    if default_value.nil?
      confirm_prompt = "#{message} (yes/no): "
    else
      confirm_prompt = "#{message} (yes/no) [#{!!default_value ? 'yes' : 'no'}]: "
    end
    input = Readline.readline(confirm_prompt, false).to_s
    input = input.chomp.strip

    if input.empty? && !default_value.nil?
      return !!default_value
    end
    if input.downcase == 'yes' || input.downcase == 'y'
      return true
    elsif input.downcase == 'no' || input.downcase == 'n'
      return false
    else
      puts "Invalid Option... Please try again."
    end
  end
end
display_option_types_help(option_types, opts={}) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1524
def self.display_option_types_help(option_types, opts={})
  puts self.format_option_types_help(option_types, opts)
end
display_select_options(opt, select_options = [], paging = nil) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1502
def self.display_select_options(opt, select_options = [], paging = nil)
  puts self.format_select_options_help(opt, select_options, paging)
end
field_input_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1094
def self.field_input_prompt(option_type)
  value_found = false
  value = nil

  input_field_label = option_type['fieldInput'].gsub(/[A-Z]/, ' \0').split(' ').collect {|it| it.capitalize}.join(' ')
  input_field_name = option_type['fieldName'].split('.').reverse.drop(1).reverse.push(option_type['fieldInput']).join('.')
  input_option_type = option_type.merge({'fieldName' => input_field_name, 'fieldLabel' => input_field_label, 'required' => true, 'type' => 'text'})

  while !value_found do
    print "#{input_field_label}#{option_type['defaultInputValue'] ? " [#{option_type['defaultInputValue']}]" : ''}: "
    input = $stdin.gets.chomp!
    value = input.empty? ? option_type['defaultInputValue'] : input
    if input == '?'
      help_prompt(input_option_type)
    elsif !value.nil?
      value_found = true
    end
  end
  return value
end
file_content_prompt(option_type, options={}, api_client=nil, api_params={}) click to toggle source

file_content_prompt() prompts for source (local,repository,url) and then content or repo or. returns a Hash like {sourceType:“local”,content:“yadda”,contentPath:null,contentRef:null}

# File lib/morpheus/cli/option_types.rb, line 1253
def self.file_content_prompt(option_type, options={}, api_client=nil, api_params={})
  file_params = {}
  options ||= {}
  full_field_key = option_type['fieldContext'] ? "#{option_type['fieldContext']}.#{option_type['fieldName']}" : "#{option_type['fieldName']}"
  passed_file_params = get_object_value(options, full_field_key)
  if passed_file_params.is_a?(Hash)
    file_params = passed_file_params
  end
  is_required = option_type['required']

  if file_params['source']
    file_params['sourceType'] = file_params.delete('source')
  end
  source_type = file_params['sourceType']
  # source
  if source_type.nil?
    source_type = select_prompt({'fieldContext' => full_field_key, 'fieldName' => 'source', 'fieldLabel' => 'Source', 'type' => 'select', 'optionSource' => 'fileContentSource', 'required' => is_required, 'defaultValue' => (is_required ? 'local' : nil)}, api_client, {}, options[:no_prompt])
    file_params['sourceType'] = source_type
  end
  # source type options
  if source_type == "local"
    # prompt for content
    if file_params['content'].nil?
      if options[:no_prompt]
        print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * Content [-O #{full_field_key}.content=] - File Content\n", Term::ANSIColor.reset
        print "\n"
        exit 1
      else
        file_params['content'] = multiline_prompt({'fieldContext' => full_field_key, 'fieldName' => 'content', 'type' => 'code-editor', 'fieldLabel' => 'Content', 'required' => true})
      end
    end
  elsif source_type == "url"
    if file_params['url']
      file_params['contentPath'] = file_params.delete('url')
    end
    if file_params['contentPath'].nil?
      if options[:no_prompt]
        print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * URL [-O #{full_field_key}.url=] - Path of file in the repository\n", Term::ANSIColor.reset
        print "\n"
        exit 1
      else
        file_params['contentPath'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'url', 'fieldLabel' => 'URL', 'type' => 'text', 'required' => true})
      end
    end
  elsif source_type == "repository"
    if file_params['repository'].nil?
      repository_id = select_prompt({'fieldContext' => full_field_key, 'fieldName' => 'repositoryId', 'fieldLabel' => 'Repository', 'type' => 'select', 'optionSource' => 'codeRepositories', 'required' => true}, api_client, {}, options[:no_prompt])
      file_params['repository'] = {'id' => repository_id}
    end
    if file_params['contentPath'].nil?
      if options[:no_prompt]
        print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * File Path [-O #{full_field_key}.path=] - Path of file in the repository\n", Term::ANSIColor.reset
        print "\n"
        exit 1
      else
        file_params['contentPath'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'path', 'fieldLabel' => 'File Path', 'type' => 'text', 'required' => true})
      end
    end
    if !file_params.key?('contentRef')
      if options[:no_prompt]
        # pass
      else
        file_params['contentRef'] = generic_prompt({'fieldContext' => full_field_key, 'fieldName' => 'ref', 'fieldLabel' => 'Version Ref', 'type' => 'text'})
      end
    end
  end
  return file_params
end
file_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1216
def self.file_prompt(option_type)
  value_found = false
  value = nil
  while !value_found do
    #print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
    #Readline.input = $stdin
    Readline.completion_append_character = ""
    Readline.basic_word_break_characters = ''
    Readline.completion_proc = proc {|s| Readline::FILENAME_COMPLETION_PROC.call(s) }
    input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: ", false).to_s
    input = input.chomp.strip
    #input = $stdin.gets.chomp!
    value = input.empty? ? option_type['defaultValue'] : input.to_s
    if input == '?'
      help_prompt(option_type)
    elsif value.empty? && option_type['required'] != true
      value = nil
      value_found = true
    elsif value
      filename = File.expand_path(value)
      if !File.exist?(filename)
        # print_red_alert "File not found: #{filename}"
        # exit 1
        print Term::ANSIColor.red,"  File not found: #{filename}",Term::ANSIColor.reset, "\n"
      elsif !File.file?(filename)
        print Term::ANSIColor.red,"  Argument is not a file: #{filename}",Term::ANSIColor.reset, "\n"
      else
        value = filename
        value_found = true
      end
    end
  end
  return value
end
format_option_types_help(option_types, opts={}) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1506
def self.format_option_types_help(option_types, opts={})
  option_types = self.sort_option_types(option_types).reject {|it| it['hidden']}

  if option_types.empty?
    "#{opts[:color]}#{opts[:title] || "Available Options:"}\nNone\n\n"
  else
    if opts[:include_context]
      option_lines = option_types.collect {|it|
        field_context = (opts[:context_map] || {})[it['fieldContext']] || it['fieldContext']
        "    -O #{field_context && field_context != '' ? "#{field_context}." : ''}#{it['fieldName']}=\"value\""
      }
    else
      option_lines = option_types.collect {|it| "    -O #{it['fieldName']}=\"value\"" }
    end
    "#{opts[:color]}#{opts[:title] || "Available Options:"}\n#{option_lines.join("\n")}\n\n"
  end
end
format_select_options_help(opt, select_options = [], paging = nil) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1472
def self.format_select_options_help(opt, select_options = [], paging = nil)
  out = ""
  header = opt['fieldLabel'] ? "#{opt['fieldLabel']} Options" : "Options"
  value_field = (opt['config'] ? opt['config']['valueField'] : nil) || 'value'
  if paging
    offset = paging[:cur_page] * paging[:page_size]
    limit = [offset + paging[:page_size], select_options.count].min - 1
    header = "#{header} (#{offset+1}-#{limit+1} of #{paging[:total]})"
    select_options = select_options[(offset)..(limit)]
  end
  out = ""
  out << "\n"
  out << "#{header}\n"
  out << "#{'=' * header.length}\n"
  select_options.each do |option|
    out << (option['isGroup'] ? "- #{option['name']}\n" : " * #{option['name']} [#{option[value_field]}]\n")
  end
  return out
end
generic_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1115
def self.generic_prompt(option_type)
  value_found = false
  value = nil
  while !value_found do
    # print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
    # input = $stdin.gets.chomp!
    Readline.completion_append_character = ""
    Readline.basic_word_break_characters = ''
    Readline.completion_proc = nil
    prompt_label = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
    input = Readline.readline(prompt_label, false).to_s
    input = input.chomp #.strip

    value = input.empty? ? option_type['defaultValue'] : input
    if input == '?'
      help_prompt(option_type)
    elsif !value.nil? || option_type['required'] != true
      value_found = true
    end
    # attempt to parse Java regex and validate it
    if option_type["verifyPattern"].to_s != "" && !(value.to_s == "" && option_type['required'])
      begin
        # pattern is matched on the entire string
        verify_pattern = Regexp.compile("^" + option_type["verifyPattern"] + "$")
        if !verify_pattern.match(value)
          value_found = false
          puts "Invalid Option. Value must match the pattern '#{option_type['verifyPattern']}'. Please try again."
          next
        end
      rescue => regex_ex
        puts "Failed to parse verifyPattern '#{option_type['verifyPattern']}' as a regular expression"
      end
    end
  end
  return value
end
get_last_select() click to toggle source
# File lib/morpheus/cli/option_types.rb, line 617
def self.get_last_select()
  Thread.current[:_last_select]
end
get_option_value(obj, option_type, format=false) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1537
def self.get_option_value(obj, option_type, format=false)
  context = option_type['fieldContext'] == 'config' ? obj['config'] : obj
  name = option_type['fieldName']
  tokens = name.split('.')

  if tokens.length > 1
    tokens.slice(0, tokens.length - 1).each do |token|
      context = context[token]
    end
    name = tokens.last
  end

  if context.kind_of?(Array)
    rtn = context.collect {|it| it['name'] || it[name]}.join ', '
  else
    rtn = context[name]
  end

  if format
    rtn = (rtn ? 'On' : 'Off') if option_type['type'] == 'checkbox'
    rtn = rtn.join(', ') if rtn.is_a?(Array)
  end
  rtn
end
help_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1451
def self.help_prompt(option_type)
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
  # an attempt at prompting help for natural options without the -O switch
  if option_type[:fmt] == :natural
    print Term::ANSIColor.green,"  * #{option_type['fieldLabel']} [--#{help_field_key}=] ", Term::ANSIColor.reset , "#{option_type['description']}\n"
  else
    print Term::ANSIColor.green,"  * #{option_type['fieldLabel']} [-O #{help_field_key}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
  end
  if option_type['type'].to_s == 'typeahead'
    print "This is a typeahead input. Enter the name or value of an option.\n"
    print "If the specified input matches more than one option, they will be printed and you will be prompted again.\n"
    print "the matching options will be shown and you can try again.\n"
  end
end
load_options(option_type, api_client, api_params, query_value=nil) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1411
def self.load_options(option_type, api_client, api_params, query_value=nil)
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
  select_options = []
  # local array of options
  if option_type['selectOptions']
    # calculate from inline lambda
    if option_type['selectOptions'].is_a?(Proc)
      select_options = option_type['selectOptions'].call(api_client, api_params || {})
    else
      select_options = option_type['selectOptions']
    end
    # filter options ourselves
    if query_value.to_s != ""
      filtered_options = select_options.select { |it| it['value'].to_s == query_value.to_s }
      if filtered_options.empty?
        filtered_options = select_options.select { |it| it['name'].to_s == query_value.to_s }
      end
      select_options = filtered_options
    end
  elsif option_type['optionSource']
    api_params = api_params.select {|k,v| option_type['params'].include(k)} if !option_type['params'].nil? && api_params

    # calculate from inline lambda
    if option_type['optionSource'].is_a?(Proc)
      select_options = option_type['optionSource'].call(api_client, api_params || {})
    elsif option_type['optionSource'] == 'list'
      # /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
      select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, (api_params || {}).merge({'optionTypeId' => option_type['id']}))
    else
      # remote optionSource aka /api/options/$optionSource?
      select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, api_params || {})
    end
  else
    raise "option '#{help_field_key}' is type: 'typeahead' and missing selectOptions or optionSource!"
  end

  return select_options
end
load_source_options(source,sourceType,api_client,params) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1468
def self.load_source_options(source,sourceType,api_client,params)
  api_client.options.options_for_source("#{sourceType ? "#{sourceType}/" : ''}#{source}", params)['data']
end
multiline_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1152
def self.multiline_prompt(option_type)
  value_found = false
  value = nil
  while !value_found do
    if value.nil?
      print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)} [Type 'EOF' to stop input]: \n"
    end
    input = $stdin.gets.chomp!
    # value = input.empty? ? option_type['defaultValue'] : input
    if input == '?' && value.nil?
      help_prompt(option_type)
    elsif input.chomp == '' && value.nil?
      # just hit enter right away to skip this
      value_found = true
    elsif input.chomp == 'EOF'
      value_found = true
    else
      if value.nil?
        value = ''
      end
      value << input + "\n"
    end
  end
  return value ? value.strip : value
end
multitext_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1325
def self.multitext_prompt(option_type)
  rtn = nil

  # supports multi-part fields via config.fields
  # {"fields": [{"name":"tag", "required":true, "label": "Tag"}, {"name":"value", "required":false, "label": "Scope"}]}
  min_count = option_type['minCount'] || 1
  count = 0

  if option_type['config']['fields']
    while (option_type['required'] && (rtn.empty? || count < min_count)) || self.confirm("Add#{rtn.empty? ? '': ' more'} #{option_type['fieldLabel']}?", {:default => false})
      rtn ||= []
      value = {}
      option_type['config']['fields'].each do |field|
        field_label = field['label'] || field['name'].capitalize
        value[field['name']] = generic_prompt(option_type.merge({'fieldLabel' => min_count > 1 ? "#{field_label} #{count + 1}" : field_label, 'required' => field['required'], 'description' => "#{option_type['fieldLabel']} #{field_label}"}))
      end
      rtn << value
      count += 1
    end
  else
    if rtn = generic_prompt(option_type)
      rtn = [rtn]
      while self.confirm("Add more #{option_type['fieldLabel']}?", {:default => false}) do
        rtn << generic_prompt(option_type)
      end
    end
  end
  rtn
end
no_prompt(option_types, options={}, api_client=nil,api_params={}) click to toggle source

supresses prompting unless –prompt has been passed

# File lib/morpheus/cli/option_types.rb, line 50
def self.no_prompt(option_types, options={}, api_client=nil,api_params={})
  options[:edit_mode] = true # hack used for updates to avoid default values being used
  if options[:always_prompt]
    prompt(option_types, options, api_client, api_params)
  else
    prompt(option_types, options, api_client, api_params, true)
  end
end
number_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 594
def self.number_prompt(option_type)
  value_found = false
  value = nil
  while !value_found do
    print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
    input = $stdin.gets.chomp!
    value = input.empty? ? option_type['defaultValue'] : input
    if !value.to_s.empty?
      value = value.to_s.include?('.') ? value.to_f : value.to_i
    end
    if input == '?'
      help_prompt(option_type)
    elsif !value.nil? || option_type['required'] != true
      value_found = true
    end
  end
  return value
end
optional_label(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1528
def self.optional_label(option_type)
  # removing this for now, for the sake of providing less to look at
  if option_type[:fmt] == :natural # || true
    return ""
  else
    return option_type['required'] ? '' : ' (optional)'
  end
end
password_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1178
def self.password_prompt(option_type)
  value_found = false
  while !value_found do
    # readline is still echoing secret with 'NUL:'' so just use $stdin on windows
    # and some other environments? just use noecho unless running unit tests
    if Morpheus::Cli.windows? || !Morpheus::Cli.testing?
      print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+'************'+']' : ''}: "
      input = $stdin.noecho(&:gets).chomp!
    else
      Readline.completion_append_character = ""
      Readline.basic_word_break_characters = ''
      Readline.completion_proc = nil
      # needs to work like $stdin.noecho
      Readline.pre_input_hook = lambda {
        Readline.output = File.open('/dev/null', 'w')
        #Readline.output = File.open(Morpheus::Cli.windows? ? 'NUL:' : '/dev/null', 'w')
        #$stdout = File.open(Morpheus::Cli.windows? ? 'NUL:' : '/dev/null', 'w')
      }
      password_prompt = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+'************'+']' : ''}: "
      input = Readline.readline(password_prompt, false).to_s.chomp
      Readline.pre_input_hook = nil
      Readline.output = Morpheus::Terminal.instance.stdout #my_terminal.stdout
    end

    value = input
    print "\n"
    if input == '?'
      help_prompt(option_type)
    elsif input == "" && option_type['defaultValue'] != nil
      value = option_type['defaultValue'].to_s
      value_found = true
    elsif !value.empty? || option_type['required'] != true
      value_found = true
    end
  end
  return value
end
prompt(option_types, options={}, api_client=nil, api_params={}, no_prompt=false, paging_enabled=false, ignore_empty=false, skip_sort = false) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 59
def self.prompt(option_types, options={}, api_client=nil, api_params={}, no_prompt=false, paging_enabled=false, ignore_empty=false, skip_sort = false)
  paging_enabled = false if Morpheus::Cli.windows?
  no_prompt = no_prompt || options[:no_prompt]
  results = {}
  options = options || {}

  # inject cli only stuff into option_types (should clone() here)
  option_types.each do |option_type|
    if options[:help_field_prefix]
      option_type[:help_field_prefix] = options[:help_field_prefix]
    end
    # a lot of optionTypes have fieldGroup:'Options' instead of 'default'
    if option_type['fieldGroup'].to_s.downcase == 'options'
      option_type['fieldGroup'] = 'default'
    end
    # apply custom templates
    if option_type['fieldName'] == 'sshHosts'
      option_type['type'] = 'multiText'
    end
    # swap types to multiSelect when flag is set..
    config_multi_select = option_type["config"] && ["true","on"].include?(option_type["config"]["multiSelect"].to_s)
    if config_multi_select
      if option_type["type"] == "typeahead"
        option_type["type"] = "multiTypeahead"
      elsif option_type["type"] == "select"
        option_type["type"] = "multiSelect"
      elsif option_type["type"] == "textarea"
        option_type["type"] = "multiText"
      end
    end
  end

  # puts "Options Prompt #{options}"
  # Sort options by default, group, advanced
  # add displayOrder if it's missing, so it doesn't end up using a random order
  # if !option_types.find {|it| it['displayOrder'] && it['displayOrder'] != 0 }
  #   option_types.each_with_index {|it, i| it['displayOrder'] = i+1 }
  # end
  cur_field_group = 'default'
  prompt_local_credentials = true
  # reject help only options..
  option_types.reject! {|it| it[:for_help_only]}
  # sort options
  if !skip_sort
    option_types = self.sort_option_types(option_types)
  end
  option_types.each do |option_type|
    next if option_type['localCredential'] && !prompt_local_credentials
    context_map = results
    value = nil
    value_found = false

    # allow for mapping of domain to relevant type: domain.zone => router.zone
    option_type['fieldContext'] = (options[:context_map] || {})[option_type['fieldContext']] || option_type['fieldContext']
    field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
    help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
    namespaces = field_key.split(".")
    field_name = namespaces.pop


    # support --no-options --skip-option x,y,z --only-option x,y,z
    if options[:no_options]
      next
    elsif options[:skip_options] && options[:skip_options].find {|it| it.to_s.downcase == option_type['fieldName'].to_s.downcase || it.to_s.downcase == option_type['fieldLabel'].to_s.downcase }
      next
    elsif options[:only_options] && !options[:only_options].find {|it| it.to_s.downcase == option_type['fieldName'].to_s.downcase || it.to_s.downcase == option_type['fieldLabel'].to_s.downcase }
      next
    end            
    

    field_group = (option_type['fieldGroup'] || 'default').to_s.sub(/options\Z/i, "").strip # avoid "ADVANCED OPTION OPTIONS"

    if cur_field_group != field_group
      cur_field_group = field_group
      if !no_prompt && option_type['noPrompt'] != true
        print "\n#{cur_field_group.upcase} OPTIONS\n#{"=" * ("#{cur_field_group} OPTIONS".length)}\n\n"
      end
    end

    # How about this instead?
    # option_type = option_type.clone
    # field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
    # if field_key != ''
    #   value = get_object_value(options, field_key)
    #   if value != nil && options[:always_prompt] != true
    #     value_found = true
    #   end
    # end


    # respect optionType.dependsOnCode
    # i guess this switched to visibleOnCode, respect one or the other
    visible_option_check_value = option_type['dependsOnCode']
    if !option_type['visibleOnCode'].to_s.empty?
      visible_option_check_value = option_type['visibleOnCode']
    end
    # adding a slight hack for dependencies on the network component since its not an option type
    extra_option_params = {}
    if visible_option_check_value == 'networkInterfaces'
      # extra_option_params["networkInterfaceIds[]"] = []
    elsif !visible_option_check_value.to_s.empty?
      match_type = 'any'

      if visible_option_check_value.include?('::')
        match_type = 'all' if visible_option_check_value.start_with?('matchAll')
        visible_option_check_value = visible_option_check_value[visible_option_check_value.index('::') + 2..-1]
      end

      found_dep_value = match_type == 'all' ? true : false
      visible_option_check_value.sub(',', ' ').split(' ').each do |value|
        parts = value.split(':')
        depends_on_code = parts[0]
        depends_on_value = parts.count > 1 ? parts[1].to_s.strip : nil
        depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
        if !depends_on_option_type
          depends_on_option_type = option_types.find {|it|
            (it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
          }
        end

        depends_on_field_key = depends_on_code
        if depends_on_option_type
          depends_on_field_key = depends_on_option_type['fieldContext'].nil? || depends_on_option_type['fieldContext'].empty? ? "#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}"
        end

        field_value = get_object_value(results, depends_on_field_key) ||
                      get_object_value(options, depends_on_field_key) ||
                      get_object_value(api_params, depends_on_field_key)

        if field_value.nil? && !options['_object_key'].nil?
          field_value = get_object_value({options['_object_key'] => results}, depends_on_field_key)
        end

        if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.to_s.match?(depends_on_value))
          found_dep_value = true if match_type != 'all'
        else
          found_dep_value = false if match_type == 'all'
        end
      end
      next if !found_dep_value
    end

    # respect optionType.requireOnCode
    require_option_check_value = option_type['requireOnCode']
    if !require_option_check_value.to_s.empty?
      # require_on_code = check_require_on_code(option_type, option_types, options)

      match_type = 'any'

      if require_option_check_value.include?('::')
        match_type = 'all' if require_option_check_value.start_with?('matchAll')
        require_option_check_value = require_option_check_value[require_option_check_value.index('::') + 2..-1]
      end

      found_dep_value = match_type == 'all' ? true : false
      require_option_check_value.sub(',', ' ').split(' ').each do |value|
        parts = value.split(':')
        depends_on_code = parts[0]
        depends_on_value = parts.count > 1 ? parts[1].to_s.strip : nil
        depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
        if !depends_on_option_type
          depends_on_option_type = option_types.find {|it|
            (it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
          }
        end

        depends_on_field_key = depends_on_code
        if depends_on_option_type
          depends_on_field_key = depends_on_option_type['fieldContext'].nil? || depends_on_option_type['fieldContext'].empty? ? "#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}"
        end

        field_value = get_object_value(results, depends_on_field_key) ||
                      get_object_value(options, depends_on_field_key) ||
                      get_object_value(api_params, depends_on_field_key)

        if field_value.nil? && !options['_object_key'].nil?
          field_value = get_object_value({options['_object_key'] => results}, depends_on_field_key)
        end

        if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.to_s.match?(depends_on_value))
          found_dep_value = true if match_type != 'all'
        else
          found_dep_value = false if match_type == 'all'
        end
      end
       
      option_type = option_type.merge({'required' => found_dep_value})
    end

    # build parameters for option source api request
    option_params = (option_type['noParams'] ? {} : (api_params || {}).deep_merge(results))
    option_params.merge!(option_type['optionParams']) if option_type['optionParams']
    # option_params.merge!(extra_option_params) if extra_option_params && !extra_option_params.empty?

    cur_namespace = options
    parent_context_map = context_map
    parent_ns = field_name

    namespaces.each do |ns|
      next if ns.empty?
      parent_context_map = context_map
      parent_ns = ns
      cur_namespace[ns.to_s] ||= {}
      cur_namespace = cur_namespace[ns.to_s]
      context_map[ns.to_s] ||= {}
      context_map = context_map[ns.to_s]
    end

    # CLI only options that need some do some inflection to decide how to prompt
    # defaultValue is it right now..
    if option_type[:preprocesser].is_a?(Proc)
      option_type[:preprocesser].call(option_type, api_client, option_params)
    end

    # credential type
    handle_credential_type = -> {
      credential_type = select_prompt(option_type.merge({'defaultValue' => value}), api_client, option_params.merge({'credentialTypes' => option_type['config']['credentialTypes']}), !value.nil?, nil, paging_enabled, ignore_empty, options[:edit_mode])
      # continue prompting for local creds
      if credential_type == 'local'
        parent_context_map.reject! {|k,v| k == 'credential'}
        next
      end
      # hide local cred options
      prompt_local_credentials = false
      if credential_type.is_a?(Numeric)
        # set as credential.id
        credential = {'id' => credential_type}
      else
        # prompt credential type options
        credential = prompt(api_client.credential_types.list({name:credential_type})['credentialTypes'][0]['optionTypes'], options, api_client, option_params, options[:no_prompt], paging_enabled, ignore_empty)['credential']
        credential['type'] = credential_type
      end
      parent_context_map['credential'] = credential
    }

    # use the value passed in the options map
    if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
      value = cur_namespace[field_name]
      input_value = ['select', 'multiSelect','typeahead', 'multiTypeahead'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
      if option_type['type'] == 'number'
        if !value.to_s.empty?
          value = value.to_s.include?('.') ? value.to_f : value.to_i
        end
      # these select prompts should just fall down through below, with the extra params no_prompt, use_value
      elsif option_type['type'] == 'select'
        value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
      elsif option_type['type'] == 'multiSelect'
        # support value as csv like "thing1, thing2"
        value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
        input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
        select_value_list = []
        value_list.each_with_index do |v, i|
          select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
        end
        value = select_value_list
      elsif option_type['type'] == 'typeahead'
        value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
      elsif option_type['type'] == 'multiTypeahead'
        # support value as csv like "thing1, thing2"
        value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
        input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
        select_value_list = []
        value_list.each_with_index do |v, i|
          select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
        end
        value = select_value_list
      elsif option_type['type'] == 'credential'
        handle_credential_type.call
      end
      if options[:always_prompt] != true
        value_found = true
      end
    end

    # set the value that has been passed to the option type default value
    if value != nil # && value != ''
      option_type = option_type.clone
      option_type['defaultValue'] = value
    end
    # no_prompt means skip prompting and instead
    # use default value or error if a required option is not present
    if no_prompt || option_type['noPrompt'] == true
      if !value_found
        if option_type['defaultValue'] != nil && !['select', 'multiSelect','typeahead','multiTypeahead'].include?(option_type['type'])
          value = option_type['defaultValue']
          value_found = true
        end
        if !value_found
          # select type is special because it supports skipSingleOption
          # and prints the available options on error
          if option_type['type'] == 'azureMarketplace'
            value = azure_marketplace_prompt(option_type, options, api_client, option_params)
            # inject {marketplacePublisher:'...',} into config, not as config.azureMarketplace = {}
            # and remove any passed in values from
            if value.is_a?(Hash)
              context_map.merge!(value)
            end
            next
          end
          if ['select', 'multiSelect'].include?(option_type['type'])
            value = select_prompt(option_type, api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
            value_found = !!value
          end
          if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
            value = typeahead_prompt(option_type, api_client, option_params, true)
            value_found = !!value
          end
          if option_type['type'] == 'hidden'
            if option_type['optionSource'].nil?
              value = option_type['defaultValue']
            else
              select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, option_params)
              config_multi_select = option_type["config"] && ["true","on"].include?(option_type["config"]["multiSelect"].to_s)
              if config_multi_select
                value = select_options.collect { |it| it['value'] }
              elsif select_options.is_a?(Array)
                value = select_options[0] ? select_options[0]['value'] : nil
              elsif select_options.is_a?(Hash)
                value = select_options['value']
              else
                value = select_options
              end
            end
          end
          if !value_found && !ignore_empty
            if option_type['required']
              print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
              print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
              print "\n"
              exit 1
            else
              parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
              next
            end
          end
        end
      end
    end

    if !value_found
      if option_type['type'] == 'text'
        value = generic_prompt(option_type)
      elsif option_type['type'] == 'number'
        value = number_prompt(option_type)
      elsif option_type['type'] == 'password'
        value = password_prompt(option_type)
      elsif option_type['type'] == 'checkbox'
        value = checkbox_prompt(option_type)
        if options[:checkbox_as_boolean]
          value = (value == 'on')
        end
      elsif option_type['type'] == 'radio'
        value = radio_prompt(option_type)
      elsif option_type['type'] == 'textarea'
        value = multiline_prompt(option_type)
      elsif option_type['type'] == 'code-editor'
        value = multiline_prompt(option_type)
      elsif option_type['type'] == 'credential'
        print "\nCREDENTIALS\n#{"=" * ("CREDENTIALS".length)}\n\n"
        handle_credential_type.call
      elsif ['select', 'multiSelect'].include?(option_type['type'])
        # so, the /api/options/source is may need ALL the previously
        # selected values that are being accumulated in options
        # api_params is just extra params to always send
        # I suppose the entered value should take precedence
        # api_params = api_params.merge(options) # this might be good enough
        # dup it
        value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty, options[:edit_mode])
        if value && option_type['type'] == 'multiSelect'
          value = [value]
          recommended_count = (option_type['config'] || {})['recommendedCount'] || 0
          while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => recommended_count > value.count}) do
            if addn_value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled, ignore_empty, options[:edit_mode])
              value << addn_value
            else
              break
            end
          end
        end
      elsif ['typeahead', 'multiTypeahead'].include?(option_type['type'])
        value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
        if value && option_type['type'] == 'multiTypeahead'
          value = [value]
          while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
            if addn_value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
              value << addn_value
            else
              break
            end
          end
        end
      elsif option_type['type'] == 'hidden'
        if option_type['optionSource'].nil?
          value = option_type['defaultValue']
        else
          select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, option_params)
          config_multi_select = option_type["config"] && ["true","on"].include?(option_type["config"]["multiSelect"].to_s)
          if config_multi_select
            value = select_options.collect { |it| it['value'] }
          elsif select_options.is_a?(Array)
            value = select_options[0] ? select_options[0]['value'] : nil
          elsif select_options.is_a?(Hash)
            value = select_options['value']
          else
            value = select_options
          end
        end
      elsif option_type['type'] == 'file'
        value = file_prompt(option_type)
      elsif option_type['type'] == 'file-content' || option_type['type'] == 'fileContent'
        value = file_content_prompt(option_type, options, api_client, {})
      elsif option_type['type'] == 'logoSelector'
        value = file_prompt(option_type)
      elsif option_type['type'] == 'multiText'
        value = multitext_prompt(option_type)
      elsif option_type['type'] == 'azureMarketplace'
        value = azure_marketplace_prompt(option_type, options, api_client, option_params)
        # inject {marketplacePublisher:'...',} into config, not as config.azureMarketplace = {}
        # and remove any passed in values from
        if value.is_a?(Hash)
          context_map.merge!(value)
        end
        next
      else
        value = generic_prompt(option_type)
      end
    end
    # --labels x,y,z uses processValue proc to convert strings to an array
    if option_type['processValue'].is_a?(Proc)
      value = option_type['processValue'].call(value)
    end

    if option_type['type'] == 'multiSelect'
      value = [value] if !value.nil? && !value.is_a?(Array)
    elsif option_type['type'] == 'multiText'
      # multiText expects csv value
      if value && value.is_a?(String)
        value = value.split(",").collect {|it| it.strip }
      end
    # todo: Handle these types added with the new form fields:
    #
    # byteSize
    # code-editor
    # fileContent
    # logoSelector
    # keyValue
    # textArray
    # typeahead
    # group
    # cloud
    # environment
    # diskManager
    # layout
    # networkManager
    # plan
    # resourcePool
    # secGroup
    # tag
    # httpHeader
    elsif option_type['type'] == 'byteSize'
      if value.to_s.empty?
        value = 0 # nil
      elsif value.is_a?(String)
        if value.to_s.upcase.include?("G")
          value = value.to_i * 1024 * 1024 * 1024
        elsif value.to_s.upcase.include?("M")
          value = value * 1024 * 1024
        else
          # assume bytes by default..
          value = value.to_i
        end
      end
    elsif option_type['type'] == 'keyValue'
      value = try_as_json(value)
      if value.is_a?(String)
        map = {}
        value.split(",").each do |it| 
          pair = it.split("="); 
          map[pair[0].to_s.strip] = pair[1..-1].join("=").strip
        end
        value = map
      end
    elsif option_type['type'] == 'textArray'
      value = try_as_json(value)
      if value.is_a?(String)
        value = value.split(",").collect {|it| it.to_s.strip }
      end
    else
      # default translation
      # for non text inputs, try to parse value as JSON
      # if option_type['type'] == 'group' || option_type['type'] == 'cloud' etc..
      if value.is_a?(String) && option_type['type'] != 'text'
        value = try_as_json(value)
      end
    end
    context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
    parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
  end
  results
end
radio_prompt(option_type) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 560
def self.radio_prompt(option_type)
  value_found = false
  value = nil
  options = []
  if option_type['config'] and option_type['config']['radioOptions']
    option_type['config']['radioOptions'].each do |radio_option|
      options << {key: radio_option['key'], checked: radio_option['checked']}
    end
  end
  optionString = options.collect{ |b| b[:checked] ? "(#{b[:key]})" : b[:key]}.join(', ')
  while !value_found do
    print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
    input = $stdin.gets.chomp!
    if input == '?'
      help_prompt(option_type)
    else
      if input.nil? || input.empty?
        selectedOption = options.find{|o| o[:checked] == true}
      else
        selectedOption = options.find{|o| o[:key].downcase == input.downcase}
      end
      if selectedOption
        value = selectedOption[:key]
      else
        puts "Invalid Option. Please select from #{optionString}."
      end
      if !value.nil? || option_type['required'] != true
        value_found = true
      end
    end
  end
  return value
end
select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false, ignore_empty=false, edit_mode=false) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 621
def self.select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false, ignore_empty=false, edit_mode=false)
  paging_enabled = false if Morpheus::Cli.windows?
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
  value_found = false
  value = nil
  value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
  default_value = option_type['defaultValue']
  default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?

  if !option_type['params'].nil?
    api_params = (api_params || {}).select {|k,v| option_type['params'].key?(k) || option_type['params'].key?(k.to_s)}
    option_type['params'].select {|k,v| !v.empty?}.each {|k,v| api_params[k] = v}
  end

  # local array of options
  if option_type['selectOptions']
    # calculate from inline lambda
    if option_type['selectOptions'].is_a?(Proc)
      select_options = option_type['selectOptions'].call(api_client, api_params || {})
    else
      # todo: better type validation
      select_options = option_type['selectOptions']
    end
  elsif option_type['optionSource']
    # calculate from inline lambda
    if option_type['optionSource'].is_a?(Proc)
      select_options = option_type['optionSource'].call(api_client, api_params || {})
    elsif option_type['optionSource'] == 'list'
      # /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
      select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, (api_params || {}).merge({'optionTypeId' => option_type['id']}))
    else
      # remote optionSource aka /api/options/$optionSource?
      select_options = load_source_options(option_type['optionSource'], option_type['optionSourceType'], api_client, api_params || {})
    end
  else
    raise "option '#{help_field_key}' is type: 'select' and missing selectOptions or optionSource!"
  end

  return nil if (select_options.nil? || select_options.count == 0) && ignore_empty

  # ensure the preselected value (passed as an option) is in the dropdown
  if !use_value.nil?
    matched_option = select_options.find {|it| it[value_field].to_s == use_value.to_s }
    if matched_option.nil?
      matched_options = select_options.select {|it| opt['name'].to_s == use_value.to_s }
      if matched_options.size > 1
        print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{use_value}]\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
        if matched_options && matched_options.size > 10
          display_select_options(option_type, matched_options.first(10))
          puts " (#{matched_options.size-10} more)"
        else
          display_select_options(option_type, matched_options)
        end
        print "The value '#{input}' matched #{matched_options.size()} options.\n"
        # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
        print "Try using value instead of name.\n"
        print "\n"
        exit 1
      elsif matched_options.size == 1
        matched_option = matched_options[0]
      end
    end
    if !matched_option.nil?
      value = matched_option[value_field]
      value_found = true
    else
      print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{use_value}]\n\n", Term::ANSIColor.reset
      print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
      if select_options && select_options.size > 10
        display_select_options(option_type, select_options.first(10))
        puts " (#{select_options.size-10} more)"
      else
        display_select_options(option_type, select_options)
      end
      print "\n"
      exit 1
    end
  elsif edit_mode
    # do not use a default value for edit mode
  # skipSingleOption is no longer supported
  # elsif !select_options.nil? && select_options.count == 1 && option_type['skipSingleOption'] == true
  #   value_found = true
  #   value = select_options[0]['value']
  # if there is just one option, use it as the defaultValue
  elsif !select_options.nil? && select_options.count == 1
    if option_type['required'] && default_value.nil?
      default_value = select_options[0]['name'] # name is prettier than value
    end
  elsif !select_options.nil?
    if default_value.nil?
      found_default_option = select_options.find {|opt| opt['isDefault'] == true }
      if found_default_option
        default_value = found_default_option['name'] # name is prettier than value
      end
    else
      found_default_option = select_options.find {|opt| opt[value_field].to_s == default_value.to_s || opt['name'] == default_value.to_s}
      found_default_option = select_options.find {|opt| opt[value_field].to_s.start_with?(default_value.to_s) || opt['name'].to_s.start_with?(default_value.to_s)} if !found_default_option
      if found_default_option
        default_value = found_default_option['name'] # name is prettier than value
      end
    end
  end
  # default to the first option
  if !value_found && default_value.nil? && option_type['defaultFirstOption'] && select_options && select_options[0]
    # default_value = select_options[0][value_field]
    # nicer to display name instead, it will match and replace with value
    default_value = select_options[0]['name'] ? select_options[0]['name'] : select_options[0][value_field]
  end

  if no_prompt
    if !value_found
      if default_value == ""
        default_value = nil
      end
      if !default_value.nil? && !select_options.nil?
        matched_option = select_options.find {|it| it[value_field].to_s == default_value.to_s }
        if matched_option.nil?
          matched_options = select_options.select {|it| it['name'].to_s == default_value.to_s }
          if matched_options.size > 1
            print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{default_value}]\n\n", Term::ANSIColor.reset
            print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
            if matched_options && matched_options.size > 10
              display_select_options(option_type, matched_options.first(10))
              puts " (#{matched_options.size-10} more)"
            else
              display_select_options(option_type, matched_options)
            end
            print "The value '#{default_value}' matched #{matched_options.size()} options.\n"
            # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
            print "Try using value instead of name.\n"
            print "\n"
            exit 1
          elsif matched_options.size == 1
            matched_option = matched_options[0]
          end
        end
        if !matched_option.nil?
          value = matched_option[value_field]
          value_found = true
        else
          print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{default_value}]\n\n", Term::ANSIColor.reset
          print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
          if select_options && select_options.size > 10
            display_select_options(option_type, select_options.first(10))
            puts " (#{select_options.size-10} more)"
          else
            display_select_options(option_type, select_options)
          end
          print "\n"
          exit 1
        end
      elsif !select_options.nil? && select_options.count > 0 && option_type['autoPickOption'] == true
        value_found = true
        value = select_options[0][value_field]
      elsif option_type['required']
        print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
        if select_options && select_options.size > 10
          display_select_options(option_type, select_options.first(10))
          puts " (#{select_options.size-10} more)"
        else
          display_select_options(option_type, select_options)
        end
        print "\n"
        exit 1
      else
        return nil
      end
    end
  end

  paging = nil
  if paging_enabled
    option_count = select_options ? select_options.count : 0
    page_size = Readline.get_screen_size[0] - 6
    if page_size < option_count
      paging = {:cur_page => 0, :page_size => page_size, :total => option_count}
    end
  end

  while !value_found do
    #Readline.input = $stdin
    Readline.completion_append_character = ""
    Readline.basic_word_break_characters = ''
    Readline.completion_proc = proc {|s| 
      matches = []
      available_options = (select_options || [])
      available_options.each{|option| 
        if option['name'] && option['name'] =~ /^#{Regexp.escape(s)}/
          matches << option['name']
        # elsif option['id'] && option['id'].to_s =~ /^#{Regexp.escape(s)}/
        elsif option[value_field] && option[value_field].to_s == s
          matches << option['name']
        end
      }
      matches
    }

    has_more_pages = paging && (paging[:cur_page] * paging[:page_size]) < paging[:total]
    input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages && paging[:cur_page] > 0 ? ' more ' : ' '}options]: ", false).to_s
    input = input.chomp.strip
    if input.empty? && default_value
      input = default_value.to_s
    end
    matched_option = (select_options || []).find{|it| (!it['value'].nil? && it['value'].to_s == input) || (!it[value_field].nil? && it[value_field].to_s == input) || (it[value_field].nil? && input.empty?)}
    if matched_option.nil?
      matched_options = (select_options || []).select {|it| it['name'] == input } # should probably be case insensitive
      if matched_options.size > 1
        print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{input}]\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
        if matched_options && matched_options.size > 10
          display_select_options(option_type, matched_options.first(10))
          puts " (#{matched_options.size-10} more)"
        else
          display_select_options(option_type, matched_options)
        end
        print "The value '#{input}' matched #{matched_options.size()} options.\n"
        # print "Perhaps you meant one of these? #{ored_list(matched_options.collect {|i|i[value_field]}, 3)}\n"
        print "Try using value instead of name.\n"
        print "\n"
        #exit 1
      elsif matched_options.size == 1
        matched_option = matched_options[0]
      end
    end

    if matched_option
      value = matched_option[value_field]
      set_last_select(matched_option)
    elsif !input.nil?  && !input.to_s.empty?
      input = '?'
    end
    
    if input == '?'
      help_prompt(option_type)
      display_select_options(option_type, (select_options || []), paging)
      if paging
        paging[:cur_page] = (paging[:cur_page] + 1) * paging[:page_size] < paging[:total] ? paging[:cur_page] + 1 : 0
      end
    elsif !value.nil? || option_type['required'] != true
      value_found = true
    end
  end

  # wrap in object when using fieldInput
  if value && !option_type['fieldInput'].nil?
    value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
  end

  if value && !option_type['resultValueField'].nil?
    value = {option_type['resultValueField'] => value}
  end
  value
end
set_last_select(obj) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 613
def self.set_last_select(obj)
  Thread.current[:_last_select] = obj
end
sort_option_types(option_types) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1492
def self.sort_option_types(option_types)
  if option_types.find {|it| it['fieldGroup'] || it['displayOrder'] }
    option_types.select {|it| (it['fieldGroup'] || 'default').casecmp?('default')}.sort {|a,b| a['displayOrder'].to_i <=> b['displayOrder'].to_i} +
    option_types.reject {|it| ['default', 'advanced'].include?((it['fieldGroup'] || 'default').downcase)}.sort{|a,b| a['displayOrder'] <=> b['displayOrder']}.group_by{|it| it['fieldGroup']}.values.collect { |it| it.sort{|a,b| a['displayOrder'].to_i <=> b['displayOrder'].to_i}}.flatten +
    option_types.select {|it| 'advanced'.casecmp?(it['fieldGroup'])}.sort {|a,b| a['displayOrder'].to_i <=> b['displayOrder'].to_i}
  else
    option_types
  end
end
try_as_json(val) click to toggle source
# File lib/morpheus/cli/option_types.rb, line 1562
def self.try_as_json(val)
  if val.is_a?(String)
    if (val.to_s[0] == '{' && val.to_s[-1] == '}') || (val.to_s[0] == '[' && val.to_s[-1] == ']')
      begin
        val = JSON.parse(val)
      rescue
        Morpheus::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if Morpheus::Logging.debug?
      end
    end
  end
  return val
end
typeahead_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false) click to toggle source

this works like select_prompt, but refreshes options with ?query=value between inputs paging_enabled is ignored right now

# File lib/morpheus/cli/option_types.rb, line 880
def self.typeahead_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
  paging_enabled = false if Morpheus::Cli.windows?
  paging = nil
  select_options = nil
  field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
  help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
  input = ""
  value_found = false
  value = nil
  value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
  default_value = option_type['defaultValue']
  default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?

  while !value_found do
    # ok get input, refresh options and see if it matches
    # if matches one, cool otherwise print matches and reprompt or error
    if use_value
      input = use_value
    elsif no_prompt
      input = default_value
    else
      #Readline.input = $stdin
      Readline.completion_append_character = ""
      Readline.basic_word_break_characters = ''
      Readline.completion_proc = proc {|s| 
        matches = []
        available_options = (select_options || [])
        available_options.each{|option| 
          if option['name'] && option['name'] =~ /^#{Regexp.escape(s)}/
            matches << option['name']
          # elsif option['id'] && option['id'].to_s =~ /^#{Regexp.escape(s)}/
          elsif option[value_field] && option[value_field].to_s == s
            matches << option['name']
          end
        }
        matches
      }
      # prompt for typeahead input value
      has_more_pages = paging && ((paging[:cur_page] + 1) * paging[:page_size]) < paging[:total]
      input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages ? ' more ' : ' '}options]: ", false).to_s
      input = input.chomp.strip
    end

    # just hit enter, use [default] if set
    if input.empty? && default_value
      input = default_value.to_s
    end
    
    # not required and no value? ok proceed
    if input.to_s == "" && option_type['required'] != true
      value_found = true
      value = nil # or "" # hmm
      #next
      break
    end

    # required and no value? you need help
    # if input.to_s == "" && option_type['required'] == true
    #   help_prompt(option_type)
    #   display_select_options(option_type, select_options) unless select_options.empty?
    #   next
    # end

    # looking for help with this input
    if input == '?'
      help_prompt(option_type)
      select_options = select_options || load_options(option_type, api_client, api_params)

      if !select_options.empty?
        if paging_enabled
          if paging.nil?
            option_count = select_options ? select_options.count : 0
            page_size = Readline.get_screen_size[0] - 6
            if page_size < option_count
              paging = {:cur_page => 0, :page_size => page_size, :total => option_count}
            end
          else
            paging[:cur_page] = (paging[:cur_page] + 1) * paging[:page_size] < paging[:total] ? paging[:cur_page] + 1 : 0
          end
        end
        display_select_options(option_type, select_options, paging)
      end
      next
    end

    # just hit enter? scram
    # looking for help with this input
    # if input == ""
    #   help_prompt(option_type)
    #   display_select_options(option_type, select_options)
    #   next
    # end

    # this is how typeahead works, it keeps refreshing the options with a new ?query={value}
    # query_value = (value || use_value || default_value || '')
    query_value = (input || '')
    api_params ||= {}
    api_params['query'] = query_value
    # skip refresh if you just hit enter
    if !query_value.empty? || (select_options.nil? || select_options.empty?)
      select_options = load_options(option_type, api_client, api_params, query_value)
    end

    # match input to option name or value
    # actually that is redundant, it should already be filtered to matches
    # and can just do this:
    # select_option = select_options.size == 1 ? select_options[0] : nil
    select_option = select_options.find{|b| (b[value_field] && (b[value_field].to_s == input.to_s)) || ((b[value_field].nil? || b[value_field] == "") && (input == "")) }
    if select_option.nil?
      select_option = select_options.find{|b| b['name'] && b['name'] == input }
    end

    # found matching value, else did not find a value, show matching options and prompt again or error
    if select_option
      value = select_option[value_field]
      set_last_select(select_option)
      value_found = true
    else
      if use_value || no_prompt
        # todo: make this nicer
        # help_prompt(option_type)
        print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
        print Term::ANSIColor.red, "  * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
        if select_options && select_options.size > 10
          display_select_options(option_type, select_options.first(10))
          puts " (#{select_options.size-10} more)"
        else
          display_select_options(option_type, select_options)
        end
        print "\n"
        if select_options.empty?
          print "The value '#{input}' matched 0 options.\n"
          # print "Please try again.\n"
        elsif select_options.size() == 1
          print "The value '#{input}' matched 1 option.\n"
          print "Perhaps you meant '#{select_options[0]['name']}' instead?"
          # print "Please try again.\n"
        else
          print "The value '#{input}' matched #{select_options.size()} options.\n"
          print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
          # print "Please try again.\n"
        end
        print "\n"
        exit 1
      else
        #help_prompt(option_type)
        if select_options && select_options.size > 10
          display_select_options(option_type, select_options.first(10))
          puts " (#{select_options.size-10} more)"
        else
          display_select_options(option_type, select_options)
        end
        print "\n"
        if select_options.empty?
          print "The value '#{input}' matched 0 options.\n"
          print "Please try again.\n"
        elsif select_options.size() == 1
          print "The value '#{input}' matched 1 option.\n"
          print "Perhaps you meant '#{select_options[0]['name']}' instead?"
        else
          print "The value '#{input}' matched #{select_options.size()} options.\n"
          print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
          print "Please try again.\n"
        end
        print "\n"
        # reprompting now...
      end
    end
  end # end while !value_found

  # wrap in object when using fieldInput
  if value && !option_type['fieldInput'].nil?
    value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
  end
  value
end