Module: Xolo::Admin::Interactive

Defined in:
lib/xolo/admin/interactive.rb

Overview

Module for gathering and validating xadm options from an interactive terminal session

Constant Summary collapse

MULTILINE_EDITORS =

Constants

{
  'vim (vi)' => '/usr/bin/vim',
  'mg (emacs)' => '/usr/bin/mg',
  'pico (nano)' => '/usr/bin/pico'
}.freeze
MULTILINE_HEADER_SEPARATOR =
"\nDO NOT EDIT anything above the next line:\n=================================="
DEFAULT_HIGHLINE_READLINE_PROMPT =
'Enter value'
HIGHLINE_READLINE_GATHER_ERR_INSTRUCTIONS =
<<~ENDINSTR
  Use tab for auto-completion, tab twice to see available choices
  Type '#{Xolo::X}' to exit."
ENDINSTR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#last_converted_valueObject

getter/setter for the value converted by the last validation method call - we do this so the same value is available in the convert and validate lambdas

Returns:

  • (Object)


774
775
776
# File 'lib/xolo/admin/interactive.rb', line 774

def last_converted_value
  @last_converted_value
end

#last_validation_errorString

getter/setter for any validation error message when a validation fails.

Returns:

  • (String)


780
781
782
# File 'lib/xolo/admin/interactive.rb', line 780

def last_validation_error
  @last_validation_error
end

Class Method Details

.included(includer) ⇒ Object

when this module is included



40
41
42
# File 'lib/xolo/admin/interactive.rb', line 40

def self.included(includer)
  Xolo.verbose_include includer, self
end

Instance Method Details

#app_name_bundleid_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



191
192
193
194
195
# File 'lib/xolo/admin/interactive.rb', line 191

def app_name_bundleid_na
  return unless walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:subscribed]

  'N/A when using Version Script, or subcribing via a Patch Source'
end

#auto_release_delay_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



221
222
223
224
225
# File 'lib/xolo/admin/interactive.rb', line 221

def auto_release_delay_na
  return if walkthru_cmd_opts[:subscribed] && !walkthru_cmd_opts[:autopkg_recipe].pix_empty?

  'N/A unless title is Subscribed and uses an AutoPkg Recipe'
end

#default_for_value(key, deets, curr_val) ⇒ Object

The 'default' value for the highline question when prompting for a value TODO: POSSIBLY NOT NEEDED. See commented-out call above



657
658
659
660
661
662
663
# File 'lib/xolo/admin/interactive.rb', line 657

def default_for_value(key, deets, curr_val)
  # default is the current value, or the
  # defined value if no current.
  default = walkthru_cmd_opts[key] || curr_val || deets[:default]
  default = default.join(Xolo::COMMA_JOIN) if default.is_a? Array
  default
end

#display_name_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



162
163
164
165
166
# File 'lib/xolo/admin/interactive.rb', line 162

def display_name_na
  return unless walkthru_cmd_opts[:subscribed]

  'N/A when subcribing via a Patch Source'
end

#display_walkthru_headervoid

This method returns an undefined value.

Clear the terminal window and display the menu header above the highline menu



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/xolo/admin/interactive.rb', line 287

def display_walkthru_header
  header_text = Xolo::Admin::Options::COMMANDS[cli_cmd.command][:walkthru_header].dup
  return unless header_text

  header_text.sub! Xolo::Admin::Options::TARGET_TITLE_PLACEHOLDER, cli_cmd.title if cli_cmd.title
  header_text.sub! Xolo::Admin::Options::TARGET_VERSION_PLACEHOLDER, cli_cmd.version if cli_cmd.version
  if edit_command?
    header_text <<
      if current_opt_values[:subscribed]
        " (subscribed as '#{current_opt_values[:display_name]}')"
      else
        ' (managed)'
      end
  end
  header_sep_line = Xolo::DASH * header_text.length

  system 'clear'
  puts <<~ENDPUTS
    #{header_sep_line}
    #{header_text}
    #{header_sep_line}
    Current Settings -> New Settings

  ENDPUTS
end

#display_walkthru_menuvoid

This method returns an undefined value.

