Skip to content

Commit 1513428

Browse files
authored
Merge pull request #170 from mbland/input-prompts
Add @go.trim to lib/strings, add lib/prompt module
2 parents 4ccbc06 + 528e67d commit 1513428

File tree

12 files changed

+581
-78
lines changed

12 files changed

+581
-78
lines changed

go-core.bash

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -229,43 +229,6 @@ declare _GO_INJECT_MODULE_PATH="$_GO_INJECT_MODULE_PATH"
229229
return "$result"
230230
}
231231

232-
# Prompts the user to select one item from a list of options.
233-
#
234-
# This is a thin wrapper around the `select` builtin command for
235-
# straightforward, single-option user prompts. If you need to do anything more
236-
# complex, use the `select` builtin command directly.
237-
#
238-
# This will prompt the user for a single input, returned in the caller-declared
239-
# variable identified by `result_var`. If the user enters an invalid option,
240-
# this will notify the user and prompt again. If the user terminates input (via
241-
# EOF, i.e. Ctrl-D), `result_var` will remain unchanged and the function will
242-
# return nonzero.
243-
#
244-
# Globals:
245-
# PS3 environment variable defining the selection prompt
246-
#
247-
# Arguments:
248-
# result_var: Name of the caller-declared variable used to store the option
249-
# ...: Strings representing options available for the user to select
250-
#
251-
# Returns:
252-
# zero if `result_var` contains the user's selection, nonzero otherwise
253-
@go.select_option() {
254-
local __go_selected_option
255-
select __go_selected_option in "${@:2}"; do
256-
case "$__go_selected_option" in
257-
'')
258-
@go.printf '"%s" is not a valid option.\n' "$REPLY" >&2
259-
;;
260-
*)
261-
printf -v "$1" -- '%s' "$__go_selected_option"
262-
break
263-
;;
264-
esac
265-
done
266-
[[ -n "$__go_selected_option" ]]
267-
}
268-
269232
# Searches through plugin directories using a helper function
270233
#
271234
# The search will begin in `_GO_SCRIPTS_DIR/plugins`. As long as `search_func`

lib/prompt

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#! /usr/bin/env bash
2+
#
3+
# User input prompts
4+
#
5+
# Exports:
6+
# @go.read_prompt_response
7+
# Reads a line, trims leading/trailing space, and sets a default if empty
8+
#
9+
# @go.prompt_for_input
10+
# Prompts the user for a line of input
11+
#
12+
# @go.prompt_for_yes_or_no
13+
# Prompts the user for a yes or no response
14+
#
15+
# @go.select_option
16+
# Prompts the user to select one item from a list of options
17+
18+
. "$_GO_USE_MODULES" 'strings' 'validation'
19+
20+
# Reads a line, trims leading/trailing space, and sets a default if empty
21+
#
22+
# Arguments:
23+
# var_name: Name of the caller's variable into which to read value
24+
# default: (Optional) Default value if the input line is empty
25+
@go.read_prompt_response() {
26+
@go.validate_identifier_or_die 'Input prompt response variable name' "$1"
27+
read -r "$1"
28+
@go.trim "$1"
29+
printf -v "$1" -- '%s' "${!1:-$2}"
30+
}
31+
32+
# Prompts the user for a line of input
33+
#
34+
# If the prompt doesn't end with a whitespace character, a space will be added
35+
# between the prompt and the input cursor. Otherwise the existing character will
36+
# be preserved.
37+
#
38+
# If a default value is specified, a space will be added to the prompt, followed
39+
# by the default value in square brackets; the caller should not add the default
40+
# value to the prompt directly. If the prompt ends with a whitespace character,
41+
# it will be preserved and added after the default value.
42+
#
43+
# Arguments:
44+
# result_var Name of the caller-declared variable for the result
45+
# prompt Text prompt for user input
46+
# default (Optional) Default value if response is empty
47+
# fail_msg (Optional) Failure message if empty input isn't valid
48+
@go.prompt_for_input() {
49+
@go.validate_identifier_or_die 'Input prompt response variable name' "$1"
50+
51+
if [[ "$2" =~ [[:space:]]$ ]]; then
52+
@go.printf '%s%s%s' "${2%?}" "${3:+ [default: $3]}" "${BASH_REMATCH[0]}" >&2
53+
else
54+
@go.printf '%s %s' "$2" "${3:+[default: $3] }" >&2
55+
fi
56+
@go.read_prompt_response "$1" "$3"
57+
58+
if [[ -z "${!1}" && -n "$4" ]]; then
59+
@go.printf '%s\n' "$4" >&2
60+
return 1
61+
fi
62+
}
63+
64+
# Prompts the user for a yes or no response
65+
#
66+
# Arguments:
67+
# prompt Text prompt for user input
68+
# default (Optional) Default response; must be 'yes' or 'no'
69+
#
70+
# Returns:
71+
# Zero on 'y' or 'yes' (case- and space- insensitive), nonzero otherwise
72+
@go.prompt_for_yes_or_no() {
73+
local prompt="$1"
74+
local default="$2"
75+
local response
76+
77+
case "$default" in
78+
yes)
79+
@go.printf '%s [Y/n] ' "$prompt" >&2
80+
;;
81+
no)
82+
@go.printf '%s [y/N] ' "$prompt" >&2
83+
;;
84+
'')
85+
@go.printf '%s [y/n] ' "$prompt" >&2
86+
;;
87+
*)
88+
@go.printf 'Invalid `default` parameter "%s" for %s at:\n' \
89+
"$default" "$FUNCNAME" >&2
90+
@go.print_stack_trace '1' >&2
91+
exit 1
92+
;;
93+
esac
94+
95+
while true; do
96+
@go.read_prompt_response 'response' "$default"
97+
98+
if [[ "$response" =~ ^[Yy]([Ee][Ss])?$ ]]; then
99+
return 0
100+
elif [[ "$response" =~ ^[Nn]([Oo])?$ ]]; then
101+
return 1
102+
else
103+
if [[ -n "$response" ]]; then
104+
@go.printf '\n"%s" is an invalid response.\n' "$response" >&2
105+
fi
106+
@go.printf '\nPlease answer Y(es) or N(o): ' >&2
107+
fi
108+
done
109+
}
110+
111+
# Prompts the user to select one item from a list of options.
112+
#
113+
# This is a thin wrapper around the `select` builtin command for
114+
# straightforward, single-option user prompts. If you need to do anything more
115+
# complex, use the `select` builtin command directly.
116+
#
117+
# This will prompt the user for a single input, returned in the caller-declared
118+
# variable identified by `result_var`. If the user enters an invalid option,
119+
# this will notify the user and prompt again. If the user terminates input (via
120+
# EOF, i.e. Ctrl-D), `result_var` will remain unchanged and the function will
121+
# return nonzero.
122+
#
123+
# Globals:
124+
# PS3 environment variable defining the selection prompt
125+
#
126+
# Arguments:
127+
# result_var: Name of the caller-declared variable used to store the option
128+
# ...: Strings representing options available for the user to select
129+
#
130+
# Returns:
131+
# zero if `result_var` contains the user's selection, nonzero otherwise
132+
@go.select_option() {
133+
@go.validate_identifier_or_die 'Input selection variable name' "$1"
134+
135+
local __go_selected_option
136+
select __go_selected_option in "${@:2}"; do
137+
case "$__go_selected_option" in
138+
'')
139+
@go.printf '"%s" is not a valid option.\n' "$REPLY" >&2
140+
;;
141+
*)
142+
printf -v "$1" -- '%s' "$__go_selected_option"
143+
break
144+
;;
145+
esac
146+
done
147+
[[ -n "$__go_selected_option" ]]
148+
}

