Skip to content

Commit e7f3391

Browse files
committed
redo tty target handling
- redo terminal capability handling - merge `debug-terminal-stdin` and `debug-terminal-tty` into `debug-terminal` which is simpler, yet more advanced, and clearly showcases and consolidates terminal capaiblities - add new `echo-transpose` which is used by `debug-terminal` - `ask`/`choose`/`confirm`/`dorothy`/`read-key` correct their capability detection, using the appropriate variable for the capabilities they need - `get-terminal-*`: simplify checks - redo tty and debug target handling - `eval-tester` can now and will now test stderr on all environments and tty (before stderr was only locally, not CI/no-TTY envs; and TTY was not able to be tested) - `eval-tester` will now also show elapsed time for each test - add semlock helpers, and consolidated semlock/semaphore functionality - discover mapfile doesn't support `-d` on bash versions earlier than 4.4 - introduce WIP `bash.bash:__split` to replace `mapfile` due to this unpatchable deviation - `down`: tests now work due to these changes - `eval-helper`: correct `--` hadnling for `--elevate --inherit` commands - `is-mac`: turns out bash provides `OSTYPE` global var, so there is no need for process invocation - `is-user:` note some bash globals that could be used instead - `setup-shell`: correct URL for dash - `waiter`: add `--message-target=...` support - `versions.md`: dodcument mapfile, `BASH_XTRACEFD`, `wait`, and other changes - `bash.bash`: - add terminal capabilities - add semlock, fd, split, and epoch-time helpers - move version number definitions to the top, as they were too late - fix bugs in `__get_substring`
1 parent d6440c4 commit e7f3391

37 files changed

Lines changed: 1089 additions & 629 deletions

commands.deprecated/is-tty

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ function is_tty() (
4747
# correctly, safely, and without side-effects, determine if the TTY is attached, readable, and writable
4848
# note that &>/dev/null is only possible for checking TTY (checking stdout/stderr that way will affect stdout/stderr, it is an observer effect)
4949
if [[ $option_fallback == 'yes' ]]; then
50-
__print_lines "$TERMINAL_DEVICE_FILE"
50+
__print_lines "$TERMINAL_OUTPUT_TARGET"
5151
else
52-
__has_tty_support
52+
get-terminal-tty-support --quiet
5353
fi
5454
)
5555