Build and display the walkthru menu for the given command



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/xolo/admin/interactive.rb', line 80

def display_walkthru_menu
  cmd = cli_cmd.command
  done_with_menu = false

  # we start off with our walkthru_cmd_opts being the same
  # the same as current_opt_values
  current_opt_values.to_h.each { |k, v| walkthru_cmd_opts[k] = v }

  until done_with_menu
    # clear the screen and show the menu header
    display_walkthru_header

    # Generate the menu items
    highline_cli.choose do |menu|
      menu.select_by = :index

      menu.responses[:ambiguous_completion] = nil
      menu.responses[:no_completion] = 'Unknown Choice'

      # The menu items for setting values
      cmd_details(cmd)[:opts].each do |key, deets|
        # dont displau items for the other kind of action
        next if deets[:add_only] && edit_command?
        next if deets[:edit_only] && add_command?

        # only show items for the current title type, if applicable
        if deets[:title_type]
          if walkthru_cmd_opts[:subscribed]
            next if deets[:title_type] == Xolo::MANAGED
          elsif deets[:title_type] == Xolo::SUBSCRIBED
            next
          end
        end

        curr_val = current_opt_values[key]

        not_avail = send(deets[:walkthru_na]) if deets[:walkthru_na]

        # if a value is not available, remove any previously set value
        walkthru_cmd_opts.delete_field(key) if not_avail && walkthru_cmd_opts.to_h.key?(key)

        new_val = walkthru_cmd_opts[key]

        menu_item = menu_item_text(deets[:label], oldval: curr_val, newval: new_val, not_avail: not_avail)

        # no processing if item not available
        if not_avail
          menu.choice(menu_item) {}
        else
          menu.choice(menu_item) { prompt_for_walkthru_value key, deets, curr_val }
        end
      end

      # always show 'Cancel' in the same position
      menu.choice(Xolo::CANCEL) do
        done_with_menu = true
        @walkthru_cancelled = true
      end

      # check for any required values missing or if
      # there's internal inconsistency between given values
      still_needed = missing_values
      consistency_error = internal_consistency_error

      # only show 'done' when all required values are there and
      # consistency is OK
      menu.choice(nil, nil, 'Done') { done_with_menu = true } if still_needed.empty? && consistency_error.nil?

      # The prompt will include info about required values and consistency
      prompt = Xolo::BLANK
      prompt = "#{prompt}\n- Missing: #{still_needed.join Xolo::COMMA_JOIN}" unless still_needed.empty?
      prompt = "#{prompt}\n- #{consistency_error}" if consistency_error
      prompt = "#{prompt}\nYour Choice: "
      menu.prompt = prompt
    end

  end # until done with menu
end

#do_walkthruvoid

This method returns an undefined value.

Use an interactive walkthru session to populate Xolo::Admin::Options.walkthru_cmd_opts



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/xolo/admin/interactive.rb', line 63

def do_walkthru
  return unless walkthru?

  # only one readline thing per line
  Readline.completion_append_character = nil
  # all chars are allowed in readline choices
  Readline.basic_word_break_characters = "\n"

  # if the command doesn't take any options, there's nothing to walk through
  return if Xolo::Admin::Options::COMMANDS[cli_cmd.command][:opts].empty?

  display_walkthru_menu
end

#edited_multiline_value(editor, desc, text_to_edit) ⇒ String

Save some text in a temp file, edit it with the desired multiline editor, save it then return the edited value.

Parameters:

  • editor (String, Pathname)

    The path to the editor to use

  • text_to_edit (String)

    The text to edit

Returns:

  • (String)

    the edited text.



890
891
892
893
894
895
896
# File 'lib/xolo/admin/interactive.rb', line 890

def edited_multiline_value(editor, desc, text_to_edit)
  f = Pathname.new(Tempfile.new('xadm-multiline'))
  editor_content = "#{desc.chomp}\n#{MULTILINE_HEADER_SEPARATOR}\n#{text_to_edit}"
  f.pix_save editor_content
  system "#{editor} #{f}"
  f.read.split(MULTILINE_HEADER_SEPARATOR).last
end

#expiration_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