lib/strings

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ export __GO_STRINGS_VALIDATION_SKIP_CALLERS=2
8282
printf -v "$2" -- '%s' "${*:3}"
8383
}
8484

85+
# Trims the leading and trailing whitespace from a string
86+
#
87+
# Arguments:
88+
# var_name: Name of the caller's variable containing the string to trim
89+
@go.trim() {
90+
@go.validate_identifier_or_die 'Input/output variable name' "$1"
91+
local __go_trim_input__="${!1}"
92+
93+
if [[ "$__go_trim_input__" =~ ^[[:space:]]+ ]]; then
94+
__go_trim_input__="${__go_trim_input__#${BASH_REMATCH[0]}}"
95+
fi
96+
if [[ "$__go_trim_input__" =~ [[:space:]]+$ ]]; then
97+
__go_trim_input__="${__go_trim_input__%${BASH_REMATCH[0]}}"
98+
fi
99+
printf -v "$1" -- '%s' "$__go_trim_input__"
100+
}
101+
85102
# Determines the common prefix for a set of strings
86103
#
87104
# Will return the empty string for a single argument. This facilitates prefix

libexec/demo-core.d/prompt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#! /usr/bin/env bash
2+
#
3+
# Demonstration of `lib/prompt` capabilities
4+
#
5+
# Usage:
6+
# {{go}} {{cmd}}
7+
#
8+
# Use this program to get a feel for the core `@go.prompt_for_input` and
9+
# `@go.prompt_for_yes_or_no` functions, and for examples of how to use them in
10+
# your own scripts.
11+
12+
. "$_GO_USE_MODULES" 'prompt'
13+
14+
_@go.prompt_demo() {
15+
local name
16+
local quest='To seek the grail!'
17+
18+
# No default value; returns error on no input.
19+
if ! @go.prompt_for_input 'name' $'What is your name?\n' '' \
20+
'Run away, Sir or Madam Not Appearing in this Film! Run away!'; then
21+
return 1
22+
fi
23+
@go.printf 'Nice to meet you, %s!\n' "$name"
24+
25+
if @go.prompt_for_yes_or_no 'Do you have a quest?' 'yes'; then
26+
# Default value applies if input is empty.
27+
if ! @go.prompt_for_input 'quest' $'What is your quest?\n' "$quest"; then
28+
return 1
29+
fi
30+
elif ! @go.prompt_for_yes_or_no "Might I suggest: $quest" 'yes'; then
31+
@go.printf 'OK, no quest. Suit yourself!\n'
32+
return 1
33+
fi
34+
@go.printf 'Your quest is: %s\n' "$quest"
35+
}
36+
37+
_@go.prompt_demo "$@"

