Skip to content

Commit

Permalink
use the correct quote/escape for the context
Browse files Browse the repository at this point in the history
/ref https://github.com/bevry/dorothy/actions/runs/11512899768

also fix `echo-regexp` including `stdinargs.bash` when it didn't need to
  • Loading branch information
balupton committed Oct 25, 2024
1 parent d605711 commit d2b29cb
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 122 deletions.
4 changes: 1 addition & 3 deletions commands.beta/macos-state
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function macos_state() (
echo-style \
--error='Remote backup locations must manually specify the volume.' --newline \
--notice='Try again with something like:' --newline \
--code="macos-state $(echo-quote -- "$potential_root")" >/dev/stderr
--code="$(echo-escape-command -- 'macos-state' "$potential_root")" >/dev/stderr
return 1
else
# ask
Expand Down Expand Up @@ -227,7 +227,6 @@ function macos_state() (

if is-present -- "$from"; then
if test "$backup_type" = 'remote'; then
# @todo will "${from@Q}" work?
cpr -- "$from" "$to"
else
do_replace "$to"
Expand Down Expand Up @@ -256,7 +255,6 @@ function macos_state() (
tmutil restore -v "$from" "$to"
elif test "$backup_type" = 'remote'; then
do_replace "$to"
# @todo will "${from@Q}" work?
cpr -- "$from" "$to"
elif is-present -- "$from"; then
do_replace "$to"
Expand Down
2 changes: 1 addition & 1 deletion commands.beta/video-merge
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function video_merge() (
)"
for input in "${inputs[@]}"; do
if test -n "$input"; then
__print_lines "file $(echo-quote -- "$input")" >>"$temp_list"
__print_lines "file: $(echo-escape-command -- "$input")" >>"$temp_list"
fi
done
echo-file -- "$temp_list"
Expand Down
37 changes: 31 additions & 6 deletions commands/config-helper
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,33 @@ function config_helper() (
option_columns="${1#*=}"
shift
;;
'--no-quote')
option_quote='no'
'--quote=bash')
option_quote='bash'
shift
;;
'--quote=command')
option_quote='command'
shift
;;
'--quote=generic')
option_quote='generic'
shift
;;
'--no-quote'* | '--quote'*) # will also support quoted
option_quote="$(get-flag-value --affirmative --fallback="$option_quote" -- "$1")"
shift
;;
esac