253
254
255
256
257
# File 'lib/xolo/admin/interactive.rb', line 253

def expiration_na
  no_uninstall_script = walkthru_cmd_opts[:uninstall_script].pix_empty?
  no_uninstall_ids = walkthru_cmd_opts[:uninstall_ids].pix_empty?
  'N/A until either uninstall_script or uninstall-ids are set' if no_uninstall_script && no_uninstall_ids
end

#expiration_paths_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



262
263
264
# File 'lib/xolo/admin/interactive.rb', line 262

def expiration_paths_na
  'N/A unless expiration days is > 0' unless walkthru_cmd_opts[:expiration].to_i.positive?
end

#highline_cliHighline

Returns Our HighLine instance. Word wrap at terminal-width minus 5.

Returns:

  • (Highline)

    Our HighLine instance. Word wrap at terminal-width minus 5



51
52
53
54
55
56
57
# File 'lib/xolo/admin/interactive.rb', line 51

def highline_cli
  return @highline_cli if @highline_cli

  @highline_cli ||= HighLine.new
  @highline_cli.wrap_at = terminal_word_wrap if STDOUT.tty?
  @highline_cli
end

#internal_consistency_errorString?

Returns any current internal consistency error. will be nil when none remain.

Returns:

  • (String, nil)

    any current internal consistency error. will be nil when none remain



277
278
279
280
281
282
# File 'lib/xolo/admin/interactive.rb', line 277

def internal_consistency_error
  validate_internal_consistency walkthru_cmd_opts
  nil
rescue Xolo::InvalidDataError => e
  e.to_s
end

Returns the menu item text.

Parameters:

  • lbl (String)

    the label to use at the start of the text e.g. 'Description'

  • oldval (Object) (defaults to: nil)

    the original value before we started doing whatever we are doing

  • newval (Object) (defaults to: nil)

    the latest value that was entered by the user

  • not_avail (String) (defaults to: nil)

    if the menu item is unavailable, this expalins why.

Returns:

  • (String)

    the menu item text



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/xolo/admin/interactive.rb', line 322

def menu_item_text(lbl, oldval: nil, newval: nil, not_avail: nil)
  oldval = oldval.join(Xolo::COMMA_JOIN) if oldval.is_a? Array
  newval = newval.join(Xolo::COMMA_JOIN) if newval.is_a? Array

  txt = "#{lbl}:"
  return "#{txt} ** #{not_avail}" if not_avail

  txt = "#{lbl}: #{oldval}"
  txt = "#{lbl}:\n#{oldval}\n" if txt.length >= terminal_word_wrap
  return txt if oldval == newval

  txt = "#{lbl}: #{oldval} -> #{newval}"
  txt = "#{lbl}:\n#{oldval}\n  ->\n#{newval}\n" if txt.length >= terminal_word_wrap
  txt
end

#missing_valuesArray<String>

Returns The names of any required opts that have no current value. Displayed at the bottom of the walkthru menu.

Returns:

  • (Array<String>)

    The names of any required opts that have no current value. Displayed at the bottom of the walkthru menu.



798
799
800
801
802
803
804
805
806
807
808
809
# File 'lib/xolo/admin/interactive.rb', line 798

def missing_values
  missing_values = []
  required_values.each do |key, deets|
    next if walkthru_cmd_opts[key]

    missing_values << deets[:label]
  end

  missing_values += title_missing_values if title_command?

  missing_values
end

#multiline_editor_to_useString

Prompt for an editor to use from those in MULTILINE_EDITORS

Returns:

  • (String)

    the path to an editor to use for multiline values.



869
870
871
872
873
874
875
876
877
878
879
880
# File 'lib/xolo/admin/interactive.rb', line 869

def multiline_editor_to_use
  return config.editor if config.editor

  highline_cli.choose do |menu|
    menu.select_by = :index
    menu.prompt = 'Choose an editor:'
    MULTILINE_EDITORS.each do |name, cmd|
      menu.choice(cmd, nil, name)
    end # MULTILINE_EDITORS.each
    menu.choice(Xolo::CANCEL)
  end # @cli.choose
end

#patch_source_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



