From 2eeefcd7008b5b05e91c0edbda4925e79209c502 Mon Sep 17 00:00:00 2001 From: Benjamin Lupton Date: Tue, 6 Aug 2024 20:00:26 +0800 Subject: [PATCH] ask,choose,confirm: consistent styling echo-style, styles.bash: added new helpers for caching color/nocolor lookups refactors also fix respect of no-color /ref #188 --- commands/ask | 96 +++++++++++-------- commands/choose | 223 +++++++++++++++----------------------------- commands/confirm | 107 +++++++++++---------- commands/echo-style | 9 +- config/styles.bash | 184 +++++++++++++++++++++++++++++++++++- 5 files changed, 372 insertions(+), 247 deletions(-) diff --git a/commands/ask b/commands/ask index 4880e000d..304ce93ad 100755 --- a/commands/ask +++ b/commands/ask @@ -180,6 +180,48 @@ function ask_() ( esac done + # question + local question_title question_body + if test "${#option_question[@]}" -ne 0; then # bash v3 compat + if test -n "${option_question[0]}"; then + question_title="${option_question[0]}" + question_body="$(__print_lines "${option_question[@]:1}")" + else + question_title="$(__print_lines "${option_question[@]:1}")" + question_body='' + fi + else + question_title='' + question_body='' + fi + + # enforce question if lingering + if test "$option_linger" = 'yes' -a -z "$question_title"; then + help 'A is required when using --linger' + fi + + # ===================================== + # Styles + + source "$DOROTHY/sources/config.sh" + + # styles.bash provides: + # all style variables + load_dorothy_config 'styles.bash' + + # refresh the styles + refresh_style_cache -- 'question_title_prompt' 'question_title_result' 'question_body' 'input_warning' 'input_error' 'icon_prompt' 'result_value' 'icon_nothing_provided' 'icon_using_password' + + # style the question + local question_title_prompt='' question_title_result='' question_body_prompt='' + if test -n "$question_title"; then + question_title_prompt="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}" + question_title_result="${style__question_title_result}${question_title}${style__end__question_title_result}" + fi + if test -n "$question_body"; then + question_body_prompt="${style__question_body}${question_body}${style__end__question_body}" + fi + # ===================================== # Action @@ -188,9 +230,6 @@ function ask_() ( local tty_target tty_target="$(is-tty --fallback)" - # prepare styles - local style_dim=$'\e[2m' style_bold=$'\e[1m' style_reset=$'\e[0m' - # prepare result local RESULT if test -n "$option_default"; then @@ -204,26 +243,6 @@ function ask_() ( option_timeout=60 fi - # adjust question - local question_title question_with_body - if test "${#option_question[@]}" -ne 0; then # bash v3 compat - if test -n "${option_question[0]}"; then - question_title="$style_bold${option_question[0]}$style_reset" - question_with_body="$(__print_lines "$question_title" "${option_question[@]:1}")" - else - question_title="$(__print_lines "${option_question[@]:1}")" - question_with_body="$question_title" - fi - else - question_title='' - question_with_body='' - fi - - # enforce question if lingering - if test "$option_linger" = 'yes' -a -z "$question_title"; then - help 'A is required when using --linger' - fi - # adjust tty local size_columns bin_gfold bin_gwc size_columns="$(tput cols)" @@ -236,16 +255,16 @@ function ask_() ( fi # helpers - local ASKED='no' prompt='> ' commentary='' + local ASKED='no' commentary='' function on_timeout { if is-value -- "$RESULT"; then - commentary="$(echo-style --yellow='[timed out: using fallback]')" + commentary="${style__input_warning}[timed out: using fallback]${style__end__input_warning}" return 0 elif test "$option_required" = 'no'; then - commentary="$(echo-style --yellow='[timed out: optional]')" + commentary="${style__input_warning}[timed out: optional]${style__end__input_warning}" return 0 else - commentary="$(echo-style --red='[input failure: timed out: required]')" + commentary="${style__input_error}[input failure: timed out: required]${style__end__input_error}" return 60 # ETIMEDOUT 60 Operation timed out fi } @@ -272,10 +291,13 @@ function ask_() ( fi else # reset render - if test -n "$question_with_body"; then - render="$question_with_body"$'\n' + if test -n "$question_title_prompt"; then + render+="$question_title_prompt"$'\n' + fi + if test -n "$question_body_prompt"; then + render+="$question_body_prompt"$'\n' fi - render+="$prompt" + render+="$style__icon_prompt" # we have tty stdin, can do a prompt # -i requires -e @@ -362,7 +384,7 @@ function ask_() ( on_timeout return elif test "$choose_status" -ne 0; then - commentary="$(echo-style --red="[input failure: choose failure: $choose_status]")" + commentary="${style__input_error}[input failure: choose failure: $choose_status]${style__end__input_error}" return "$choose_status" fi @@ -378,7 +400,7 @@ function ask_() ( return 0 else # unknown error - commentary="$(echo-style --red="[input failure: invalid choice: $choice]")" + commentary="${style__input_error}[input failure: invalid choice: $choice]${style__end__input_error}" return 14 # EFAULT 14 Bad address fi fi @@ -399,7 +421,7 @@ function ask_() ( # act eval_capture --statusvar=result_status -- do_validate - local render="$question_title" + local render="$question_title_result" if test -n "$commentary"; then if test -n "$render"; then render+=" $commentary" @@ -414,19 +436,17 @@ function ask_() ( # add the results only if lingering if test "$option_linger" = 'yes'; then if test -z "$RESULT"; then - render+="${style_dim}[ nothing provided ]${style_reset}"$'\n' + render+="${style__result_value}${style__icon_nothing_provided}${style__end__result_value}"$'\n' elif test "$option_password" = 'yes'; then - render+="${style_dim}[ using the entered password ]${style_reset}"$'\n' + render+="${style__result_value}${style__icon_using_password}${style__end__result_value}"$'\n' else - render+="${style_dim}${RESULT}${style_reset}"$'\n' + render+="${style__result_value}${RESULT}${style__end__result_value}"$'\n' fi # inform __print_string "$render" >"$tty_target" elif test -n "$commentary"; then # inform to stderr, consistent with ask, choose, confirm __print_string "$render" >/dev/stderr - # sleep 3 - # echo-clear-lines --stdin < <(__print_string "$render") >/dev/stderr fi # stdout if test -z "$RESULT"; then diff --git a/commands/choose b/commands/choose index 705ce3185..27ab44340 100755 --- a/commands/choose +++ b/commands/choose @@ -1,43 +1,33 @@ #!/usr/bin/env bash -# QUIRKS: -# - if there are more options than [$LINES - header], then this will fall apart - -# TODOS: -# - [ ] limit the options output to [$LINES - header] -# - [ ] if one gets to $LINES, and there are truncated values, then scroll downwards -# - [ ] support $COLUMNS - if a menu item is larger than the column, then it will show all of it when active -# - [ ] ctrl n/p for navigating up/down. -# - [ ] `hjkl` vim arrow keys. - function choose_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" ## choose-menu ## - # timeout response not required - eval-tester --name='timeout response not required' --status='60' --stderr='Read timed out [60], without selection.' \ - -- env NO_COLOR=yes choose --index --question='timeout response not required' --timeout=5 -- a b c + # test where timeout response not required + eval-tester --name='test with tineout where response is NOT required' --status='60' --stderr='Read timed out [60], without selection.' \ + -- env NO_COLOR=yes choose --index --question='test with tineout where response is NOT required' --timeout=5 -- a b c - # timeout response is required - eval-tester --name='timeout response is required' --status='60' --stderr='Read timed out [60], without selection.' \ - -- env NO_COLOR=yes choose --index --question='timeout response is required' --timeout=5 --required -- a b c + # test where timeout response is required + eval-tester --name='test with tineout where response IS required' --status='60' --stderr='Read timed out [60], without selection.' \ + -- env NO_COLOR=yes choose --index --question='test with tineout where response IS required' --timeout=5 --required -- a b c - # default response + # test with default response { sleep 3 - } | eval-tester --name='default response' --stdout='1' --ignore-stderr \ - -- choose --index --question='default response' --timeout=2 --default=b -- a b c + } | eval-tester --name='test with default response' --stdout='1' --ignore-stderr \ + -- choose --index --question='test with default response' --timeout=2 --default=b -- a b c - # default response should clear on movement + # test with default response should clear on movement { # move down and select second response sleep 3 printf $'\eOB' sleep 3 - } | eval-tester --name='default response should clear on movement' --status='60' --stdout='' --ignore-stderr \ - -- choose --index --question='default response should clear on movement' --timeout=10 --default=b -- a b c + } | eval-tester --name='test with default response should clear on movement' --status='60' --stdout='' --ignore-stderr \ + -- choose --index --question='test with default response should clear on movement' --timeout=10 --default=b -- a b c # default multi response { @@ -86,19 +76,19 @@ function choose_test() ( ## choose ## - # timeout response not required - eval-tester --name='timeout response not required' --status='0' --stderr=$'Read timed out [60], without selection.\nMenu timed out [60], no result, not required.' \ - -- env NO_COLOR=yes choose --question='timeout response not required' --timeout=5 -- a b c + # test where timeout response not required + eval-tester --name='test where timeout response not required' --status='0' --stderr=$'Read timed out [60], without selection.\nMenu timed out [60], no result, not required.' \ + -- env NO_COLOR=yes choose --question='test where timeout response not required' --timeout=5 -- a b c - # timeout response is required - eval-tester --name='timeout response is required' --status='60' --stderr=$'Read timed out [60], without selection.\nMenu timed out [60], no result, is required.' \ - -- env NO_COLOR=yes choose --question='timeout response is required' --timeout=5 --required -- a b c + # test where timeout response is required + eval-tester --name='test where timeout response is required' --status='60' --stderr=$'Read timed out [60], without selection.\nMenu timed out [60], no result, is required.' \ + -- env NO_COLOR=yes choose --question='test where timeout response is required' --timeout=5 --required -- a b c - # default response + # test with default response { sleep 3 - } | eval-tester --name='default response' --stdout='b' --ignore-stderr \ - -- choose --question='default response' --timeout=2 --default=b -- a b c + } | eval-tester --name='test with default response' --stdout='b' --ignore-stderr \ + -- choose --question='test with default response' --timeout=2 --default=b -- a b c # default multi response { @@ -219,14 +209,11 @@ function choose_() ( local defaults_exact=() defaults_fuzzy=() option_confirm_default='yes' option_confirm_input='no' local option_required='no' option_multi='no' local option_linger='no' option_timeout='' - local use_colors - use_colors="$(echo-color-enabled --fallback=yes -- "$@")" while test "$#" -ne 0; do item="$1" shift case "$item" in '--help' | '-h') help ;; - '--no-color'* | '--color'*) ;; # handled by echo-color-enabled '--question='*) option_question+=("${item#*=}") ;; '--no-label'* | '--label'*) # label can be 'yes', 'no', and 'first' @@ -284,6 +271,21 @@ function choose_() ( help 'No s provided.' fi + # question + local question_title question_body + if test "${#option_question[@]}" -ne 0; then # bash v3 compat + if test -n "${option_question[0]}"; then + question_title="${option_question[0]}" + question_body="$(__print_lines "${option_question[@]:1}")" + else + question_title="$(__print_lines "${option_question[@]:1}")" + question_body='' + fi + else + question_title='' + question_body='' + fi + # ===================================== # Styles @@ -293,6 +295,9 @@ function choose_() ( # all style variables load_dorothy_config 'styles.bash' + # refresh the styles + refresh_style_cache -- 'question_title_prompt' 'question_title_result' 'question_body' 'input_warning' 'input_error' 'icon_prompt' 'result_value' 'error' 'notice' 'count_spacer' 'result_line' 'active_line' 'selected_line' 'default_line' 'empty_line' 'inactive_line' 'legend' 'key' 'count_more' 'count_selected' 'count_default' 'count_empty' 'bar_top' 'bar_middle' 'bar_bottom' 'icon_multi_selected' 'icon_multi_default' 'icon_multi_active' 'icon_multi_standard' 'icon_single_selected' 'icon_single_default' 'icon_single_active_required' 'icon_single_active_optional' 'icon_single_standard' 'icon_nothing_provided' 'icon_no_selection' 'icon_nothing_selected' + # select icons if test "$option_multi" = 'yes'; then style__icon_selected="$style__icon_multi_selected" @@ -310,104 +315,22 @@ function choose_() ( style__icon_standard="$style__icon_single_standard" fi - # select colors - if test "$use_colors" = 'yes'; then - # spacers - style__count_spacer="${style__color__count_spacer-}" - # lines - style__result_line="${style__color__result_line-}" - style__end__result_line="${style__color_end__result_line-}" - style__active_line="${style__color__active_line-}" - style__end__active_line="${style__color_end__active_line-}" - style__selected_line="${style__color__selected_line-}" - style__end__selected_line="${style__color_end__selected_line-}" - style__default_line="${style__color__default_line-}" - style__end__default_line="${style__color_end__default_line-}" - style__empty_line="${style__color__empty_line-}" - style__end__empty_line="${style__color_end__empty_line-}" - style__inactive_line="${style__color__inactive_line-}" - style__end__inactive_line="${style__color_end__inactive_line-}" - # legend - style__legend="${style__color__legend-}" - style__end__legend="${style__color_end__legend-}" - style__key="${style__color__key-}" - style__end__key="${style__color_end__key-}" - # paging counts - style__count_more="${style__color__count_more-}" - style__end__count_more="${style__color_end__count_more-}" - style__count_selected="${style__color__count_selected-}" - style__end__count_selected="${style__color_end__count_selected-}" - style__count_default="${style__color__count_default-}" - style__end__count_default="${style__color_end__count_default-}" - style__count_empty="${style__color__count_empty-}" - style__end__count_empty="${style__color_end__count_empty-}" - # paging headers - style__bar_top="${style__color__bar_top-}" - style__end__bar_top="${style__color_end__bar_top-}" - style__bar_middle="${style__color__bar_middle-}" - style__end__bar_middle="${style__color_end__bar_middle-}" - style__bar_bottom="${style__color__bar_bottom-}" - style__end__bar_bottom="${style__color_end__bar_bottom-}" - else - # spacers - style__count_spacer="${style__nocolor__count_spacer-}" - # lines - style__result_line="${style__nocolor__result_line-}" - style__end__result_line="${style__nocolor_end__result_line-}" - style__active_line="${style__nocolor__active_line-}" - style__end__active_line="${style__nocolor_end__active_line-}" - style__selected_line="${style__nocolor__selected_line-}" - style__end__selected_line="${style__nocolor_end__selected_line-}" - style__default_line="${style__nocolor__default_line-}" - style__end__default_line="${style__nocolor_end__default_line-}" - style__empty_line="${style__nocolor__empty_line-}" - style__end__empty_line="${style__nocolor_end__empty_line-}" - style__inactive_line="${style__nocolor__inactive_line-}" - style__end__inactive_line="${style__nocolor_end__inactive_line-}" - # legend - style__legend="${style__nocolor__legend-}" - style__end__legend="${style__nocolor_end__legend-}" - style__key="${style__nocolor__key-}" - style__end__key="${style__nocolor_end__key-}" - # paging counts - style__count_more="${style__nocolor__count_more-}" - style__end__count_more="${style__nocolor_end__count_more-}" - style__count_selected="${style__nocolor__count_selected-}" - style__end__count_selected="${style__nocolor_end__count_selected-}" - style__count_default="${style__nocolor__count_default-}" - style__end__count_default="${style__nocolor_end__count_default-}" - style__count_empty="${style__nocolor__count_empty-}" - style__end__count_empty="${style__nocolor_end__count_empty-}" - # paging headers - style__bar_top="${style__nocolor__bar_top-}" - style__end__bar_top="${style__nocolor_end__bar_top-}" - style__bar_middle="${style__nocolor__bar_middle-}" - style__end__bar_middle="${style__nocolor_end__bar_middle-}" - style__bar_bottom="${style__nocolor__bar_bottom-}" - style__end__bar_bottom="${style__nocolor_end__bar_bottom-}" + # style the question + local question_title_prompt='' question_title_result='' question_body_prompt='' + if test -n "$question_title"; then + question_title_prompt="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}" + question_title_result="${style__question_title_result}${question_title}${style__end__question_title_result}" + fi + if test -n "$question_body"; then + question_body_prompt="${style__question_body}${question_body}${style__end__question_body}" fi # ===================================== # Menu - # adjust question - local question_title question_with_body - if test "${#option_question[@]}" -ne 0; then # bash v3 compat - if test -n "${option_question[0]}"; then - question_title="$(echo-style --underline+bold="${option_question[0]}")" - question_with_body="$(__print_lines "$question_title" "${option_question[@]:1}")" - else - question_title="$(__print_lines "${option_question[@]:1}")" - question_with_body="$question_title" - fi - else - question_title='' - question_with_body='' - fi - # enforce question if lingering if test "$option_linger" = 'yes' -a -z "$question_title"; then - help 'A is required when using --linger' + help 'A is required when using --linger' fi # generic helpers @@ -426,9 +349,9 @@ function choose_() ( # verify we have a label for each value if is-odd -- "${#inputs[@]}"; then { - echo-style --colors="$use_colors" --error="The amount of