gitlab-ce/scripts/internal_events/cli/helpers/cli_inputs.rb

139 lines
4.8 KiB
Ruby
Executable File

# frozen_string_literal: true
# Helpers related to configuration of TTY::Prompt prompts
module InternalEventsCli
module Helpers
module CliInputs
def prompt_for_array_selection(message, choices, default = nil, **opts, &formatter)
formatter ||= ->(choice) { choice.sort.join(", ") }
choices = choices.map do |choice|
{ name: formatter.call(choice), value: choice }
end
cli.select(message, choices, **select_opts, **opts) do |menu|
menu.enum "."
menu.default formatter.call(default) if default
end
end
# Prompts the user to input text. Prefer this over calling cli#ask directly (so styling is consistent).
#
#
# @return [String, nil] user-provided text
# @param message [String] a single line prompt/question or last line of a prompt
# @param value [String, nil] prepopulated as the answer which user can accept/modify
# @option multiline [Boolean] indicates that any help text or prompt prefix will be printed on another line
# before calling #prompt_for_text --> ex) see MetricDefiner#prompt_for_description
# @yield [TTY::Prompt::Question]
# @see https://github.com/piotrmurach/tty-prompt?tab=readme-ov-file#21-ask
def prompt_for_text(message, value = nil, multiline: false, **opts)
prompt = message.dup # mutable for concat in #ask callback
options = { **input_opts, **opts }
value ||= options.delete(:value)
options.delete(:prefix) if multiline
cli.ask(prompt, **options) do |q|
q.value(value) if value
yield q if block_given?
if multiline
# wrap error messages so they render nicely with prompt
q.messages.each do |key, error|
closing_text = "\n#{format_error('<<|')}" if error.lines.length > 1
q.messages[key] = [error, closing_text, "\n\n\n"].join('')
end
else
# append help text only if this line includes the formatted 'prompt' prefix,
# otherwise depend on the caller to print the help text if needed
prompt.concat(" #{q.required ? input_required_text : input_optional_text(value)}")
end
end
end
def input_opts
{ prefix: format_prompt('Input text: ') }
end
def yes_no_opts
{ prefix: format_prompt('Yes/No: ') }
end
# Provide to cli#select as kwargs for consistent style/ux
def select_opts
{
prefix: format_prompt('Select one: '),
cycle: true,
show_help: :always,
# Strip colors so #format_selection is applied uniformly
active_color: ->(choice) { format_selection(clear_format(choice)) }
}
end
# Provide to cli#multiselect as kwargs for consistent style/ux
def multiselect_opts
{
**select_opts,
prefix: format_prompt('Select multiple: '),
min: 1,
help: "(Space to select, Enter to submit, ↑/↓/←/→ to move, Ctrl+A|R to select all|none, letters to filter)"
}
end
# Accepts a number of lines occupied by text, so remaining
# screen real estate can be filled with select options
def filter_opts(header_size: nil)
{
filter: true,
per_page: header_size ? [(window_height - header_size), 10].max : 30
}
end
# Creates divider to be passed to a select or multiselect
# as a menu item. Use with #format_disabled_options_as_dividers
# for best formatting.
def select_option_divider(text)
{ name: "-- #{text} --", value: nil, disabled: '' }
end
# Styling all disabled options in a menu without indication
# of being a selectable option
# @param select_menu [TTY::Prompt]
def format_disabled_options_as_dividers(select_menu)
select_menu.symbols(cross: '')
end
# For use when menu options are disabled by being grayed out
def disabled_format_callback
proc { |menu| menu.symbols(cross: format_help("")) }
end
# Help text to use with required, multiline cli#ask prompts.
# Otherwise, prefer #prompt_for_text.
def input_required_text
format_help("(leave blank for help)")
end
# Help text to use with optional, multiline cli#ask prompts.
# Otherwise, prefer #prompt_for_text.
def input_optional_text(value)
format_help("(enter to #{value ? 'submit' : 'skip'})")
end
def disableable_option(value:, disabled:, name: nil)
should_disable = yield
name ||= value
{
value: value,
name: (should_disable ? format_help(name) : name),
disabled: (disabled if should_disable)
}
end
end
end
end