200
201
202
203
204
205
206
207
# File 'lib/xolo/admin/interactive.rb', line 200

def patch_source_na
  # if walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id]
  #   'N/A when using Version Script or App Name/BundleID'
  # elsif walkthru_cmd_opts[:publisher] || walkthru_cmd_opts[:display_name]
  #   'N/A when using Display Name or Publisher'
  # end
  nil unless walkthru_cmd_opts[:subscribed]
end

#prompt_for_local_files_via_readline(question:, q_desc:, deets:, validate: nil) ⇒ Object

Highline's ability to do autocompletion for local file selection is limited at best (it only will autocomplete within a single directory, defaulting to the one containing the executable)

So if we want a shell-style autocompletion for selecting one or more files then we'll use readline directly, where its pretty simple to do.

Parameters:

  • question (String)

    The question to ask

  • q_desc (String)

    A longer description of what we're asking for

  • deets (Hash)

    The option-details for the value for which we are prompting

  • validate (Lambda) (defaults to: nil)

    The lambda for validating the answer before conversion

Returns:

  • (Object)

    The validated and converted value given by the user.



524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/xolo/admin/interactive.rb', line 524

def prompt_for_local_files_via_readline(question:, q_desc:, deets:, validate: nil)
  prompt = setup_for_readline_local_files(deets)

  highline_cli.say "#{q_desc}\n#{question}"

  validated_new_val = deets[:multi] ? [] : nil
  all_done = false
  until all_done
    latest_input = Readline.readline(prompt, true).strip
    # dragging in items from the finder will esacpe spaces in the path with \'s
    # in the shell this is good, but ruby is interpreting the \'s, so lets remove them.
    latest_input.gsub!(/\\ /, ' ')

    break if latest_input == Xolo::X
    return Xolo::NONE if !deets[:required] && (latest_input == Xolo::NONE)

    if validate
      if validate.call latest_input
        latest_input = last_converted_value
      else
        highline_cli.say "#{last_validation_error}\nType 'x' to exit"
        next
      end
    end
    # if we are here, the latest_input is valid

    # We only validate individual items, but the validation
    # method might return an array (which it does for CLI option validation
    # for options that are stored in arrays - it validates them  all at once)
    # so deal with that here or we'll get nested arrays here.
    latest_input = latest_input.first if latest_input.is_a?(Array)

    if deets[:multi]
      validated_new_val << latest_input
    else
      validated_new_val = latest_input
      all_done = true
    end

  end # until all_done

  validated_new_val
end

#prompt_for_multi_values_with_highline(question:, q_desc:, deets:, convert: nil, validate: nil) ⇒ Array, String

Prompt for an array of values using highline 'ask' with 'gather' and possibly readline auto-completion from an array

Parameters:

  • question (String)

    The question to ask

  • q_desc (String)

    A longer description of what we're asking for

  • convert (Lambda) (defaults to: nil)

    The lambda for converting the validated value

  • validate (Lambda) (defaults to: nil)

    The lambda for validating the answer before conversion

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (Array, String)

    The validated and converted values given by the user, or 'none'



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/xolo/admin/interactive.rb', line 449

def prompt_for_multi_values_with_highline(question:, q_desc:, deets:, convert: nil, validate: nil)
  use_readline, convert, validate = setup_for_readline_in_highline(deets, convert, validate)

  highline_cli.say q_desc

  chosen_values = highline_cli.ask(question, convert) do |q|
    if use_readline
      q.readline = true
      q.responses[:no_completion] = "Unknown Choice.#{HIGHLINE_READLINE_GATHER_ERR_INSTRUCTIONS}"
      q.responses[:ambiguous_completion] = "Ambiguous Choice.#{HIGHLINE_READLINE_GATHER_ERR_INSTRUCTIONS}"
    end
    if validate
      q.validate = validate
      q.responses[:not_valid] = ->(_x) { "\nERROR: #{last_validation_error}" }
      q.responses[:ask_on_error] = :question
    end
    q.gather = Xolo::X
  end
  chosen_values.flatten!

  # don't return an empty array if none was chosen, but
  # return 'none' so that the whole value is cleared.
  chosen_values = Xolo::NONE if chosen_values.include? Xolo::NONE
  chosen_values
