From 70c63d7f86c78c120469513889eec1fbfef566d5 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Tue, 20 Feb 2024 12:54:09 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=92=A8=20mommy=20tries=20to=20speed?= =?UTF-8?q?=20up=20again~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/sh/mommy | 84 ++++++++++++++++++++++----------- src/test/helper/spec_helper.sh | 2 +- src/test/sh/integration_spec.sh | 6 +-- src/test/sh/unit_spec.sh | 2 +- 4 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/main/sh/mommy b/src/main/sh/mommy index 550b7c0..04c283f 100755 --- a/src/main/sh/mommy +++ b/src/main/sh/mommy @@ -73,7 +73,7 @@ MOMMY_IGNORED_STATUSES="130" ## Input validation # Writes whitespace-concatenated input arguments to stderr and exits. -die() { echo "$*" >&2; exit 1; } +die() { printf "%s\n" "$*" >&2; exit 1; } # Dies if `$OPTARG` is an empty string. require_arg() { if [ -z "$OPTARG" ]; then die "mommy is missing the argument for option '$OPT'~"; fi } @@ -81,7 +81,7 @@ require_arg() { if [ -z "$OPTARG" ]; then die "mommy is missing the argument for # Dies if `$OPTARG` is not a non-negative integer. require_int() { case "$OPTARG" in - ''|*[!0-9]*) die "mommy expected the argument for option '$OPT' to be an integer, but it was '$OPTARG'~" ;; + ""|*[!0123456789]*) die "mommy expected the argument for option '$OPT' to be an integer, but it was '$OPTARG'~" ;; *) ;; esac } @@ -93,12 +93,42 @@ require_int() { # on that line are part of the comment and are not considered separators. A sanitized list is a list in which there are # no forward slashes (`/`) and in which there are no comments. +# Replaces in `$1` any character in `$2` by a newline. +split() { + test "${-#*f}" != "$-" + globbing_enabled="$?" + + set -f + old_ifs="$IFS" + IFS="$2" + + set -- $1 # Do not quote this! + printf "%s\n" "$@" + + IFS="$old_ifs" + if [ "$globbing_enabled" != "0" ]; then set +f; fi +} + +# Returns `0` if and only if `$1` consists of more than just whitespace. +is_blank() { + trim="${1#${1%%[![:space:]]*}}" + trim="${trim%${trim##*[![:space:]]}}" + test -z "$trim" + return "$?" +} + +# Returns `0` if and only if `$1` starts with a `#`. +is_comment() { + test -z "${1##\#*}" + return "$?" +} + # Returns `0` if the string `$1` contains any of the entries in the sanitized list `$2` as a substring, and returns `1` # otherwise. list_contains_any() { if [ -z "$2" ]; then return 1; fi - echo "$2" | while IFS="$n" read -r needle; do [ -z "${1##*"$needle"*}" ] && return 0; done + printf "%s\n" "$2" | while IFS="$n" read -r needle; do [ -z "${1##*"$needle"*}" ] && return 0; done return "$?" } @@ -107,10 +137,10 @@ list_contains_any() { # substring. list_normalize() { cat | - grep -v "^#" | - tr "/" "$n" | - grep -v "^[[:space:]]*\$" | - while IFS="$n" read -r line; do list_contains_any "$line" "$1" || echo "$line"; done + while IFS="$n" read -r line; do if ! is_comment "$line"; then split "$line" "/"; fi; done | + while IFS="$n" read -r line; do + if ! is_blank "$line" && ! list_contains_any "$line" "$1"; then printf "%s\n" "$line"; fi + done return 0 } @@ -122,9 +152,9 @@ if [ -x "$(command -v shuf)" ]; then else list_choose() { lines="$(cat)" - count="$(echo "$lines" | wc -l)" + count="$(printf "%s\n" "$lines" | wc -l)" idx="$(jot -r 1 1 "$count")" - echo "$lines" | sed "${idx}q;d" + printf "%s\n" "$lines" | sed "${idx}q;d" } fi @@ -139,19 +169,19 @@ escape_sed_replacement() { ## Templates # Prints `$2`, but with color depending on `$1`. If `$1` equals `lolcat`, stdin is piped to `lolcat`. If `$1` is empty, # or color is not enabled in the terminal, stdin is printed normally. Otherwise, `$1` is used as the xterm color. -color_echo() { +color_print() { colors="$(tput colors 2>/dev/null)" if [ "$1" = "lolcat" ]; then - echo "$2" | lolcat -f + printf "%s\n" "$2" | lolcat -f elif [ -z "$1" ] || [ -z "$colors" ] || [ "$colors" -lt 8 ]; then - echo "$2" + printf "%s\n" "$2" else # Work around OpenBSD bug https://www.mail-archive.com/bugs@openbsd.org/msg18443.html tput setaf 0 1>/dev/null 2>/dev/null || tput_bug="0 0" # shellcheck disable=SC2086 # Intentional word splitting: OpenBSD workaround requires two arguments - echo "$(tput setaf "$1" $tput_bug)$2$(tput sgr0)" + printf "%s\n" "$(tput setaf "$1" $tput_bug)$2$(tput sgr0)" fi return 0 } @@ -162,10 +192,10 @@ capitalize() { case "$2" in 0) mapping="tolower" ;; 1) mapping="toupper" ;; - *) echo "$1"; return 0 ;; + *) printf "%s\n" "$1"; return 0 ;; esac - echo "$1" | awk "{ print $mapping(substr(\$0, 1, 1)) substr(\$0, 2) }" + printf "%s\n" "$1" | awk "{ print $mapping(substr(\$0, 1, 1)) substr(\$0, 2) }" return 0 } @@ -192,12 +222,12 @@ split_pronouns() { # 4. prepends `$4` and appends `$5`; and # 5. writes to stdout. fill_template() { - sweetie="$(echo "$1" | list_normalize | list_choose | escape_sed_replacement)" - split_pronouns "$(echo "$2" | list_normalize | list_choose | escape_sed_replacement)" - caregiver="$(echo "$3" | list_normalize | list_choose | escape_sed_replacement)" + sweetie="$(printf "%s\n" "$1" | list_normalize | list_choose | escape_sed_replacement)" + split_pronouns "$(printf "%s\n" "$2" | list_normalize | list_choose | escape_sed_replacement)" + caregiver="$(printf "%s\n" "$3" | list_normalize | list_choose | escape_sed_replacement)" - prefix="$(echo "$4" | list_normalize | list_choose | escape_sed_replacement)" - suffix="$(echo "$5" | list_normalize | list_choose | escape_sed_replacement)" + prefix="$(printf "%s\n" "$4" | list_normalize | list_choose | escape_sed_replacement)" + suffix="$(printf "%s\n" "$5" | list_normalize | list_choose | escape_sed_replacement)" template="$(cat | sed -e "s/%%SWEETIE%%/$sweetie/g" \ -e "s/%%THEY%%/$they/g" \ @@ -278,7 +308,7 @@ if [ -n "$opt_help" ]; then fi exit "$status" elif [ -n "$opt_version" ]; then - echo "mommy, v%%VERSION_NUMBER%%, %%VERSION_DATE%%" + printf "%s\n" "mommy, v%%VERSION_NUMBER%%, %%VERSION_DATE%%" exit 0 else # Run command @@ -293,7 +323,7 @@ else fi # Choose and fill template (if enabled) - if list_contains_any "$command_exit_code" "$(echo "$MOMMY_IGNORED_STATUSES" | list_normalize)"; then + if list_contains_any "$command_exit_code" "$(printf "%s\n" "$MOMMY_IGNORED_STATUSES" | list_normalize)"; then exit "$command_exit_code" elif [ "$command_exit_code" -eq 0 ] && [ "$MOMMY_COMPLIMENTS_ENABLED" = "1" ]; then templates="$MOMMY_COMPLIMENTS/$MOMMY_COMPLIMENTS_EXTRA" @@ -303,10 +333,10 @@ else exit "$command_exit_code" fi - color="$(echo "$MOMMY_COLOR" | list_normalize | list_choose)" - forbidden_words="$(echo "$MOMMY_FORBIDDEN_WORDS" | list_normalize)" + color="$(printf "%s\n" "$MOMMY_COLOR" | list_normalize | list_choose)" + forbidden_words="$(printf "%s\n" "$MOMMY_FORBIDDEN_WORDS" | list_normalize)" - response="$(echo "$templates" | + response="$(printf "%s\n" "$templates" | list_normalize "$forbidden_words" | list_choose | fill_template "$MOMMY_SWEETIE" "$MOMMY_PRONOUNS" "$MOMMY_CAREGIVER" "$MOMMY_PREFIX" \ @@ -314,8 +344,8 @@ else # Output template case "$opt_target" in - 1) color_echo "$color" "$response" >&1 ;; - 2) color_echo "$color" "$response" >&2 ;; + 1) color_print "$color" "$response" >&1 ;; + 2) color_print "$color" "$response" >&2 ;; esac exit "$command_exit_code" diff --git a/src/test/helper/spec_helper.sh b/src/test/helper/spec_helper.sh index 180a88e..3aaedec 100644 --- a/src/test/helper/spec_helper.sh +++ b/src/test/helper/spec_helper.sh @@ -21,7 +21,7 @@ export MOMMY_TMP_DIR export n=" " -strip_opt() { echo "$1" | sed "s/[-= ]//g"; } +strip_opt() { printf "%s\n" "$1" | sed "s/[-= ]//g"; } ## Hooks diff --git a/src/test/sh/integration_spec.sh b/src/test/sh/integration_spec.sh index 711b20b..82f7d1b 100644 --- a/src/test/sh/integration_spec.sh +++ b/src/test/sh/integration_spec.sh @@ -104,11 +104,11 @@ Describe "integration of mommy with other programs" Skip if "zsh is skipped or not installed" zsh_is_skipped_or_not_installed zsh_before_each() { - echo "source '$(pwd)/../resources/zsh_loader.zsh'" > "$MOMMY_ZSH_PREAMBLE_FILE" + printf "source '%s/../resources/zsh_loader.zsh'\n" "$(pwd)" > "$MOMMY_ZSH_PREAMBLE_FILE" if [ "$MOMMY_SYSTEM" != "1" ]; then - echo "FPATH='$(pwd)/../../main/completions/zsh/:'\"\$FPATH\"" >> "$MOMMY_ZSH_PREAMBLE_FILE" + printf "FPATH='%s/../../main/completions/zsh/:'\"\$FPATH\"\n" "$(pwd)" >> "$MOMMY_ZSH_PREAMBLE_FILE" fi - echo "autoload -U compinit; compinit -u" >> "$MOMMY_ZSH_PREAMBLE_FILE" + printf "autoload -U compinit; compinit -u\n" >> "$MOMMY_ZSH_PREAMBLE_FILE" } BeforeEach "zsh_before_each" diff --git a/src/test/sh/unit_spec.sh b/src/test/sh/unit_spec.sh index 101267c..a91ba49 100755 --- a/src/test/sh/unit_spec.sh +++ b/src/test/sh/unit_spec.sh @@ -8,7 +8,7 @@ # Writes `$1` to `$2` (the latter defaulting to `$MOMMY_CONFIG_FILE`), setting both `MOMMY_COLOR` and `MOMMY_SUFFIX` to # the empty string, unless overridden in `$1`. set_config() { - echo "MOMMY_COLOR='';MOMMY_SUFFIX='';$1" > "${2:-$MOMMY_CONFIG_FILE}" + printf "MOMMY_COLOR='';MOMMY_SUFFIX='';%s\n" "$1" > "${2:-$MOMMY_CONFIG_FILE}" } From 1aba647b7d8c7a089599ea3b3239ddcb277daf52 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Tue, 20 Feb 2024 21:48:47 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9A=80=20mommy=20is=20now=20almost=20?= =?UTF-8?q?4=20times=20as=20fast~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 + src/main/sh/mommy | 249 ++++++++++++++++++++++++++++------------------ 2 files changed, 155 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eff9fd..a81890f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog ## [unreleased] +### changed +* 🚀 mommy is now 4 times as fast~ ([#106](https://github.com/FWDekker/mommy/issues/106)) + + +## [1.4.0] -- 2024-02-17 ### added * 🌍 mommy now supports a global config file that applies to all users, stored for example in `/usr/mommy/config.sh`~ ([#95](https://github.com/FWDekker/mommy/issues/95)) ([#96](https://github.com/FWDekker/mommy/issues/96)) * 📏 mommy accepts long command-line options for all options (like `--config=` for `-c `)~ diff --git a/src/main/sh/mommy b/src/main/sh/mommy index 04c283f..0bb222f 100755 --- a/src/main/sh/mommy +++ b/src/main/sh/mommy @@ -76,44 +76,57 @@ MOMMY_IGNORED_STATUSES="130" die() { printf "%s\n" "$*" >&2; exit 1; } # Dies if `$OPTARG` is an empty string. -require_arg() { if [ -z "$OPTARG" ]; then die "mommy is missing the argument for option '$OPT'~"; fi } +require_arg() { if [ -z "$OPTARG" ]; then die "mommy is missing the argument for option '$OPT'~"; fi; return 0; } # Dies if `$OPTARG` is not a non-negative integer. require_int() { case "$OPTARG" in ""|*[!0123456789]*) die "mommy expected the argument for option '$OPT' to be an integer, but it was '$OPTARG'~" ;; - *) ;; + *) return 0 ;; esac } -## Lists -# A list is a collection of entries. Entries are separated by a forward slash (`/`) or by a newline. Entries containing -# only whitespace or starting with a `#` are considered comments and are ignored. If a line starts with a `#`, all `/` -# on that line are part of the comment and are not considered separators. A sanitized list is a list in which there are -# no forward slashes (`/`) and in which there are no comments. +## String manipulation +# Replaces in `$1` all occurrences of `$2` with `$3`, and writes to a variable named `$replace_all__out`. If the input +# has no trailing newline, neither will the output. +# Based on https://stackoverflow.com/a/75037170/ +replace_all() { + replace_all__remainder="$1" + replace_all__out="" -# Replaces in `$1` any character in `$2` by a newline. -split() { - test "${-#*f}" != "$-" - globbing_enabled="$?" + while [ -n "$replace_all__remainder" ]; do + replace_all__section="${replace_all__remainder%%"$2"*}" + + if [ "$replace_all__section" = "$replace_all__remainder" ]; then + replace_all__out="$replace_all__out$replace_all__remainder" + break + fi - set -f - old_ifs="$IFS" - IFS="$2" + replace_all__out="$replace_all__out$replace_all__section$3" + replace_all__remainder="${replace_all__remainder#*"$2"}" + done + + return 0 +} + +# Replaces in `$1` all occurrences of any single character in `$2` by a newline, and writes the resulting lines to +# variable `$split__out`, separated by a newline each, without a trailing newline. +split() { + test "${-#*f}" != "$-"; split__glob="$?"; set -f; split__ifs="$IFS"; IFS="$2" set -- $1 # Do not quote this! - printf "%s\n" "$@" + IFS="$n" + split__out="$*" - IFS="$old_ifs" - if [ "$globbing_enabled" != "0" ]; then set +f; fi + IFS="$split__ifs"; if [ "$split__glob" != "0" ]; then set +f; fi + return 0 } # Returns `0` if and only if `$1` consists of more than just whitespace. is_blank() { - trim="${1#${1%%[![:space:]]*}}" - trim="${trim%${trim##*[![:space:]]}}" - test -z "$trim" + is_blank__trimmed="${1#${1%%[![:space:]]*}}" + test -z "${is_blank__trimmed%${is_blank__trimmed##*[![:space:]]}}" return "$?" } @@ -123,46 +136,94 @@ is_comment() { return "$?" } -# Returns `0` if the string `$1` contains any of the entries in the sanitized list `$2` as a substring, and returns `1` +# Reads `$1`; if `$2` is `0`, the first character on the line is changed to lowercase, if `$2` is `1`, the first +# character is changed to uppercase, and otherwise nothing is changed; and stores the output in `$capitalize__out`. +capitalize() { + case "$2" in + 0) capitalize__mapping="tolower" ;; + 1) capitalize__mapping="toupper" ;; + *) capitalize__out="$1"; return 0 ;; + esac + + capitalize__out="$(printf "%s\n" "$1" | awk "{ print $capitalize__mapping(substr(\$0, 1, 1)) substr(\$0, 2) }")" + return 0 +} + + +## Lists +# A list is a collection of entries. Entries are separated by a forward slash (`/`) or by a newline. Entries containing +# only whitespace or starting with a `#` are considered comments and are ignored. If a line starts with a `#`, all `/` +# on that line are part of the comment and are not considered separators. A normalized list is a list in which there are +# no forward slashes (`/`) and in which there are no comments. + +# Returns `0` if the string `$1` contains any of the entries in the normalized list `$2` as a substring, and returns `1` # otherwise. list_contains_any() { if [ -z "$2" ]; then return 1; fi - printf "%s\n" "$2" | while IFS="$n" read -r needle; do [ -z "${1##*"$needle"*}" ] && return 0; done - return "$?" + test "${-#*f}" != "$-"; list_contains_any__glob="$?"; set -f; list_contains_any__ifs="$IFS"; IFS="$n" + + for list_contains_any__needle in $2; do + if [ -z "${1##*"$list_contains_any__needle"*}" ]; then return 0; fi + done + + IFS="$list_contains_any__ifs"; if [ "$list_contains_any__glob" != "0" ]; then set +f; fi + return 1 } -# Takes the list in stdin and (1) removes all lines starting with `#`, (2) replaces each `/` with a newline, (3) removes -# all blank lines, and (4) removes all entries that contain any of the entries in the sanitized list `$1` as a -# substring. +# Takes the list in `$1` and (1) removes all lines starting with `#`, (2) replaces each `/` with a newline, (3) removes +# all blank lines, and (4) removes all entries that contain any of the entries in the normalized list `$2` as a +# substring, and stores the output in `$list_normalize__out`, separated by newlines, without a trailing newline. list_normalize() { - cat | - while IFS="$n" read -r line; do if ! is_comment "$line"; then split "$line" "/"; fi; done | - while IFS="$n" read -r line; do - if ! is_blank "$line" && ! list_contains_any "$line" "$1"; then printf "%s\n" "$line"; fi - done + test "${-#*f}" != "$-"; list_normalize__glob="$?"; set -f; list_normalize__ifs="$IFS"; IFS="$n" + + # Remove comments and split at `/` + list_normalize__lines="" + for list_normalize__line in $1; do + if ! is_comment "$list_normalize__line"; then + split "$list_normalize__line" "/" + list_normalize__lines="$list_normalize__lines$split__out$n" + fi + done + + # Remove blank lines and "forbidden" entries + list_normalize__out="" + for list_normalize__line in $list_normalize__lines; do + if ! is_blank "$list_normalize__line" && ! list_contains_any "$list_normalize__line" "$2"; then + list_normalize__out="$list_normalize__out$list_normalize__line$n" + fi + done + list_normalize__out="${list_normalize__out%?}" # Remove trailing newline + + IFS="$list_normalize__ifs"; if [ "$list_normalize__glob" != "0" ]; then set +f; fi return 0 } -# Writes a random entry from the sanitized list in stdin to stdout. +# Outputs a random line from `$1` to `$list_choose__out`. if [ -x "$(command -v shuf)" ]; then list_choose() { - cat | shuf -n1 + test "${-#*f}" != "$-"; list_choose__glob="$?"; set -f; list_choose__ifs="$IFS"; IFS="$n" + + list_choose__out="$(shuf -n1 -e $1)" + + IFS="$list_choose__ifs"; if [ "$list_choose__glob" != "0" ]; then set +f; fi + return 0 } else list_choose() { - lines="$(cat)" - count="$(printf "%s\n" "$lines" | wc -l)" - idx="$(jot -r 1 1 "$count")" - printf "%s\n" "$lines" | sed "${idx}q;d" + list_choose__lines="$1" + list_choose__count="$(printf "%s\n" "$list_choose__lines" | wc -l)" + list_choose__idx="$(jot -r 1 1 "$list_choose__count")" + list_choose__out="$(printf "%s\n" "$list_choose__lines" | sed "${list_choose__idx}q;d")" + return 0 } fi -# Escapes stdin to be used as a replacement string in sed. -# -# Only the characters `\` and `&` need replacement, since we assume that `/` does not occur. -escape_sed_replacement() { - cat | sed -e 's/\\/\\\\/g' -e 's/&/\\\&/g' +# Invokes both `list_normalize` and `list_choose`. Input arguments are the same as with `list_normalize`, and the output +# argument is the same as with `list_choose`. +list_normal_choose() { + list_normalize "$1" "$2" + list_choose "$list_normalize__out" } @@ -170,11 +231,11 @@ escape_sed_replacement() { # Prints `$2`, but with color depending on `$1`. If `$1` equals `lolcat`, stdin is piped to `lolcat`. If `$1` is empty, # or color is not enabled in the terminal, stdin is printed normally. Otherwise, `$1` is used as the xterm color. color_print() { - colors="$(tput colors 2>/dev/null)" + color_print__colors="$(tput colors 2>/dev/null)" if [ "$1" = "lolcat" ]; then printf "%s\n" "$2" | lolcat -f - elif [ -z "$1" ] || [ -z "$colors" ] || [ "$colors" -lt 8 ]; then + elif [ -z "$1" ] || [ -z "$color_print__colors" ] || [ "$color_print__colors" -lt 8 ]; then printf "%s\n" "$2" else # Work around OpenBSD bug https://www.mail-archive.com/bugs@openbsd.org/msg18443.html @@ -183,19 +244,7 @@ color_print() { # shellcheck disable=SC2086 # Intentional word splitting: OpenBSD workaround requires two arguments printf "%s\n" "$(tput setaf "$1" $tput_bug)$2$(tput sgr0)" fi - return 0 -} - -# Reads `$1`; if `$2` is `0`, the first character on the line is changed to lowercase, if `$2` is `1`, the first -# character is changed to uppercase, and otherwise nothing is changed; and writes to stdout. -capitalize() { - case "$2" in - 0) mapping="tolower" ;; - 1) mapping="toupper" ;; - *) printf "%s\n" "$1"; return 0 ;; - esac - printf "%s\n" "$1" | awk "{ print $mapping(substr(\$0, 1, 1)) substr(\$0, 2) }" return 0 } @@ -203,40 +252,41 @@ capitalize() { # respectively. split_pronouns() { they="${1%% *}" - remainder="${1#* }" - them="${remainder%% *}" - their="${remainder##* }" + replace_all__remainder="${1#* }" + them="${replace_all__remainder%% *}" + their="${replace_all__remainder##* }" return 0 } -# Reads stdin, and +# Reads `$1`, and # 1. replaces -# * `%%SWEETIE%%` with a random entry from `$1`, -# * `%%THEY%%` with the first word of a random entry from `$2`, -# * `%%THEM%%` with the second word of the same random entry from `$2`, -# * `%%THEIR%%` with the third word of the same random entry from `$2`, and -# * `%%CAREGIVER%%` with a random entry from `$3`; -# 2. applies `capitalize_lines` using `$6` as the choice parameter; +# * `%%SWEETIE%%` with a random entry from `$2`, +# * `%%THEY%%` with the first word of a random entry from `$3`, +# * `%%THEM%%` with the second word of the same random entry from `$3`, +# * `%%THEIR%%` with the third word of the same random entry from `$3`, and +# * `%%CAREGIVER%%` with a random entry from `$4`; +# 2. applies `capitalize_lines` using `$7` as the choice parameter; # 3. removes leading and trailing newlines; -# 4. prepends `$4` and appends `$5`; and -# 5. writes to stdout. +# 4. prepends `$5` and appends `$6`; and +# 5. stores the output in `$fill_template__out`. fill_template() { - sweetie="$(printf "%s\n" "$1" | list_normalize | list_choose | escape_sed_replacement)" - split_pronouns "$(printf "%s\n" "$2" | list_normalize | list_choose | escape_sed_replacement)" - caregiver="$(printf "%s\n" "$3" | list_normalize | list_choose | escape_sed_replacement)" - - prefix="$(printf "%s\n" "$4" | list_normalize | list_choose | escape_sed_replacement)" - suffix="$(printf "%s\n" "$5" | list_normalize | list_choose | escape_sed_replacement)" - - template="$(cat | sed -e "s/%%SWEETIE%%/$sweetie/g" \ - -e "s/%%THEY%%/$they/g" \ - -e "s/%%THEM%%/$them/g" \ - -e "s/%%THEIR%%/$their/g" \ - -e "s/%%CAREGIVER%%/$caregiver/g" \ - -e "s/%%N%%/\\$n/g")" - - capitalize "$prefix$template$suffix" "$6" + list_normal_choose "$2"; sweetie="$list_choose__out" + list_normal_choose "$3"; split_pronouns "$list_choose__out" + list_normal_choose "$4"; caregiver="$list_choose__out" + + list_normal_choose "$5"; prefix="$list_choose__out" + list_normal_choose "$6"; suffix="$list_choose__out" + + replace_all__out="$1" + replace_all "$replace_all__out" "%%SWEETIE%%" "$sweetie" + replace_all "$replace_all__out" "%%THEY%%" "$they" + replace_all "$replace_all__out" "%%THEM%%" "$them" + replace_all "$replace_all__out" "%%THEIR%%" "$their" + replace_all "$replace_all__out" "%%CAREGIVER%%" "$caregiver" + replace_all "$replace_all__out" "%%N%%" "$n" + + capitalize "$prefix$replace_all__out$suffix" "$7"; fill_template__out="$capitalize__out" return 0 } @@ -322,30 +372,31 @@ else command_exit_code="$?" fi - # Choose and fill template (if enabled) - if list_contains_any "$command_exit_code" "$(printf "%s\n" "$MOMMY_IGNORED_STATUSES" | list_normalize)"; then - exit "$command_exit_code" - elif [ "$command_exit_code" -eq 0 ] && [ "$MOMMY_COMPLIMENTS_ENABLED" = "1" ]; then - templates="$MOMMY_COMPLIMENTS/$MOMMY_COMPLIMENTS_EXTRA" + # Check if premature exit is desired + list_normalize "$MOMMY_IGNORED_STATUSES"; ignored_statuses="$list_normalize__out" + if list_contains_any "$command_exit_code" "$ignored_statuses"; then exit "$command_exit_code"; fi + + # Populate list of templates + if [ "$command_exit_code" -eq 0 ] && [ "$MOMMY_COMPLIMENTS_ENABLED" = "1" ]; then + templates="$MOMMY_COMPLIMENTS$n$MOMMY_COMPLIMENTS_EXTRA" elif [ "$command_exit_code" -ne 0 ] && [ "$MOMMY_ENCOURAGEMENTS_ENABLED" = "1" ]; then - templates="$MOMMY_ENCOURAGEMENTS/$MOMMY_ENCOURAGEMENTS_EXTRA" + templates="$MOMMY_ENCOURAGEMENTS$n$MOMMY_ENCOURAGEMENTS_EXTRA" else exit "$command_exit_code" fi - color="$(printf "%s\n" "$MOMMY_COLOR" | list_normalize | list_choose)" - forbidden_words="$(printf "%s\n" "$MOMMY_FORBIDDEN_WORDS" | list_normalize)" - - response="$(printf "%s\n" "$templates" | - list_normalize "$forbidden_words" | - list_choose | - fill_template "$MOMMY_SWEETIE" "$MOMMY_PRONOUNS" "$MOMMY_CAREGIVER" "$MOMMY_PREFIX" \ - "$MOMMY_SUFFIX" "$MOMMY_CAPITALIZE")" + # Select and fill template + list_normalize "$MOMMY_FORBIDDEN_WORDS"; forbidden_words="$list_normalize__out" + list_normal_choose "$templates" "$forbidden_words"; template="$list_choose__out" + fill_template "$template" "$MOMMY_SWEETIE" "$MOMMY_PRONOUNS" "$MOMMY_CAREGIVER" "$MOMMY_PREFIX" "$MOMMY_SUFFIX" \ + "$MOMMY_CAPITALIZE"; template="$fill_template__out" # Output template + list_normalize "$MOMMY_COLOR"; list_choose "$list_normalize__out"; color="$list_choose__out" + case "$opt_target" in - 1) color_print "$color" "$response" >&1 ;; - 2) color_print "$color" "$response" >&2 ;; + 1) color_print "$color" "$template" >&1 ;; + 2) color_print "$color" "$template" >&2 ;; esac exit "$command_exit_code" From 97ecb4f60e56ea25695e5f7f6d5f808fadd90890 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Tue, 20 Feb 2024 23:49:15 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=92=80=20mommy=20fixes=20a=20few=20no?= =?UTF-8?q?n-posix=20comparisons~?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/sh/mommy | 52 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/sh/mommy b/src/main/sh/mommy index 0bb222f..89dfcb4 100755 --- a/src/main/sh/mommy +++ b/src/main/sh/mommy @@ -76,7 +76,7 @@ MOMMY_IGNORED_STATUSES="130" die() { printf "%s\n" "$*" >&2; exit 1; } # Dies if `$OPTARG` is an empty string. -require_arg() { if [ -z "$OPTARG" ]; then die "mommy is missing the argument for option '$OPT'~"; fi; return 0; } +require_arg() { if [ "x" = "x$OPTARG" ]; then die "mommy is missing the argument for option '$OPT'~"; fi; return 0; } # Dies if `$OPTARG` is not a non-negative integer. require_int() { @@ -95,10 +95,10 @@ replace_all() { replace_all__remainder="$1" replace_all__out="" - while [ -n "$replace_all__remainder" ]; do + while [ "x" != "x$replace_all__remainder" ]; do replace_all__section="${replace_all__remainder%%"$2"*}" - if [ "$replace_all__section" = "$replace_all__remainder" ]; then + if [ "x$replace_all__section" = "x$replace_all__remainder" ]; then replace_all__out="$replace_all__out$replace_all__remainder" break fi @@ -113,26 +113,26 @@ replace_all() { # Replaces in `$1` all occurrences of any single character in `$2` by a newline, and writes the resulting lines to # variable `$split__out`, separated by a newline each, without a trailing newline. split() { - test "${-#*f}" != "$-"; split__glob="$?"; set -f; split__ifs="$IFS"; IFS="$2" + test "x${-#*f}" != "x$-"; split__glob="$?"; set -f; split__ifs="$IFS"; IFS="$2" set -- $1 # Do not quote this! IFS="$n" split__out="$*" - IFS="$split__ifs"; if [ "$split__glob" != "0" ]; then set +f; fi + IFS="$split__ifs"; if [ 0 -ne "$split__glob" ]; then set +f; fi return 0 } # Returns `0` if and only if `$1` consists of more than just whitespace. is_blank() { is_blank__trimmed="${1#${1%%[![:space:]]*}}" - test -z "${is_blank__trimmed%${is_blank__trimmed##*[![:space:]]}}" + test "x" = "x${is_blank__trimmed%${is_blank__trimmed##*[![:space:]]}}" return "$?" } # Returns `0` if and only if `$1` starts with a `#`. is_comment() { - test -z "${1##\#*}" + test "x" = "x${1##\#*}" return "$?" } @@ -159,15 +159,15 @@ capitalize() { # Returns `0` if the string `$1` contains any of the entries in the normalized list `$2` as a substring, and returns `1` # otherwise. list_contains_any() { - if [ -z "$2" ]; then return 1; fi + if [ "x" = "x$2" ]; then return 1; fi - test "${-#*f}" != "$-"; list_contains_any__glob="$?"; set -f; list_contains_any__ifs="$IFS"; IFS="$n" + test "x${-#*f}" != "x$-"; list_contains_any__glob="$?"; set -f; list_contains_any__ifs="$IFS"; IFS="$n" for list_contains_any__needle in $2; do - if [ -z "${1##*"$list_contains_any__needle"*}" ]; then return 0; fi + if [ "x" = "x${1##*"$list_contains_any__needle"*}" ]; then return 0; fi done - IFS="$list_contains_any__ifs"; if [ "$list_contains_any__glob" != "0" ]; then set +f; fi + IFS="$list_contains_any__ifs"; if [ 0 -ne "$list_contains_any__glob" ]; then set +f; fi return 1 } @@ -175,7 +175,7 @@ list_contains_any() { # all blank lines, and (4) removes all entries that contain any of the entries in the normalized list `$2` as a # substring, and stores the output in `$list_normalize__out`, separated by newlines, without a trailing newline. list_normalize() { - test "${-#*f}" != "$-"; list_normalize__glob="$?"; set -f; list_normalize__ifs="$IFS"; IFS="$n" + test "x${-#*f}" != "x$-"; list_normalize__glob="$?"; set -f; list_normalize__ifs="$IFS"; IFS="$n" # Remove comments and split at `/` list_normalize__lines="" @@ -195,18 +195,18 @@ list_normalize() { done list_normalize__out="${list_normalize__out%?}" # Remove trailing newline - IFS="$list_normalize__ifs"; if [ "$list_normalize__glob" != "0" ]; then set +f; fi + IFS="$list_normalize__ifs"; if [ 0 -ne "$list_normalize__glob" ]; then set +f; fi return 0 } # Outputs a random line from `$1` to `$list_choose__out`. if [ -x "$(command -v shuf)" ]; then list_choose() { - test "${-#*f}" != "$-"; list_choose__glob="$?"; set -f; list_choose__ifs="$IFS"; IFS="$n" + test "x${-#*f}" != "x$-"; list_choose__glob="$?"; set -f; list_choose__ifs="$IFS"; IFS="$n" list_choose__out="$(shuf -n1 -e $1)" - IFS="$list_choose__ifs"; if [ "$list_choose__glob" != "0" ]; then set +f; fi + IFS="$list_choose__ifs"; if [ 0 -ne "$list_choose__glob" ]; then set +f; fi return 0 } else @@ -233,9 +233,9 @@ list_normal_choose() { color_print() { color_print__colors="$(tput colors 2>/dev/null)" - if [ "$1" = "lolcat" ]; then + if [ "lolcat" = "$1" ]; then printf "%s\n" "$2" | lolcat -f - elif [ -z "$1" ] || [ -z "$color_print__colors" ] || [ "$color_print__colors" -lt 8 ]; then + elif [ "x" = "x$1" ] || [ "x" = "x$color_print__colors" ] || [ 8 -gt "$color_print__colors" ]; then printf "%s\n" "$2" else # Work around OpenBSD bug https://www.mail-archive.com/bugs@openbsd.org/msg18443.html @@ -302,7 +302,7 @@ opt_status="" while getopts ":hv1c:e:s:-:" OPT; do # Cheap workaround for long options, cf. https://stackoverflow.com/a/28466267 - if [ "$OPT" = "-" ]; then + if [ "-" = "$OPT" ]; then OPT="${OPTARG%%=*}" OPTARG="${OPTARG#$OPT}" OPTARG="${OPTARG#=}" @@ -329,7 +329,7 @@ shift "$((OPTIND - 1))" ## Load configuration # Global config_dir="" -while [ "$opt_global_config_dirs" != "$config_dir" ] ;do +while [ "x$opt_global_config_dirs" != "x$config_dir" ] ;do config_dir="${opt_global_config_dirs%%:*}" opt_global_config_dirs="${opt_global_config_dirs#$config_dir:}" @@ -346,26 +346,26 @@ done ## Output -if [ -n "$opt_help" ]; then +if [ "x" != "x$opt_help" ]; then man mommy status="$?" - if [ "$status" -ne 0 ]; then + if [ 0 -ne "$status" ]; then die "oops!" \ "mommy couldn't find the help page." \ "but you can visit https://github.com/FWDekker/mommy/blob/%%VERSION_NUMBER%%/README.md for more" \ "information~" fi exit "$status" -elif [ -n "$opt_version" ]; then +elif [ "x" != "x$opt_version" ]; then printf "%s\n" "mommy, v%%VERSION_NUMBER%%, %%VERSION_DATE%%" exit 0 else # Run command - if [ -n "$opt_eval" ]; then + if [ "x" != "x$opt_eval" ]; then (eval "$opt_eval") command_exit_code="$?" - elif [ -n "$opt_status" ]; then + elif [ "x" != "x$opt_status" ]; then command_exit_code="$opt_status" else ("$@") @@ -377,9 +377,9 @@ else if list_contains_any "$command_exit_code" "$ignored_statuses"; then exit "$command_exit_code"; fi # Populate list of templates - if [ "$command_exit_code" -eq 0 ] && [ "$MOMMY_COMPLIMENTS_ENABLED" = "1" ]; then + if [ 0 -eq "$command_exit_code" ] && [ 1 -eq "$MOMMY_COMPLIMENTS_ENABLED" ]; then templates="$MOMMY_COMPLIMENTS$n$MOMMY_COMPLIMENTS_EXTRA" - elif [ "$command_exit_code" -ne 0 ] && [ "$MOMMY_ENCOURAGEMENTS_ENABLED" = "1" ]; then + elif [ 0 -ne "$command_exit_code" ] && [ 1 -eq "$MOMMY_ENCOURAGEMENTS_ENABLED" ]; then templates="$MOMMY_ENCOURAGEMENTS$n$MOMMY_ENCOURAGEMENTS_EXTRA" else exit "$command_exit_code"