diff --git a/bin/omarchy-cmd-missing b/bin/omarchy-cmd-missing index 470d69c3..2b48be81 100755 --- a/bin/omarchy-cmd-missing +++ b/bin/omarchy-cmd-missing @@ -3,7 +3,7 @@ # omarchy:summary=Check whether any required commands are missing for cmd in "$@"; do - if ! command -v "$cmd" &>/dev/null; then + if ! command -v -- "$cmd" &>/dev/null; then exit 0 fi done diff --git a/bin/omarchy-cmd-present b/bin/omarchy-cmd-present index 4e758760..57b05102 100755 --- a/bin/omarchy-cmd-present +++ b/bin/omarchy-cmd-present @@ -3,7 +3,7 @@ # omarchy:summary=Check whether all required commands are available for cmd in "$@"; do - command -v "$cmd" &>/dev/null || exit 1 + command -v -- "$cmd" &>/dev/null || exit 1 done exit 0 diff --git a/bin/omarchy-hook-install b/bin/omarchy-hook-install index 7c536482..3eb1197a 100755 --- a/bin/omarchy-hook-install +++ b/bin/omarchy-hook-install @@ -16,7 +16,7 @@ fi HOOK_TYPE=$1 HOOK_FILE=$2 HOOK_DIR="$HOME/.config/omarchy/hooks/$HOOK_TYPE.d" -HOOK_NAME=$(basename "$HOOK_FILE") +HOOK_NAME=$(basename -- "$HOOK_FILE") HOOK_PATH="$HOOK_DIR/$HOOK_NAME" if [[ ! -f $HOOK_FILE ]]; then @@ -25,7 +25,7 @@ if [[ ! -f $HOOK_FILE ]]; then fi mkdir -p "$HOOK_DIR" -cp "$HOOK_FILE" "$HOOK_PATH" +cp -- "$HOOK_FILE" "$HOOK_PATH" chmod 755 "$HOOK_PATH" echo "Installed $HOOK_TYPE hook: $HOOK_PATH" diff --git a/bin/omarchy-hw-match b/bin/omarchy-hw-match index 318ca7e8..cecca3b0 100755 --- a/bin/omarchy-hw-match +++ b/bin/omarchy-hw-match @@ -3,5 +3,5 @@ # omarchy:summary=Match against the computer's DMI product name or product family (case-insensitive). # omarchy:args= -grep -qi "$1" /sys/class/dmi/id/product_name 2>/dev/null || -grep -qi "$1" /sys/class/dmi/id/product_family 2>/dev/null +grep -qi -- "$1" /sys/class/dmi/id/product_name 2>/dev/null || +grep -qi -- "$1" /sys/class/dmi/id/product_family 2>/dev/null diff --git a/bin/omarchy-launch-editor b/bin/omarchy-launch-editor index 8a69dabd..926e75d1 100755 --- a/bin/omarchy-launch-editor +++ b/bin/omarchy-launch-editor @@ -23,12 +23,12 @@ omarchy-cmd-present "$editor" || editor="nvim" case "${editor##*/}" in nvim | vim | nano | micro | hx | helix | fresh) if [[ $inline == "true" ]]; then - exec "$editor" "$@" + exec "$editor" -- "$@" else - exec omarchy-launch-tui "$editor" "$@" + exec omarchy-launch-tui "$editor" -- "$@" fi ;; *) - exec setsid uwsm-app -- "$editor" "$@" + exec setsid uwsm-app -- "$editor" -- "$@" ;; esac diff --git a/bin/omarchy-pkg-add b/bin/omarchy-pkg-add index 977d7efd..1f5e4091 100755 --- a/bin/omarchy-pkg-add +++ b/bin/omarchy-pkg-add @@ -7,15 +7,15 @@ if omarchy-pkg-missing "$@"; then if (( EUID == 0 )); then - pacman -S --noconfirm --needed "$@" || exit 1 + pacman -S --noconfirm --needed -- "$@" || exit 1 else - sudo pacman -S --noconfirm --needed "$@" || exit 1 + sudo pacman -S --noconfirm --needed -- "$@" || exit 1 fi fi for pkg in "$@"; do # Secondary check to handle states where pacman doesn't actually register an error - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2 exit 1 fi diff --git a/bin/omarchy-pkg-aur-add b/bin/omarchy-pkg-aur-add index 7a152be7..64ad097c 100755 --- a/bin/omarchy-pkg-aur-add +++ b/bin/omarchy-pkg-aur-add @@ -4,12 +4,12 @@ # omarchy:args= if omarchy-pkg-missing "$@"; then - yay -S --noconfirm --needed "$@" || exit 1 + yay -S --noconfirm --needed -- "$@" || exit 1 fi for pkg in "$@"; do # Secondary check to handle states where pacman doesn't actually register an error - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2 exit 1 fi diff --git a/bin/omarchy-pkg-missing b/bin/omarchy-pkg-missing index c4059dff..b282d2f3 100755 --- a/bin/omarchy-pkg-missing +++ b/bin/omarchy-pkg-missing @@ -4,7 +4,7 @@ # omarchy:args= for pkg in "$@"; do - if ! pacman -Q "$pkg" &>/dev/null; then + if ! pacman -Q -- "$pkg" &>/dev/null; then exit 0 fi done diff --git a/bin/omarchy-pkg-present b/bin/omarchy-pkg-present index 8c4340d6..0a0645bd 100755 --- a/bin/omarchy-pkg-present +++ b/bin/omarchy-pkg-present @@ -4,7 +4,7 @@ # omarchy:args= for pkg in "$@"; do - pacman -Q "$pkg" &>/dev/null || exit 1 + pacman -Q -- "$pkg" &>/dev/null || exit 1 done exit 0 diff --git a/bin/omarchy-restart-app b/bin/omarchy-restart-app index d250650d..e7d7ce5d 100755 --- a/bin/omarchy-restart-app +++ b/bin/omarchy-restart-app @@ -3,5 +3,5 @@ # omarchy:summary=Restart an application by killing it and relaunching via uwsm. # omarchy:args= [application-args...] -pkill -x $1 +pkill -x -- "$1" setsid uwsm-app -- "$@" >/dev/null 2>&1 & diff --git a/test/shell.d/command-operand-boundaries-test.sh b/test/shell.d/command-operand-boundaries-test.sh new file mode 100755 index 00000000..652b1ce7 --- /dev/null +++ b/test/shell.d/command-operand-boundaries-test.sh @@ -0,0 +1,259 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +export OMARCHY_PATH="$ROOT" + +if "$ROOT/bin/omarchy-cmd-present" -p; then + fail "cmd-present treats an option-shaped command name literally" +fi +pass "cmd-present treats an option-shaped command name literally" + +"$ROOT/bin/omarchy-cmd-missing" -p || + fail "cmd-missing treats an option-shaped command name literally" +pass "cmd-missing treats an option-shaped command name literally" + +"$ROOT/bin/omarchy-cmd-present" bash || fail "cmd-present still finds an ordinary command" +pass "cmd-present still finds an ordinary command" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +mock_bin="$test_tmp/bin" +mkdir -p "$mock_bin" + +cat >"$mock_bin/pacman" <<'STUB' +#!/bin/bash + +if [[ -n ${OMARCHY_TEST_PACKAGE_CALLS:-} ]]; then + { + printf 'pacman' + printf ' <%s>' "$@" + printf '\n' + } >>"$OMARCHY_TEST_PACKAGE_CALLS" +fi + +if [[ $1 == "-S" ]]; then + exit 0 +elif [[ $1 != "-Q" ]]; then + exit 2 +fi +shift + +if [[ -n ${OMARCHY_TEST_PACKAGE_CALLS:-} ]]; then + exit 0 +elif [[ ${1:-} == "--" ]]; then + shift + [[ ${1:-} == "installed" ]] +elif [[ ${1:-} == "--help" ]]; then + exit 0 +else + [[ ${1:-} == "installed" ]] +fi +STUB +chmod +x "$mock_bin/pacman" + +if PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-present" --help; then + fail "pkg-present treats an option-shaped package name literally" +fi +pass "pkg-present treats an option-shaped package name literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-missing" --help || + fail "pkg-missing treats an option-shaped package name literally" +pass "pkg-missing treats an option-shaped package name literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-pkg-present" installed || + fail "pkg-present still finds an installed package" +pass "pkg-present still finds an installed package" + +package_calls="$test_tmp/package-calls" +cat >"$mock_bin/omarchy-pkg-missing" <<'STUB' +#!/bin/bash + +exit 0 +STUB +cat >"$mock_bin/sudo" <<'STUB' +#!/bin/bash + +exec "$@" +STUB +cat >"$mock_bin/yay" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_PACKAGE_CALLS:?}" +{ + printf 'yay' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_PACKAGE_CALLS" +STUB +chmod +x "$mock_bin/omarchy-pkg-missing" "$mock_bin/sudo" "$mock_bin/yay" + +: >"$package_calls" +PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_PACKAGE_CALLS="$package_calls" \ + "$ROOT/bin/omarchy-pkg-add" --hookdir=/tmp/hooks + +grep -Fx 'pacman <-S> <--noconfirm> <--needed> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-add separates package operands from pacman options" "$(<"$package_calls")" +grep -Fx 'pacman <-Q> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-add verifies an option-shaped package name literally" "$(<"$package_calls")" +pass "pkg-add keeps option-shaped package names out of pacman option parsing" + +: >"$package_calls" +PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_PACKAGE_CALLS="$package_calls" \ + "$ROOT/bin/omarchy-pkg-aur-add" --hookdir=/tmp/hooks + +grep -Fx 'yay <-S> <--noconfirm> <--needed> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-aur-add separates package operands from yay options" "$(<"$package_calls")" +grep -Fx 'pacman <-Q> <--> <--hookdir=/tmp/hooks>' "$package_calls" >/dev/null || + fail "pkg-aur-add verifies an option-shaped package name literally" "$(<"$package_calls")" +pass "pkg-aur-add keeps option-shaped package names out of package-manager option parsing" + +cat >"$mock_bin/grep" <<'STUB' +#!/bin/bash + +if [[ $1 != "-qi" ]]; then + exit 2 +fi +shift + +if [[ ${1:-} == "--help" ]]; then + exit 0 +elif [[ ${1:-} == "--" ]]; then + shift +fi +[[ ${1:-} == "known-hardware" ]] +STUB +chmod +x "$mock_bin/grep" + +if PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-hw-match" --help; then + fail "hw-match treats an option-shaped hardware pattern literally" +fi +pass "hw-match treats an option-shaped hardware pattern literally" + +PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-hw-match" known-hardware || + fail "hw-match still accepts an ordinary hardware pattern" +pass "hw-match still accepts an ordinary hardware pattern" + +pkill_calls="$test_tmp/pkill-calls" +cat >"$mock_bin/pkill" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_PKILL_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_PKILL_CALLS" +STUB +cat >"$mock_bin/setsid" <<'STUB' +#!/bin/bash + +if [[ -n ${OMARCHY_TEST_SETSID_CALLS:-} ]]; then + printf '<%s>\n' "$@" >"$OMARCHY_TEST_SETSID_CALLS" +fi +exit 0 +STUB +chmod +x "$mock_bin/pkill" "$mock_bin/setsid" + +PATH="$mock_bin:$PATH" OMARCHY_TEST_PKILL_CALLS="$pkill_calls" \ + "$ROOT/bin/omarchy-restart-app" "-9 kitty" + +pkill_argv=$(<"$pkill_calls") +[[ $pkill_argv == $'<-x>\n<-->\n<-9 kitty>' ]] || + fail "restart-app treats the application name as one literal pkill pattern" "$pkill_argv" +pass "restart-app treats the application name as one literal pkill pattern" + +fake_home="$test_tmp/home" +editor_calls="$test_tmp/editor-calls" +mkdir -p "$fake_home/.local/state/omarchy/defaults" +printf 'nvim\n' >"$fake_home/.local/state/omarchy/defaults/editor" + +cat >"$mock_bin/nvim" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_EDITOR_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_EDITOR_CALLS" +STUB +chmod +x "$mock_bin/nvim" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_EDITOR_CALLS="$editor_calls" \ + "$ROOT/bin/omarchy-launch-editor" --inline -cquit + +editor_argv=$(<"$editor_calls") +[[ $editor_argv == $'<-->\n<-cquit>' ]] || + fail "launch-editor separates an option-shaped path from editor options" "$editor_argv" +pass "launch-editor separates an option-shaped path from editor options" + +cat >"$mock_bin/omarchy-launch-tui" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_EDITOR_CALLS:?}" +printf '<%s>\n' "$@" >"$OMARCHY_TEST_EDITOR_CALLS" +STUB +chmod +x "$mock_bin/omarchy-launch-tui" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_EDITOR_CALLS="$editor_calls" \ + "$ROOT/bin/omarchy-launch-editor" -cquit + +editor_argv=$(<"$editor_calls") +[[ $editor_argv == $'\n<-->\n<-cquit>' ]] || + fail "launch-editor separates a terminal editor path from editor options" "$editor_argv" +pass "launch-editor separates a terminal editor path from editor options" + +setsid_calls="$test_tmp/setsid-calls" +printf 'code\n' >"$fake_home/.local/state/omarchy/defaults/editor" +cat >"$mock_bin/code" <<'STUB' +#!/bin/bash + +exit 0 +STUB +chmod +x "$mock_bin/code" + +HOME="$fake_home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_SETSID_CALLS="$setsid_calls" \ + "$ROOT/bin/omarchy-launch-editor" -cquit + +setsid_argv=$(<"$setsid_calls") +[[ $setsid_argv == $'\n<-->\n\n<-->\n<-cquit>' ]] || + fail "launch-editor separates a graphical editor path from editor options" "$setsid_argv" +pass "launch-editor separates a graphical editor path from editor options" + +hook_calls="$test_tmp/hook-calls" +hook_home="$test_tmp/hook-home" +printf '#!/bin/bash\n' >"$test_tmp/--help" +cat >"$mock_bin/basename" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_HOOK_CALLS:?}" +{ + printf 'basename' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_HOOK_CALLS" +printf 'literal-hook\n' +STUB +cat >"$mock_bin/cp" <<'STUB' +#!/bin/bash + +: "${OMARCHY_TEST_HOOK_CALLS:?}" +{ + printf 'cp' + printf ' <%s>' "$@" + printf '\n' +} >>"$OMARCHY_TEST_HOOK_CALLS" +STUB +for command in mkdir chmod; do + printf '#!/bin/bash\nexit 0\n' >"$mock_bin/$command" +done +chmod +x "$mock_bin/basename" "$mock_bin/cp" "$mock_bin/mkdir" "$mock_bin/chmod" + +: >"$hook_calls" +( + cd -- "$test_tmp" + HOME="$hook_home" PATH="$mock_bin:$PATH" OMARCHY_TEST_HOOK_CALLS="$hook_calls" \ + "$ROOT/bin/omarchy-hook-install" post-update --help >/dev/null +) + +hook_argv=$(<"$hook_calls") +expected_hook_argv=$'basename <--> <--help>\ncp <--> <--help> <'"$hook_home"'/.config/omarchy/hooks/post-update.d/literal-hook>' +[[ $hook_argv == $expected_hook_argv ]] || + fail "hook-install treats its source file as an operand" "$hook_argv" +pass "hook-install treats its source file as an operand"