end

#prompt_for_single_value_with_highline(question:, q_desc:, convert:, validate:, deets:) ⇒ Object

Prompt for a one-line single value via highline, possibly with readline auto-completion from an array of possible values

Parameters:

  • question (String)

    The question to ask

  • q_desc (String)

    A longer description of what we're asking for

  • convert (Lambda)

    The lambda for converting the validated value

  • validate (Lambda)

    The lambda for validating the answer before conversion

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (Object)

    The validated and converted value given by the user.



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/xolo/admin/interactive.rb', line 421

def prompt_for_single_value_with_highline(question:, q_desc:, convert:, validate:, deets:)
  use_readline, convert, validate = setup_for_readline_in_highline(deets, convert, validate)

  highline_cli.say q_desc

  highline_cli.ask(question, convert) do |q|
    q.readline = use_readline
    q.echo = '*' if deets[:secure_interactive_input]

    if validate
      q.validate = validate
      q.responses[:not_valid] = ->(_x) { "\nERROR: #{last_validation_error}" }
      q.responses[:ask_on_error] = :question
    end
  end
end

#prompt_for_walkthru_value(key, deets, _curr_val) ⇒ void

This method returns an undefined value.

prompt for an option value and store it in walkthru_cmd_opts

Parameters:

  • key (Symbol)

    One of the keys of the opts hash for the current command; the value for which we are prompting

  • deets (Hash)

    the details about the option key we prompting for

  • curr_val (Object)

    The current value of the option, if any



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
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
403
404
405
406
407
408
# File 'lib/xolo/admin/interactive.rb', line 347

def prompt_for_walkthru_value(key, deets, _curr_val)
  # current_value = default_for_value(key, deets, curr_val) # Needed??
  current_value = walkthru_cmd_opts[key]
  q_desc = question_desc(deets)
  question = question_for_value(deets)

  # Highline wants a separate lambda for conversion
  # and validation, validation just returns boolean,
  # but conversion returns the converted value.
  # but our validation methods do the conversion.
  #
  # so we'll just return the last_converted_value we got
  # when we validate, or nil if we don't validate
  #
  validate = validation_lambda(key, deets)
  convert = validate ? ->(_ans) { last_converted_value } : ->(ans) { ans }

  answer =
    if deets[:multiline]
      prompt_via_multiline_editor(
        question: question,
        q_desc: q_desc,
        current_value: current_value,
        validate: validate
      )
    elsif deets[:readline] == :get_files
      prompt_for_local_files_via_readline(
        question: question,
        q_desc: q_desc,
        deets: deets,
        validate: validate
      )
    elsif deets[:multi]
      prompt_for_multi_values_with_highline(
        question: question,
        q_desc: q_desc,
        deets: deets,
        convert: convert,
        validate: validate
      )
    else
      prompt_for_single_value_with_highline(
        question: question,
        q_desc: q_desc,
        convert: convert,
        validate: validate,
        deets: deets
      )
    end

  # answer = answer.map(&:strip).join("\n") if deets[:multiline]
  # x means keep the current value
  # answer = nil if answer == 'x'

  # if no answer, keep the current value
  return if answer.pix_empty?

  # if 'none', erase the value in walkthru_cmd_opts
  answer = nil if answer == Xolo::NONE

  walkthru_cmd_opts[key] = answer
end

#prompt_via_multiline_editor(question:, q_desc:, current_value: Xolo::BLANK, validate: nil) ⇒ String

Prompt for a single multiline value via an editor, like vim. This always returns a string. We handle validation ourselves, since we can't use highline.ask

Parameters:

  • question (String)

    The question to ask

  • q_desc (String)

    A longer description of what we're asking for

  • current_value (String) (defaults to: Xolo::BLANK)

    The string to start editing.

  • validate (Lambda) (defaults to: nil)

    The lambda for validating the answer before conversion

Returns:

  • (String)

    the edited value.



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/xolo/admin/interactive.rb', line 485