commands/ask

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,16 @@ function ask_() (
233233
local RESULT="$option_default"
234234

235235
# adjust tty
236-
local terminal_reactive inline
237-
terminal_reactive="$(get-terminal-reactivity-support)"
238-
if [[ $terminal_reactive == 'no' || $option_inline == 'no' || $BASH_CAN_READ_I != 'yes' ]]; then
236+
local inline
237+
if [[ $option_inline == 'no' || $BASH_CAN_READ_I != 'yes' ]]; then
239238
inline='no'
240239
else
241240
inline='yes'
242241
fi
243242

244243
# adjust prompt
245244
local input_prompt_and_newline=''
246-
if [[ $terminal_reactive == 'no' && -n $option_default ]]; then
245+
if [[ $IS_STDIN_LINE_BUFFERED == 'yes' && -n $option_default ]]; then
247246
input_prompt_and_newline="Press ${style__key}ENTER${style__end__key} to use the default value of ${style__code}${option_default}${style__end__code}. Press ${style__key}ESC${style__end__key} then ${style__key}ENTER${style__end__key} to use no value."$'\n'
248247
fi
249248

commands/choose

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,6 @@ function choose_() (
10671067
paging_supported='no'
10681068
fi
10691069
if ! get-terminal-title-support --quiet; then
1070-
# [ssh -T ...] passes [! -t 0] ["$TERMINAL_DEVICE_FILE" = '/dev/stderr'] [__command_exists tput]
10711070
title_supported='no'
10721071
fi
10731072

commands/confirm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ function confirm_() (
341341
# prep clearing of any input that leaked
342342
question_confirm_and_input_lines=''
343343
# only applies in line-buffer mode
344-
if ! __has_tty_support; then
344+
if [[ $IS_STDIN_LINE_BUFFERED == 'yes' ]]; then
345345
for key in "${keys[@]}"; do
346346
if [[ $key =~ ^(enter|line-buffer)$ ]]; then
347347
question_confirm_and_input_lines+=$'\n'

commands/debug-terminal

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
#!/usr/bin/env bash
2+
3+
# prepare
4+
self="${BASH_SOURCE[0]}"
5+
if [[ -z ${DOROTHY-} ]]; then
6+
export DOROTHY
7+
DOROTHY="${XDG_DATA_HOME:-"$HOME/.local/share"}/dorothy"
8+
source "$DOROTHY/sources/environment.sh"
9+
fi
10+
source "$DOROTHY/sources/bash.bash"
11+
if [[ -z $* ]]; then
12+
test='--normal'
13+
else
14+
test="$*"
15+
if is-ci; then
16+
test+=' --within-ci'
17+
fi
18+
test="${test//--no-recurse/}"
19+
while [[ $test == *' ' ]]; do
20+
test="$(__get_substring "$test" 0 -1)"
21+
done
22+
while [[ $test == ' '* ]]; do
23+
test="$(__get_substring "$test" 1)"
24+
done
25+
fi
26+
result_file="$(fs-temp --directory='debug-terminal' --file='debug-terminal.tsv')"
27+
28+
# process action
29+
if [[ -z $* || $1 == '--normal' || $* == *'--no-recurse'* ]]; then
30+
:
31+
elif [[ $1 == '--all' ]]; then
32+
variations=(
33+
'--normal'
34+
'--background'
35+
'--stdin-pipe'
36+
'--stdin-redirection'
37+
'--stdin-stdout-pipe'
38+
'--stdin-stdout-stderr-redirection'
39+
'--closed-stdin-stdout'
40+
'--delayed-stdin-pipe'
41+
'--delayed-stdin-redirection'
42+
'--immediate-and-delayed-stdin-pipe'
43+
'--immediate-and-delayed-stdin-redirection'
44+
)
45+
for variation in "${variations[@]}"; do
46+
"$self" "$variation" "${@:2}"
47+
done
48+
exit
49+
elif [[ $1 == '--test' ]]; then
50+
# erase the result file
51+
: >"$result_file"
52+
53+
# run all the variations
54+
"$self" --all "${@:2}"
55+
if ! is-ci; then
56+
"$self" --ssh "${@:2}"
57+
fi
58+
59+
# it goes header row, data row, header row, data row, ...
60+
# so we want to remove all header rows except the first one
61+
awk 'NR==1 || (NR%2==0)' "$result_file" >"$result_file.trimmed"
62+
63+
# transpose the columns and rows, the following breaks the file, so we have to do it ourself:
64+
# qsv transpose -d $'\t' ...
65+
echo-transpose --stdin <"$result_file.trimmed" >"$result_file.transposed"
66+
67+
# render the result
68+
if [[ $IS_STDIN_OPENED_ON_TERMINAL == 'no' || ${DEBUG-} == 'yes' ]]; then # is-ci; then
69+
echo-file --plain -- "$result_file.transposed"
70+
else
71+
csvlens -t "$result_file.transposed"
72+
fi
73+
exit
74+
elif [[ $1 == '--ssh' ]]; then
75+
ssh -T localhost "$self" --all --within-ssh
76+
exit
77+
elif [[ $1 == '--background' ]]; then
78+
"$self" "$@" --no-recurse &
79+
wait $! # just [wait] is bash v5
80+
exit
81+
elif [[ $1 == '--stdin-pipe' ]]; then
82+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0 | "$self" "$@" --no-recurse
83+
exit
84+
elif [[ $1 == '--stdin-redirection' ]]; then
85+
"$self" "$@" --no-recurse < <(printf '%s\n' 1 2 3 4 5 6 7 8 9 0)
86+
exit
87+
elif [[ $1 == '--stdin-stdout-pipe' ]]; then
88+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0 | "$self" "$@" --no-recurse | cat
89+
exit
90+
elif [[ $1 == '--stdin-stdout-stderr-redirection' ]]; then
91+
"$self" "$@" --no-recurse 2> >(cat) > >(cat) < <(printf '%s\n' 1 2 3 4 5 6 7 8 9 0)
92+
exit
93+
elif [[ $1 == '--closed-stdin-stdout' ]]; then
94+
"$self" "$@" --no-recurse >&- <&- # can't close stderr, as for some reason, it hangs: 2>&-
95+
# > BASH_XTRACEFD=1 debug-bash -x -- debug-terminal --closed-stdin-stdout-stderr --no-recurse 2>&-
96+
exit
97+
elif [[ $1 == '--delayed-stdin-pipe' ]]; then
98+
{
99+
sleep 5
100+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
101+
} | "$self" "$@" --no-recurse || __ignore_sigpipe # this will sigpipe fail as the command will finish before the sleep finishes
102+
exit
103+
elif [[ $1 == '--delayed-stdin-redirection' ]]; then
104+
"$self" "$@" --no-recurse < <(
105+
sleep 5
106+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
107+
)
108+
exit
109+
elif [[ $1 == '--immediate-and-delayed-stdin-pipe' ]]; then
110+
{
111+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
112+
sleep 5
113+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
114+
} | "$self" "$@" --no-recurse || __ignore_sigpipe # this will sigpipe fail as the command will finish before the sleep finishes
115+
exit
116+
elif [[ $1 == '--immediate-and-delayed-stdin-redirection' ]]; then
117+
"$self" "$@" --no-recurse < <(
118+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
119+
sleep 5
120+
printf '%s\n' 1 2 3 4 5 6 7 8 9 0
121+
)
122+
exit
123+
else
124+
echo "Unknown option: $1" >&2
125+
exit 22 # EINVAL 22 Invalid argument
126+
fi
127+
128+
# helpers
129+
check_keys=('test')
130+
check_values=("$test")
131+
function __check {
132+
check_keys+=("$1")
133+
printf '%s\t\t%s\n' "$test" "$1" >&2 || : # this will fail on our closed file descriptor test
134+
if eval "$1"; then
135+
check_values+=(OK)
136+
else
137+
check_values+=(FAIL)
138+
fi
139+
}
140+
function __finish {
141+
local result='' semlock_file
142+
result+="$(__join $'\t' -- "${check_keys[@]}")"$'\n'
143+
result+="$(__join $'\t' -- "${check_values[@]}")"$'\n'
144+
semlock_file="$(__get_semlock 'get-devices')"
145+
printf '%s' "$result" >>"$result_file"
146+
# @todo remove need for manual semlock tracking and do: `| echo-write --append -- "$result_file"` instead which will be updated to use a semlock
147+
rm -f "$semlock_file" || :
148+
}
149+
150+
function __file {
151+
[[ -e $1 ]] || return 2 # ENOENT 2 No such file or directory
152+
file "$1" || return 1
153+
}
154+
155+
__check 'ls -la /proc/self/fd'
156+
__check '__file /proc/self/fd/0'
157+
__check '__file /proc/self/fd/1'
158+
__check '__file /proc/self/fd/2'
159+
160+
__check 'ls -la /proc/$$/fdinfo'
161+
__check '__file /proc/$$/fdinfo/0'
162+
__check '__file /proc/$$/fdinfo/1'
163+
__check '__file /proc/$$/fdinfo/2'
164+
165+
__check 'ls -la /dev/fd'
166+
__check '__file /dev/fd/0'
167+
__check '__file /dev/fd/1'
168+
__check '__file /dev/fd/2'
169+
170+
__check 'ls -la /dev/pts'
171+
__check '__file /dev/pts/0'
172+
__check '__file /dev/pts/1'
173+
__check '__file /dev/pts/2'
174+
175+
__check '__file /dev/stdin'
176+
__check '__file /dev/stdout'
177+
__check '__file /dev/stderr'
178+
__check '__file /dev/tty'
179+
180+
# __check 'printf [%s] $SSH_CONNECTION'
181+
# __check 'printf [%s] $SSH_CLIENT'
182+
# __check 'printf [%s] $SSH_TTY'
183+
184+
# stdin can be output and input depending, whereas the rest in practice are only output
185+
__check '(: </dev/stdin >/dev/stdin)'
186+
__check '(: </dev/stdin)'
187+
__check '(: >/dev/stdin)'
188+
__check '(: </dev/stdout >/dev/stdout)'
189+
__check '(: </dev/stdout)'
190+
__check '(: >/dev/stdout)'
191+
__check '(: </dev/stderr >/dev/stderr)'
192+
__check '(: </dev/stderr)'
193+
__check '(: >/dev/stderr)'
194+
__check '(: </dev/tty >/dev/tty)'
195+
__check '(: </dev/tty)'
196+
__check '(: >/dev/tty)'
197+
198+
# True if FD is opened on a terminal.
199+
__check '[[ -t 0 ]]'
200+
__check '[[ -t 1 ]]'
201+
__check '[[ -t 2 ]]'
202+
203+
# True if file is a named pipe.
204+
__check '[[ -p /dev/stdin ]]'
205+
__check '[[ -p /dev/stdout ]]'
206+
__check '[[ -p /dev/stderr ]]'
207+
__check '[[ -p /dev/tty ]]' # This is always false
208+
209+
# True if file is character special.
210+
__check '[[ -c /dev/stdin ]]'
211+
__check '[[ -c /dev/stdout ]]'
212+
__check '[[ -c /dev/stderr ]]'
213+
__check '[[ -c /dev/tty ]]'
214+
215+
# True if file is block special.
216+
# These are always false
217+
# __check '[[ -b /dev/stdin ]]'
218+
# __check '[[ -b /dev/stdout ]]'
219+
# __check '[[ -b /dev/stderr ]]'
220+
# __check '[[ -b /dev/tty ]]'
221+
222+
# True if file is readable by you.
223+
__check '[[ -r /dev/stdin ]]'
224+
__check '[[ -r /dev/stdout ]]'
225+
__check '[[ -r /dev/stderr ]]'
226+
__check '[[ -r /dev/tty ]]'
227+
228+
# True if file is writable by you.
229+
__check '[[ -w /dev/stdin ]]'
230+
__check '[[ -w /dev/stdout ]]'
231+
__check '[[ -w /dev/stderr ]]'
232+
__check '[[ -w /dev/tty ]]'
233+
234+
# True if data is already available for reading.
235+
__check 'read -t 0 </dev/stdin'
236+
__check 'read -t 0 </dev/stdout' # This is always false
237+
__check 'read -t 0 </dev/stderr' # This is always false
238+
__check 'read -t 0 </dev/tty' # This is always false, but perhaps, if we preload some data, it will work
239+
240+
function __get_terminal_size {
241+
if [[ -n ${BASH_SUBSHELL-} && $BASH_SUBSHELL -ne 0 ]]; then
242+
return 76 # EPROCUNAVAIL 76 Bad procedure for program
243+
else
244+
# checkwinsize: If set, Bash checks the window size after each external (non-builtin) command and, if necessary, updates the values of LINES and COLUMNS. This option is enabled by default.
245+
shopt -s checkwinsize || return 1
246+
(:) # noop subshell which updates LINES and COLUMNS
247+
if [[ -n ${LINES-} && -n ${COLUMNS-} ]]; then
248+
printf "%s\n" "$LINES" "$COLUMNS" || :
249+
return 0
250+
else
251+
return 1
252+
fi
253+
fi
254+
}
255+
__check '__get_terminal_size'
256+
257+
function __get_terminal_cursor_position {
258+
local input_device_file="$1" line column
259+
IFS='[;' read -t 2 -srd R -p $'\e[6n' _ line column <"$input_device_file" || return 1
260+
if [[ -n ${line-} && -n ${column-} ]]; then
261+
printf "%s\n" "$line" "$column" || :
262+
return 0
263+
else
264+
return 1
265+
fi
266+
}
267+
__check '__get_terminal_cursor_position /dev/stdin'
268+
__check '__get_terminal_cursor_position /dev/stdout'
269+
__check '__get_terminal_cursor_position /dev/stderr'
270+
__check '__get_terminal_cursor_position /dev/tty'
271+
272+
# function __get_terminal_theme {
273+
# local input_device_file="$1" original_tty_settings REPLY=''
274+
# original_tty_settings="$(stty -g || :)"
275+
# stty raw -echo || return 1
276+
# read -n 24 -t 1 -rsp $'\e]11;?\a' <"$input_device_file" || return 1
277+
# stty "$original_tty_settings" || return 1
278+
# if [[ -n $REPLY ]]; then
279+
# printf "%s\n" "$REPLY"
280+
# return 0
281+
# else
282+
# return 1
283+
# fi
284+
# }
285+
# __check '__get_terminal_theme /dev/stdin'
286+
# __check '__get_terminal_theme /dev/stdout'
287+
# __check '__get_terminal_theme /dev/stderr'
288+
# __check '__get_terminal_theme /dev/tty'
289+
290+
__finish

0 commit comments

Comments
 (0)