libexec/demo-core.d/select-option

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,30 @@
1212
# Use this program to get a feel for the core `@go.select_option` function, and
1313
# for an example of how to use it in your own scripts.
1414

15+
. "$_GO_USE_MODULES" 'prompt'
16+
1517
select_option_demo() {
1618
local options=("$@")
1719
local selected
18-
local finish
1920

2021
if [[ "${#options[@]}" -eq '0' ]]; then
2122
options=('Hello, World!' 'Goodbye, World!')
2223
fi
2324

24-
while [[ -z "$finish" ]]; do
25+
while true; do
2526
@go.printf 'Please select one of the following options:\n'
2627

27-
if @go.select_option 'selected' "${options[@]}"; then
28-
@go.printf 'You selected: "%s"\n\n' "$selected"
28+
if ! @go.select_option 'selected' "${options[@]}"; then
29+
@go.printf 'You declined to select an option. Exiting...\n\n'
30+
return 1
2931
else
30-
@go.printf 'You declined to select an option.\n\n'
32+
@go.printf 'You selected: "%s"\n\n' "$selected"
3133
fi
3234

33-
@go.printf 'Would you like to select another option?\n'
34-
35-
if @go.select_option 'selected' 'Yes' 'No'; then
36-
if [[ "$selected" == 'No' ]]; then
37-
@go.printf 'Exiting...\n'
38-
finish='true'
39-
else
40-
printf '\n'
41-
fi
42-
else
43-
@go.printf 'You declined to select an option. Exiting...\n'
44-
finish='true'
35+
if ! @go.prompt_for_yes_or_no 'Would you like to select another option?' \
36+
'yes'; then
37+
@go.printf 'Exiting...\n'
38+
return 0
4539
fi
4640
done
4741
}

tests/demo-core/prompt.bats

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#! /usr/bin/env bats
2+
3+
load ../environment
4+
5+
setup() {
6+
test_filter
7+
}
8+
9+
teardown() {
10+
@go.remove_test_go_rootdir
11+
}
12+
13+
@test "$SUITE: use default quest option" {
14+
run "$_GO_SCRIPT" demo-core prompt <<<$'Mike\n\n'
15+
assert_success
16+
split_bats_output_into_lines
17+
18+
local default_quest='Do you have a quest? [Y/n] '
19+
default_quest+='What is your quest? [default: To seek the grail!]'
20+
21+
assert_lines_equal 'What is your name?' \
22+
'Nice to meet you, Mike!' \
23+
"${default_quest}" \
24+
'Your quest is: To seek the grail!'
25+
}
26+
27+
@test "$SUITE: return error if name not specified" {
28+
run "$_GO_SCRIPT" demo-core prompt <<<''
29+
assert_failure 'What is your name?' \
30+
'Run away, Sir or Madam Not Appearing in this Film! Run away!'
31+
}
32+
33+
@test "$SUITE: specify a different quest" {
34+
run "$_GO_SCRIPT" demo-core prompt \
35+
<<<$'Mike\nyes\nTo go back and face the peril!'
36+
assert_success
37+
split_bats_output_into_lines
38+
39+
local default_quest='Do you have a quest? [Y/n] '
40+
default_quest+='What is your quest? [default: To seek the grail!]'
41+
42+
assert_lines_equal 'What is your name?' \
43+
'Nice to meet you, Mike!' \
44+
"${default_quest}" \
45+
'Your quest is: To go back and face the peril!'
46+
}
47+
48+
@test "$SUITE: decline a quest" {
49+
run "$_GO_SCRIPT" demo-core prompt <<<$'Mike\nno\nno'
50+
assert_failure
51+
split_bats_output_into_lines
52+
53+
local default_quest='Do you have a quest? [Y/n] '
54+
default_quest+='Might I suggest: To seek the grail! [Y/n]'
55+
56+
assert_lines_equal 'What is your name?' \
57+
'Nice to meet you, Mike!' \
58+
"${default_quest} OK, no quest. Suit yourself!"
59+
}
60+
61+
@test "$SUITE: suggest a quest and accept it" {
62+
run "$_GO_SCRIPT" demo-core prompt <<<$'Mike\nno\nyes'
63+
assert_success
64+
split_bats_output_into_lines
65+
66+
local default_quest='Do you have a quest? [Y/n] '
67+
default_quest+='Might I suggest: To seek the grail! [Y/n]'
68+
69+
assert_lines_equal 'What is your name?' \
70+
'Nice to meet you, Mike!' \
71+
"${default_quest} Your quest is: To seek the grail!"
72+
}

0 commit comments

Comments
 (0)