def prompt_via_multiline_editor(question:, q_desc:, current_value: Xolo::BLANK, validate: nil)
  highline_cli.say "#{question}\n#{q_desc}"

  new_val = nil
  validated_new_val = nil
  editor = multiline_editor_to_use
  return if editor == Xolo::CANCEL

  until validated_new_val
    new_val = edited_multiline_value editor, q_desc, current_value
    if validate
      if validate.call new_val
        validated_new_val = last_converted_value
      else
        again = highline_cli.ask("\n#{last_validation_error}\nType a return to edit again, '#{Xolo::X}' to exit")
        break if again == Xolo::X
      end
    else
      validated_new_val = new_val
    end
  end

  validated_new_val || current_value
end

#publisher_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



171
172
173
174
175
# File 'lib/xolo/admin/interactive.rb', line 171

def publisher_na
  return unless walkthru_cmd_opts[:subscribed]

  'N/A when subcribing via a Patch Source'
end

#pw_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



269
270
271
272
273
# File 'lib/xolo/admin/interactive.rb', line 269

def pw_na
  admin_empty = walkthru_cmd_opts[:admin].pix_empty?
  host_empty = walkthru_cmd_opts[:hostname].pix_empty?
  'N/A until hostname and admin name are set' if host_empty || admin_empty
end

#question_desc(deets) ⇒ String

The multi-lines of text describing the value above the prompt

Parameters:

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (String)

    the text to display



669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'lib/xolo/admin/interactive.rb', line 669

def question_desc(deets)
  q_desc = +"============= #{deets[:label]} =============\n"
  q_desc << deets[:desc]

  if deets[:multiline]
    # nada, will be shown in the editor
  elsif deets[:multi]
    q_desc << "\nEnter one value per line."
    q_desc << "\nUse tab for auto-completion, tab twice to see available choices" if deets[:readline]
    q_desc << "\nType '#{Xolo::X}' on a line by itself to exit." if deets[:validate]

  else
    q_desc << "\nType a return to keep the current value."
  end

  q_desc
end

#question_for_value(deets) ⇒ String

The line of text prompting for a value. End with a space to keep prompt on same line

Parameters:

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (String)

    the one-line prompt to display



693
694
695
696
697
698
699
700
701
702
703
# File 'lib/xolo/admin/interactive.rb', line 693

def question_for_value(deets)
  question = +"Enter #{deets[:label]}"

  if deets[:type] == :boolean
    question << ', (y/n)'
  elsif !deets[:required]
    question << ", use '#{Xolo::NONE}' to unset"
  end
  question << ': ' unless deets[:readline]
  question
end

#setup_for_readline_in_highline(deets, convert, validate) ⇒ Array

should we use readline, and if so should we use an array of values or not?

Parameters:

  • convert (Lambda)

    The lambda for converting the validated value

  • validate (Lambda)

    The lambda for validating the answer before conversion

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (Array)

    Three items:

    • if we should use readline (boolean)
    • the new 'convert' value - either the original passed to us or an array of possible values
    • the new 'validate' value - either the original passed to us or nil if using an array of values


580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/xolo/admin/interactive.rb', line 580