# adjust quote based on file
if [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then
if [[ $option_file == *'.bash' || $option_file == *'.sh' ]]; then
option_quote='bash'
else
option_quote='generic'
fi
fi

# extract next argument, shift is handled later, as could ba another find
option_replace="${1-}"

Expand Down Expand Up @@ -327,11 +348,11 @@ function config_helper() (
mapfile -t lines <<<"$value" # read lines into an array, such that indentation works

# addition, use spaces for consistency with replacements, also use spaces as editors show tabs at variable widths
value=$'(\n'"$(echo-lines --indent=$'\t' --quoted --columns="${option_columns:-"1"}" --width=inputs --filler=space -- "${lines[@]}")"$'\n)'
value=$'(\n'"$(echo-lines --indent=$'\t' --quote="$option_quote" --columns="${option_columns:-"1"}" --width=inputs --filler=space -- "${lines[@]}")"$'\n)'
addition="$field=$value"

# replacement, use spaces as the filler, as without echo-lines being aware of the content of ${indent}, there is no way for it to correctly align tabs, also use spaces as editors show tabs at variable widths
value=$'(\n'"$(echo-lines --indent=$'${indent}\t' --quoted --columns="${option_columns:-"1"}" --width=inputs --filler=space -- "${lines[@]}")"$'\n${indent})'
value=$'(\n'"$(echo-lines --indent=$'${indent}\t' --quote="$option_quote" --columns="${option_columns:-"1"}" --width=inputs --filler=space -- "${lines[@]}")"$'\n${indent})'
replace_pattern="\${indent}$field=$value"

# reset columns
Expand All @@ -345,12 +366,16 @@ function config_helper() (
fi

# addition and replacement
if test "$option_quote" = 'yes'; then
if [[ $option_quote == 'bash' ]]; then
value="$(echo-escape-bash -- "$value")"
elif [[ $option_quote == 'command' ]]; then
value="$(echo-escape-command -- "$value")"
elif [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then
value="$(echo-quote -- "$value")"
fi
addition="$field=$value"
replace_pattern="\${indent}$field=$value"
option_quote='yes'
option_quote='yes' # reset it for next time
else
# the replacement field was a find value, do not shift it, as we will use it in the next cycle
# instead, fetch the value, then continue to use the field as the next find
Expand Down
12 changes: 6 additions & 6 deletions commands/dorothy
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@ function dorothy_() (
# apply
source "$DOROTHY/sources/config.sh"
dorothy-config 'interactive.sh' -- \
--find='export DOROTHY_THEME=(.*)' --replace="export DOROTHY_THEME=$(echo-quote -- "$theme")"
--find='export DOROTHY_THEME=(.*)' --replace="export DOROTHY_THEME=$(echo-escape-bash -- "$theme")"
if test -f "$DOROTHY/user/config/interactive.nu"; then
# dorothy-config 'interactive.nu' -- --string-find="\$env.DOROTHY_THEME" --string-replace="\$env.DOROTHY_THEME = $(echo-quote -- "$theme")"
# ^ don't use that, as theming will always be in the config not config.local
Expand Down Expand Up @@ -1906,11 +1906,11 @@ function dorothy_() (
source "$DOROTHY/sources/ripgrep.bash"

# run relevant debugs
__print_lines '' 'debug-terminal-stdin:'
debug-terminal-stdin || :
__print_lines '' 'debug-terminal-tty:'
debug-terminal-tty || :
__print_lines ''
# __print_lines '' 'debug-terminal-stdin:'
# debug-terminal-stdin || :
# __print_lines '' 'debug-terminal-tty:'
# debug-terminal-tty || :
# __print_lines ''

# able to test on bash v3?
local bash has_macos_bash_v3='no'
Expand Down
2 changes: 1 addition & 1 deletion commands/dorothy-config
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function dorothy_config_test() (
-- eval-no-color -- dorothy-config --packages-var='DOROTHY_CONFIG_TESTING_LIST' --prefer=public -- "${invented_packages[@]}" "${util_packages[@]}"

# read DOROTHY_CONFIG_TESTING_LIST
expected_stdout=$'(\n'"$(echo-lines --quote --indent=$'\t' -- "${invented_packages[@]}")"$'\n)'
expected_stdout=$'(\n'"$(echo-lines --quote=bash --indent=$'\t' -- "${invented_packages[@]}")"$'\n)'
eval-tester --name='read packages' --stdout="$expected_stdout" --ignore-stderr \
-- eval-no-color -- config-helper --file="$expected_file" -- --field='DOROTHY_CONFIG_TESTING_LIST'

Expand Down
46 changes: 46 additions & 0 deletions commands/echo-escape-bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash

function echo_escape_bash() (
source "$DOROTHY/sources/stdinargs.bash"

# =====================================
# Arguments

function help {
cat <<-EOF >/dev/stderr
ABOUT:
Escape the <...input> for usage inside bash.
USAGE:
echo-escape-bash [...options] [--] ...<input>
echo-lines ...<input> | echo-escape-bash [...options]
OPTIONS:
$(stdinargs_options_help --)
EOF
return 22 # EINVAL 22 Invalid argument
}

# =====================================
# Action

# we could use @Q for this, but it is strange, so just use %q\n
# bash-5.2$ printf '%q\n' " a'"
# \ a\'
# bash-5.2$ b=\ a\'
# bash-5.2$ echo "[$b]"
# [ a']
# bash-5.2$ echo "${b@Q}"
# ' a'\'''

function on_input {
printf '%q\n' "$@"
}

stdinargs "$@"
)

# fire if invoked standalone
if test "$0" = "${BASH_SOURCE[0]}"; then
echo_escape_bash "$@"
fi
38 changes: 15 additions & 23 deletions commands/echo-escape-command
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

function echo_escape_command() (
source "$DOROTHY/sources/bash.bash"
source "$DOROTHY/sources/stdinargs.bash"

# =====================================
# Arguments
Expand All @@ -12,35 +12,27 @@ function echo_escape_command() (
Escape the <...command> for human readability.
USAGE:
echo-escape-command [--] <...command>
echo-escape-command [...options] [--] <...command>
echo-lines <...command> | echo-escape-command [...options]
OPTIONS:
$(stdinargs_options_help --)
EOF
return 22 # EINVAL 22 Invalid argument
}

# process
local item option_cmd=()
while test "$#" -ne 0; do
item="$1"
shift
case "$item" in
'--help' | '-h') help ;;
'--')
option_cmd+=("$@")
shift $#
break
;;
*)
option_cmd+=("$item" "$@")
shift $#
break
;;
esac
done

# =====================================
# Action

__quote -- "${option_cmd[@]}" | echo-join ' ' --stdin
local args=()
function on_input {
args+=("$@")
}
function on_finish {
printf '%q\n' "${args[@]}" | echo-join ' ' --stdin
}

stdinargs "$@"
)

# fire if invoked standalone
Expand Down
25 changes: 18 additions & 7 deletions commands/echo-lines
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ function echo_lines_test() (
__print_lines e3
__print_lines f3
} | eval-tester --name='args quoted, ignoring stdin' --stdout=$'\'a\'\n\'b\'\n\'c\'\n\'d\'' \
-- echo-lines --quoted -- a b c d
-- echo-lines --quote -- a b c d

{
__print_lines e4
__print_lines f4
} | eval-tester --name='args quoted, with stdin' --stdout=$'\'a\'\n\'b\'\n\'c\'\n\'d\'\n\'e4\'\n\'f4\'' \
-- echo-lines --stdin --quoted -- a b c d
-- echo-lines --stdin --quote -- a b c d

{
__print_lines e5
Expand Down Expand Up @@ -104,7 +104,7 @@ function echo_lines_test() (
-- echo-lines --stdin --columns=2 --filler=tab --width=inputs --indent='x' --prefix='[' --suffix=']' -- aaaa b ccc d

eval-tester --name='double tab indent' --stdout=$'\t\t\'double "quote" inside\'\t\t"single \'quote\' inside"\t\t\'3\'\n\t\t\'4\'\t\t\t\t\'5\'\t\t\t\t\'6\'' \
-- echo-lines --columns=3 --filler=tab --width=inputs --quoted --indent=$'\t\t' -- 'double "quote" inside' "single 'quote' inside" 3 4 5 6
-- echo-lines --columns=3 --filler=tab --width=inputs --quote --indent=$'\t\t' -- 'double "quote" inside' "single 'quote' inside" 3 4 5 6

{
__print_lines e15
Expand Down Expand Up @@ -150,7 +150,7 @@ function echo_lines_test() (
-- echo-lines --stdin --columns=2 --filler=space --width=inputs --indent='x' --prefix='[' --suffix=']' -- aaaa b ccc d

eval-tester --name='double tab indent' --stdout=$'\t\t\'double "quote" inside\' "single \'quote\' inside" \'3\'\n\t\t\'4\' \'5\' \'6\'' \
-- echo-lines --columns=3 --filler=space --width=inputs --quoted --indent=$'\t\t' -- 'double "quote" inside' "single 'quote' inside" 3 4 5 6
-- echo-lines --columns=3 --filler=space --width=inputs --quote --indent=$'\t\t' -- 'double "quote" inside' "single 'quote' inside" 3 4 5 6

echo-style --g1="TEST: $0"
return 0
Expand Down Expand Up @@ -210,7 +210,7 @@ function echo_lines() (
}

# process our own arguments, delegate everything else to stdinargs
local item option_columns=1 option_width='terminal' option_filler=' ' option_distance=2 option_spread='yes' option_shrink='yes' option_indent='' option_prefix='' option_suffix='' option_quoted='no' option_args=()
local item option_columns=1 option_width='terminal' option_filler=' ' option_distance=2 option_spread='yes' option_shrink='yes' option_indent='' option_prefix='' option_suffix='' option_quote='no' option_args=()
while test "$#" -ne 0; do
item="$1"
shift
Expand All @@ -232,8 +232,11 @@ function echo_lines() (
'--no-shrink'* | '--shrink'*)
option_shrink="$(get-flag-value --affirmative --fallback="$option_shrink" -- "$item")"
;;
'--quote=bash') option_quote='bash' ;;
'--quote=command') option_quote='command' ;;
'--quote=generic') option_quote='generic' ;;
'--no-quote'* | '--quote'*) # will also support quoted
option_quoted="$(get-flag-value --affirmative --fallback="$option_quoted" -- "$item")"
option_quote="$(get-flag-value --affirmative --fallback="$option_quote" -- "$item")"
;;
# forward to stdinargs, however support mixing and matching of our options, with stdinarg options
'--')
Expand All @@ -250,7 +253,15 @@ function echo_lines() (

# quote the arguments if desired
local items=()
if test "$option_quoted" = 'yes'; then
if [[ $option_quote == 'bash' ]]; then
function on_input {
items+=("$option_prefix$(echo-escape-bash -- "$1")$option_suffix")
}
elif [[ $option_quote == 'command' ]]; then
function on_input {
items+=("$option_prefix$(echo-escape-command -- "$1")$option_suffix")
}
elif [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then
function on_input {
items+=("$option_prefix$(echo-quote -- "$1")$option_suffix")
}
Expand Down
64 changes: 47 additions & 17 deletions commands/echo-quote
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function echo_quote() (
function help {
cat <<-EOF >/dev/stderr
ABOUT:
For each <input>, output it as a quoted string.
For each <input>, output it as a quoted string, with escaping using backslashes, which is not compatible with bash.
USAGE:
echo-quote [...options] [--] ...<input>
Expand Down Expand Up @@ -125,24 +125,54 @@ function echo_quote() (
# Action

# this is not the same as ${var@Q}, which handles single quotes differently
# trunk-ignore(shellcheck/SC1003)
local s="'" d='"' e='\'
function on_input {
local item="$1"
if [[ $item != *"'"* ]] && test "$option_quote_desired" != 'double'; then
# does not contain single quotes
__print_lines "'$item'"
return 0
elif [[ $item != *'"'* ]] && test "$option_quote_desired" != 'single'; then
# does not contain double quotes
__print_lines "\"$item\""
return 0
elif [[ $item != *"\\'"* ]] && test "$option_quote_desired" != 'double'; then
# does not contain escaped single quotes
__print_lines "'${item//\'/\\\'}'"
return 0
elif [[ $item != *"\\\""* ]] && test "$option_quote_desired" != 'single'; then
# does not contain escaped double quotes
__print_lines "\"${item//\"/\\\"}\""
return 0

# check preference
# there is a simpler way of doing this, however it is more difficult to follow
if [[ -z $option_quote_desired ]]; then
# we have no preference, be simple
if [[ $item != *"$s"* ]]; then
# it does not contain single quotes, so wrap in single quotes
__print_lines "$s$item$s"
return 0
elif [[ $item != *"$d"* ]]; then
# it contains single quotes, but it does not contain double quotes, so wrap in double quotes
__print_lines "$d$item$d"
return 0
elif [[ $item != *"$e$s"* ]]; then
# it contains both single and double quotes, but it does not contain escaped single quotes, so escape the single quotes and wrap in single quotes
__print_lines "$s${item//$s/$e$s}$s"
return 0
elif [[ $item != *"$e$d"* ]]; then
# it contains single quotes, double quotes, and escaped double quotes, but it does not contain escaped double quotes, so escape the double quotes and wrap in double quotes
__print_lines "$d${item//$d/$e$d}$d"
return 0
fi
elif [[ $option_quote_desired == 'double' ]]; then
# we want double quotes
if [[ $item != *"$d"* ]]; then
# it does not contain double quotes, so wrap in double quotes
__print_lines "$d$item$d"
return 0
elif [[ $item != *"$e$d"* ]]; then
# it contains double quotes, but it does not contain escaped double quotes, so escape the double quotes and wrap in double quotes
__print_lines "$d${item//$d/$e$d}$d"
return 0
fi
elif [[ $option_quote_desired == 'single' ]]; then
# we want single quotes
if [[ $item != *"$s"* ]]; then
# it does not contain single quotes, so wrap in single quotes
printf '%s\n' "$s$item$s"
return 0
elif [[ $item != *"$e$s"* ]]; then
# it contains single quotes, but it does not contain escaped single quotes, so escape the single quotes and wrap in single quotes
printf '%s\n' "$s${item//$s/$e$s}$s"
return 0
fi
fi

# handle failure case, where it contains both escaped single and escaped double quotes: echo-quote -- "a\\'s\\\"a\\\"a\\'d"
Expand Down
Loading

0 comments on commit d2b29cb

Please sign in to comment.