def setup_for_readline_in_highline(deets, convert, validate)
  # if deets[:readline] is a symbol, its an xadm method that returns an array
  # of the possible values for readline completion and validation;
  # only things in the array are allowed, so no need for other validation or conversion
  # We add 'x' and 'none' to the list for clearing it, and if a multi-value we add 'x'
  # for exiting out of the prompt
  #
  # if its just truthy then we use readline without a pre-set list of values
  # (e.g. paths, which might not exist locally) and may have a separate validate
  # and convert lambdas
  use_readline =
    if deets[:readline]
      if deets[:readline].is_a? Symbol
        convert = send deets[:readline]
        convert << Xolo::NONE unless convert.include?(Xolo::NONE) || deets[:required]
        convert << Xolo::X if deets[:multi] && !convert.include?(Xolo::X)
        # if we're doing release groups, make sure the list includes 'all',
        if !convert.include?(Xolo::TARGET_ALL) && deets[:label] == Xolo::Admin::Title::ATTRIBUTES[:release_groups][:label]
          convert << Xolo::TARGET_ALL
        end
        validate = nil
      end
      true
    else
      false
    end

  if use_readline
    # Case Insensitivity, aka deets[:readline_casefold]
    #
    # If deets[:readline_casefold] is explicitly true or false, we honor that.
    #
    # Otherwise, when we have an array of possible values, we make readline case insensitive.
    # and without such array, we make it sensitive (e.g. when selecting existing file paths)
    #
    # Setting this ENV is how we use our monkey patch to make this work
    ENV['XADM_HIGHLINE_READLINE_CASE_INSENSITIVE'] =
      case deets[:readline_casefold]
      when true
        Xolo::X
      when false
        nil
      else
        convert.is_a?(Array) ? Xolo::X : nil
      end

    # Setting this ENV is how we use our monkey patch to make this work
    prompt = deets[:readline_prompt] || deets[:multi_prompt] || deets[:label] || DEFAULT_HIGHLINE_READLINE_PROMPT
    ENV['XADM_HIGHLINE_READLINE_PROMPT'] = "#{prompt}: "
  end # if use_readline

  [use_readline, convert, validate]
end

#setup_for_readline_local_files(deets) ⇒ String

set up readline for local file autocomplete return the prompt we'll be using with readline

Parameters:

  • deets (Hash)

    The option-details for the value for which we are prompting

Returns:

  • (String)

    the prompt to use with readline



641
642
643
644
645
646
647
648
649
650
651
# File 'lib/xolo/admin/interactive.rb', line 641

def setup_for_readline_local_files(deets)
  Readline.completion_append_character = nil
  Readline.basic_word_break_characters = "\n"
  Readline.completion_proc = proc do |str|
    str = Pathname.new(str).expand_path
    str = str.directory? ? "#{str}/" : str.to_s
    Dir[str + '*'].grep(/^#{Regexp.escape(str)}/)
  end
  prompt = deets[:readline_prompt] || deets[:label] || DEFAULT_HIGHLINE_READLINE_PROMPT
  "#{prompt}: "
end

#ssvc_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



230
231
232
233
234
# File 'lib/xolo/admin/interactive.rb', line 230

def ssvc_na
  tgt_all = walkthru_cmd_opts[:release_groups]&.include?(Xolo::TARGET_ALL)

  "N/A if Release Group is '#{Xolo::TARGET_ALL}'" if tgt_all
end

#title_id_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



212
213
214
215
216
# File 'lib/xolo/admin/interactive.rb', line 212

def title_id_na
  return if walkthru_cmd_opts[:patch_source]

  'N/A until Patch Source is set'
end

#title_missing_valuesArray<String>

Process missing valus for titles

Returns:

  • (Array<String>)

    The title-specific missing values



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
# File 'lib/xolo/admin/interactive.rb', line 814

def title_missing_values
  title_missing_values = []

  # if subscribing, need patch source and title id
  # if walkthru_cmd_opts[:type] == Xolo::SUBSCRIBED
  if walkthru_cmd_opts[:subscribed]
    unless walkthru_cmd_opts[:patch_source]
      title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:patch_source][:label]
    end
    title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:title_id][:label] unless walkthru_cmd_opts[:title_id]

  # if managed, need display name, publisher and version script or app name/bundle id,
  else
    unless walkthru_cmd_opts[:publisher]
      title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:publisher][:label]
    end
    unless walkthru_cmd_opts[:display_name]
      title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:display_name][:label]
    end

    # version script or app name/bundle id
    if walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id]
      # need both app name and bundle id if using either
      if walkthru_cmd_opts[:app_name] && !walkthru_cmd_opts[:app_bundle_id]
        title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:app_bundle_id][:label]
      elsif walkthru_cmd_opts[:app_bundle_id] && !walkthru_cmd_opts[:app_name]
        title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:app_name][:label]
      end
    else
      title_missing_values << "#{Xolo::Admin::Title::ATTRIBUTES[:version_script][:label]} OR #{Xolo::Admin::Title::ATTRIBUTES[:app_name][:label]} & #{Xolo::Admin::Title::ATTRIBUTES[:app_bundle_id][:label]}"
    end

  end

  # if expiring, need expire path
  if walkthru_cmd_opts[:expiration].to_i.positive? && !walkthru_cmd_opts[:expiration_paths].pix_empty?
    title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:expiration_paths][:label]
  end

  # if in ssvc, need category and icon
  return title_missing_values unless walkthru_cmd_opts[:self_service]

  unless walkthru_cmd_opts[:self_service_category]
    title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:self_service_category][:label]
  end
  return title_missing_values if walkthru_cmd_opts[:self_service_icon]

  title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:self_service_icon][:label]

  title_missing_values
end

#uninstall_ids_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



246
247
248
# File 'lib/xolo/admin/interactive.rb', line 246

def uninstall_ids_na
  'N/A if using Uninstall Script' unless walkthru_cmd_opts[:uninstall_script].pix_empty?
end

#uninstall_script_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



239
240
241
# File 'lib/xolo/admin/interactive.rb', line 239

def uninstall_script_na
  'N/A if using Uninstall IDs' unless walkthru_cmd_opts[:uninstall_ids].pix_empty?
end

#validation_lambda(key, deets) ⇒ Lambda?

Retun a lambda that calls one of our validation methods to validate a walkthru value.

Highline requires validation lambdas to return a boolean, and uses a separate lambda for type conversion. Since our validation methods do both, this lambda will put the converted result into the 'last_converted_value' accessor, or capture the error, and then return a boolean.

Later the lambda we give to highline for conversion will just return the last converted value, as stored in the last_converted_value accessor.

Returns:

  • (Lambda, nil)

    The lambda that highline will use to validate (and convert) a value, nil if we accept whatever was given.



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/xolo/admin/interactive.rb', line 721

def validation_lambda(key, deets)
  val_meth = validation_method(key, deets)
  return unless val_meth

  # lambda to validate the value given.
  # must return boolean for Highline to deal with it.
  lambda do |ans|
    # to start, the converted value is just the given value.
    #
    # Use self here, otherwise the lambda sees 'last_converted_value ='
    # as a local variable assignment, not a setter method call
    self.last_converted_value = ans

    # default to the pre-written error message
    self.last_validation_error = deets[:invalid_msg]

    # if entering multi-values, a 'x' is how we get out of
    # the loop
    return true if deets[:multi] && ans == Xolo::X

    # but for anything not multi, an empty response
    # means user just hit return, nothing to validate,
    # no changes to make
    return true if ans.pix_empty?

    # If this value isn't required, accept 'none'
    # which clears the value
    return true if !deets[:required] && (ans == Xolo::NONE)

    # otherwise 'none' becomes nil and will be validated
    # and will fail if a value is required
    ans_to_validate = ans == Xolo::NONE ? nil : ans

    # validate using the val_meth,
    # saving the validated/converted value for use in the
    # convert method.
    self.last_converted_value = send(val_meth, ans_to_validate)
    true

  # if validation fails, set the last_validation_error
  # so we can display it and ask again
  rescue Xolo::InvalidDataError => e
    self.last_validation_error = e.to_s
    false
  end # lambda
end

#validation_method(key, deets) ⇒ String, ...

The method used to validate and convert a value

Parameters:

  • deets (Hash)

    The option-details for the value for which we are prompting

  • key (Symbol)

    One of the keys of the opts hash for the current command; the value for which we are prompting

Returns:

  • (String, Symbol, nil)

    The method which will validate the value for the key



788
789
790
791
792
793
# File 'lib/xolo/admin/interactive.rb', line 788

def validation_method(key, deets)
  case deets[:validate]
  when TrueClass then "validate_#{key}"
  when Symbol then deets[:validate]
  end
end

#version_script_naString?

Returns If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.

Returns:

  • (String, nil)

    If a string, a reason why the given menu item is not available now. If nil, the menu item is displayed normally.



180
181
182
183
184
185
186
# File 'lib/xolo/admin/interactive.rb', line 180

def version_script_na
  unless walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id] || walkthru_cmd_opts[:subscribed]
    return
  end

  'N/A when using App Name/BundleID, or subcribing via a Patch Source'
end