diff --git a/commands/echo-affirmative b/commands.beta/echo-affirmative similarity index 97% rename from commands/echo-affirmative rename to commands.beta/echo-affirmative index 6dae84c47..bd31946bc 100755 --- a/commands/echo-affirmative +++ b/commands.beta/echo-affirmative @@ -13,7 +13,6 @@ function echo_affirmative() ( cat <<-EOF >/dev/stderr ABOUT: For each , output 'yes' if affirmative, 'no' if non-affirmative, otherwise note the invalidity to stderr. - Using [is-affirmative] for the validation. USAGE: echo-affirmative [...options] [--] ... diff --git a/commands.beta/echo-exit-affirmative b/commands.beta/echo-exit-affirmative index bfcb4ab52..b8c736f58 100755 --- a/commands.beta/echo-exit-affirmative +++ b/commands.beta/echo-exit-affirmative @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# @should be renamed to eval-then-affirm-status + function echo_exit_affirmative() ( source "$DOROTHY/sources/bash.bash" diff --git a/commands.beta/echo-exit-status b/commands.beta/echo-exit-status index c4e82bc01..954e05a22 100755 --- a/commands.beta/echo-exit-status +++ b/commands.beta/echo-exit-status @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# @should be renamed to eval-then-echo-status + function echo_exit_status() ( source "$DOROTHY/sources/bash.bash" diff --git a/commands.beta/echo-if-directory b/commands.beta/echo-if-directory index c1b1ca714..663976b97 100755 --- a/commands.beta/echo-if-directory +++ b/commands.beta/echo-if-directory @@ -6,7 +6,7 @@ function echo_if_directory() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Output inputs that are directories. + Output inputs that are directories (or symlinks to directories). USAGE: echo-if-directory [...options] [---] ... diff --git a/commands/echo-non-affirmative b/commands.beta/echo-non-affirmative similarity index 100% rename from commands/echo-non-affirmative rename to commands.beta/echo-non-affirmative diff --git a/commands.beta/echo-with-empty-fallback b/commands.beta/echo-with-empty-fallback new file mode 100755 index 000000000..4a76f01ac --- /dev/null +++ b/commands.beta/echo-with-empty-fallback @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +function echo_with_empty_fallback() ( + source "$DOROTHY/sources/stdinargs.bash" + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Output if all is zero-length. + Similar to [echo-with-whitespace-fallback], [eval-on-empty-stdin], [eval-on-not-empty-stdin]. + + USAGE: + echo-with-empty-fallback [...options] [--] ... + echo-lines ... | echo-with-empty-fallback [...options] + + OPTIONS: + | --fallback= + The fallback to use if is empty. + + $(stdinargs_options_help --) + + EXAMPLE: + + echo-with-empty-fallback 'my-fallback-value' + + my-fallback-value + # exit status: 0 + + printf '' | echo-with-empty-fallback 'my-fallback-value' + + my-fallback-value + # exit status: 0 + + printf ' ' | echo-with-empty-fallback 'my-fallback-value' --stdin + + # exit status: 0 + + printf 'value' | echo-with-empty-fallback 'my-fallback-value' --stdin + + value + # exit status: 0 + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process our own arguments, delegate everything else to stdinargs + local rand="$RANDOM" + local item option_fallback="$rand" option_args=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--fallback='*) option_fallback="${item#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + '--'*) option_args+=("$item") ;; + *) + if [[ $option_fallback == "$rand" ]]; then + option_fallback="$item" + else + option_args+=("$item") + fi + ;; + esac + done + + # check for expected + if [[ $option_fallback == "$rand" ]]; then + help 'Missing required argument: ' + fi + + # action + local inputs='' + function on_inline { + inputs+="$1" + } + function on_line { + inputs+="$1"$'\n' + return 210 # ECUSTOM 210 Processing complete, exit early + } + function on_finish { + if [[ -z $inputs ]]; then + __print_string "$fallback" + else + __print_string "$inputs" + fi + } + stdinargs "${option_args[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + echo_with_empty_fallback "$@" +fi diff --git a/commands.beta/echo-if-empty b/commands.beta/echo-with-whitespace-fallback similarity index 69% rename from commands.beta/echo-if-empty rename to commands.beta/echo-with-whitespace-fallback index d754dc314..46cc4caa2 100755 --- a/commands.beta/echo-if-empty +++ b/commands.beta/echo-with-whitespace-fallback @@ -1,16 +1,17 @@ #!/usr/bin/env bash -function echo_if_empty() ( +function echo_with_whitespace_fallback() ( source "$DOROTHY/sources/stdinargs.bash" function help { cat <<-EOF >/dev/stderr ABOUT: - Output if is empty. + Output if all is only whitespace. + Similar to [echo-with-empty-fallback]. USAGE: - echo-if-empty [...options] [--] ... - echo-lines ... | echo-if-empty [...options] + echo-with-whitespace-fallback [...options] [--] ... + echo-lines ... | echo-with-whitespace-fallback [...options] OPTIONS: | --fallback= @@ -20,23 +21,20 @@ function echo_if_empty() ( EXAMPLE: - echo-if-empty 'my-fallback-value' + echo-with-whitespace-fallback 'my-fallback-value' my-fallback-value # exit status: 0 - echo | echo-if-empty 'my-fallback-value' --stdin + printf ' \n\t' | echo-with-whitespace-fallback 'my-fallback-value' --stdin my-fallback-value # exit status: 0 - echo 'a-value' | echo-if-empty 'my-fallback-value' --stdin + printf 'value' | echo-with-whitespace-fallback 'my-fallback-value' --stdin - 'a-value' + value # exit status: 0 - - ALTERNATIVES: - Use [ifne] from [moreutils], which is what we use in [eval-on-empty-stdin] and [eval-on-not-empty-stdin]. EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -70,9 +68,9 @@ function echo_if_empty() ( esac done - # checck for expected + # check for expected if [[ $option_fallback == "$rand" ]]; then - help "Missing required argument: " + help 'Missing required argument: ' fi # action @@ -84,7 +82,7 @@ function echo_if_empty() ( inputs+="$1"$'\n' } function on_finish { - if is-empty-string -- "$inputs"; then + if is-whitespace -- "$inputs"; then __print_string "$fallback" else __print_string "$inputs" @@ -95,5 +93,5 @@ function echo_if_empty() ( # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - echo_if_empty "$@" + echo_with_whitespace_fallback "$@" fi diff --git a/commands.beta/is-empty-directory b/commands.beta/is-empty-directory deleted file mode 100755 index 6b6a7a64e..000000000 --- a/commands.beta/is-empty-directory +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -function is_empty_directory() ( - source "$DOROTHY/sources/bash.bash" - - # ===================================== - # Arguments - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Checks if the path is an empty directory. - - USAGE: - is-empty-directory [--] ... - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_paths=() - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - '--') - option_paths+=("$@") - shift "$#" - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; - esac - done - - # check - if [[ ${#option_paths[@]} -eq 0 ]]; then - help "No s provided." - fi - - # ===================================== - # Action - - # action - local path result - for path in "${option_paths[@]}"; do - if [[ ! -d $path ]]; then - return 1 - fi - result="$(ls -A "$path")" - if [[ -z $result ]]; then - continue - else - return 1 - fi - done -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_empty_directory "$@" -fi diff --git a/commands.beta/is-even b/commands.beta/is-even deleted file mode 100755 index f417a170c..000000000 --- a/commands.beta/is-even +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash - -function is_even() ( - source "$DOROTHY/sources/bash.bash" - - # ===================================== - # Arguments - - # help - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Checks if the is an even number. - - USAGE: - is-even [...options] [--] - - OPTIONS: - - Verify this is an even number - - RETURNS: - [0] if all s were odd numbers - [1] if any s were not odd numbers - [2] if any s were not numbers - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item inputs=() - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - '--') - inputs+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) inputs+=("$item") ;; - esac - done - - # verify - if [[ ${#inputs[@]} -eq 0 ]]; then - help "No s provided" - fi - - # verify - if ! is-number -- "${inputs[@]}"; then - return 2 - fi - - # ===================================== - # Action - - local input - for input in "${inputs[@]}"; do - [[ "$((input % 2))" -eq 0 ]] || return # explicit return with [[ required for bash v3 - done - return 0 -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_even "$@" -fi diff --git a/commands.beta/is-exec b/commands.beta/is-exec deleted file mode 100755 index 3e88ad6ba..000000000 --- a/commands.beta/is-exec +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -function is_exec() ( - source "$DOROTHY/sources/bash.bash" - - [[ -x $1 ]] - return -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_exec "$@" -fi diff --git a/commands.beta/is-nonempty-file b/commands.beta/is-nonempty-file index 042a67549..7e80c7726 100755 --- a/commands.beta/is-nonempty-file +++ b/commands.beta/is-nonempty-file @@ -1,8 +1,39 @@ #!/usr/bin/env bash +function is_nonempty_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + local dir file + dir="$(fs-temp --directory='is-nonempty-file' --directory --touch)" + file="$(fs-temp --directory='is-nonempty-file' --file --touch)" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-nonempty-file -- + + eval-tester --name='empty args' --status=22 \ + -- is-nonempty-file -- '' '' + + eval-tester --name='missing' --status=9 \ + -- is-nonempty-file -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='empty dirs' --status=9 \ + -- is-nonempty-file -- "$dir" "$dir" + + eval-tester --name='empty files' --status=1 \ + -- is-nonempty-file -- "$file" "$file" + + eval-tester --name='non-empty dir' --status=9 \ + -- is-nonempty-file -- "$DOROTHY" + + eval-tester --name='non-empty file' \ + -- is-nonempty-file -- "$DOROTHY/README.md" + + echo-style --g1="TEST: $0" + return 0 +) function is_nonempty_file() ( source "$DOROTHY/sources/bash.bash" - source "$(type -P sudo-helper)" # ===================================== # Arguments @@ -10,7 +41,7 @@ function is_nonempty_file() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Checks if is a non-empty file. + Checks if a is a file that has contents, aka a file that is not zero-length. USAGE: is-nonempty-file [...options] [--] ... @@ -23,8 +54,10 @@ function is_nonempty_file() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were non-empty files. - [1] if any s were not a non-empty file. + [0] if all s are files that have content. + [1] if any s are not a file that has content. + [9] if any s are not a file. + [22] if empty arguments are provided. EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -33,7 +66,7 @@ function is_nonempty_file() ( } # process - local item option_paths=() option_sudo='no' option_user='' option_group='' + local item option_inputs=() option_sudo='no' option_user='' option_group='' while [[ $# -ne 0 ]]; do item="$1" shift @@ -45,44 +78,32 @@ function is_nonempty_file() ( '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; '--') - option_paths+=("$@") + option_inputs+=("$@") shift "$#" break ;; '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; + *) option_inputs+=("$item") ;; esac done # check - if [[ ${#option_paths[@]} -eq 0 ]]; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help "No s provided." fi # ===================================== # Action - function __are_nonempty_files { - local path - for path in "$@"; do - [[ -s $path ]] || return # explicit return with [[ required for bash v3 - done - return 0 - } - - # if need sudo, use visa sudo - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - sudo_helper --inherit --user="$option_user" --group="$option_group" \ - -- __are_nonempty_files "${option_paths[@]}" - return - fi - - # if don't need sudo, use directly - __are_nonempty_files "${option_paths[@]}" + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-nonempty-file.bash -- "${option_inputs[@]}" return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_nonempty_file "$@" + if [[ $* == '--test' ]]; then + is_nonempty_file_test + else + is_nonempty_file "$@" + fi fi diff --git a/commands.beta/is-nonempty-file.bash b/commands.beta/is-nonempty-file.bash new file mode 100755 index 000000000..fa51e2b5d --- /dev/null +++ b/commands.beta/is-nonempty-file.bash @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -f $1 ]]; then + # not a file nor symlink to a file + exit 9 # EBADF 9 Bad file descriptor + fi + if [[ ! -s $1 ]]; then + exit 1 + fi + shift +done +exit 0 diff --git a/commands.beta/is-root b/commands.beta/is-root index f119005fc..a9cc6cce6 100755 --- a/commands.beta/is-root +++ b/commands.beta/is-root @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# @todo rename to is-root-user or is-user-root + function is_root() ( source "$DOROTHY/sources/bash.bash" diff --git a/commands.beta/is-shapeshifter b/commands.beta/is-shapeshifter index 8147cb5bd..087ce07ea 100755 --- a/commands.beta/is-shapeshifter +++ b/commands.beta/is-shapeshifter @@ -139,7 +139,7 @@ function is_shapeshifter_test() ( ) eval-tester --status=1 -- is-shapeshifter -- "blah" "0123" $'\e[0m' - eval-tester --status=0 -- is-shapeshifter -- "${inputs[@]}" + eval-tester -- is-shapeshifter -- "${inputs[@]}" echo-style --g1="TEST: $0" return 0 diff --git a/commands.beta/pdf-decrypt b/commands.beta/pdf-decrypt index ca476ba6f..55819da1d 100755 --- a/commands.beta/pdf-decrypt +++ b/commands.beta/pdf-decrypt @@ -9,13 +9,13 @@ function pdf_decrypt() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Save decrypted copies of the PDF inputs. + Take PDF s that have passwords, and save copies that have the passwords removed. USAGE: pdf-decrypt [--password=] ... EXAMPLE: - pdf-decrypt *.pdf + pdf-decrypt -- *.pdf EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -24,43 +24,58 @@ function pdf_decrypt() ( } # process - local item files=() password='' + local item option_inputs=() option_password='' while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; - '--password='*) password="${item#*=}" ;; + '--password='*) option_password="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; '--'*) help "An unrecognised flag was provided: $item" ;; - *) files+=("$item") ;; + *) option_inputs+=("$item") ;; esac done # ===================================== # Dependencies - if __command_missing -- qpdf; then - echo-style --error="[qpdf] is missing, install it first." >/dev/stderr - return 1 - fi + setup-util-qpdf --quiet # ===================================== # Act - local input output current_password - for input in "${files[@]}"; do - output="$(fs-filename -- "$input") [decrypted].pdf" - current_password="$( - ask --required --password \ - --question="Enter the password for $input" \ - --default="$password" - )" + local index filepath filename + local filenames=() + for index in "${!option_inputs[@]}"; do + filepath="${option_inputs[index]}" + filename="$(fs-filename -- "$filepath")" + filenames[index]="$filename" + done + + local password + password="$( + ask --required --password \ + --question='Enter the decryption password for the PDFs' \ + --question="$(echo-lines --columns -- "${filenames[@]}")" \ + --default="$option_password" + )" + + local outpath + for index in "${!option_inputs[@]}"; do + filepath="${option_inputs[index]}" + filename="${filenames[index]}" + outpath="$item [decrypted].pdf" eval-helper --quiet \ - --pending="$(echo-style --bold="Decrypting " --code="$input" --bold=" to " --code="$output")" \ - --success="$(echo-style --bold+green="Decrypted " --code="$input" --bold=" to " --code="$output")" \ - --failure="$(echo-style --bold+red="Failed to decrypt " --code="$input" --bold=" to " --code="$output")" \ - -- qpdf -password="$current_password" -decrypt "$input" "$output" || : + --pending="$(echo-style --bold='Decrypting ' --code="$filepath" --bold=' to ' --code="$outpath")" \ + --success="$(echo-style --bold+green='Decrypted ' --code="$filepath" --bold=' to ' --code="$outpath")" \ + --failure="$(echo-style --bold+red='Failed to decrypt ' --code="$filepath" --bold=' to ' --code="$outpath")" \ + -- qpdf -password="$password" -decrypt "$filepath" "$outpath" || : done ) diff --git a/commands.beta/rm-vmware b/commands.beta/rm-vmware-fusion similarity index 96% rename from commands.beta/rm-vmware rename to commands.beta/rm-vmware-fusion index 2880e3c27..b6b42c6f2 100755 --- a/commands.beta/rm-vmware +++ b/commands.beta/rm-vmware-fusion @@ -1,6 +1,6 @@ #!/usr/bin/env bash -function rm_vmware() ( +function setup_util_vmware_fusion() ( source "$DOROTHY/sources/bash.bash" local paths=( @@ -61,5 +61,5 @@ function rm_vmware() ( # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - rm_vmware "$@" + setup_util_vmware_fusion "$@" fi diff --git a/commands.deprecated/echo-if-empty b/commands.deprecated/echo-if-empty new file mode 100755 index 000000000..5f1466db5 --- /dev/null +++ b/commands.deprecated/echo-if-empty @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function echo_if_empty() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='echo-if-empty' --bold=' has been deprecated in favor of ' --code='echo-with-whitespace-fallback' + + # ===================================== + # Action + + is-not-whitespace "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + echo_if_empty "$@" +fi diff --git a/commands.deprecated/fs-size b/commands.deprecated/fs-size new file mode 100755 index 000000000..5d74daaf9 --- /dev/null +++ b/commands.deprecated/fs-size @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function fs_size() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='fs-size' --bold=' has been deprecated in favor of ' --code='fs-structure' --bold=' or ' --code='get-size' + + # ===================================== + # Action + + fs-structure "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + fs_size "$@" +fi diff --git a/commands/get-array-count b/commands.deprecated/get-array-count similarity index 88% rename from commands/get-array-count rename to commands.deprecated/get-array-count index a9d54c6d3..4ad55954c 100755 --- a/commands/get-array-count +++ b/commands.deprecated/get-array-count @@ -4,6 +4,7 @@ function get_array_count() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='get-array-count' --bold=' has been deprecated in favor of ' --code='echo-trim-empty-lines' # ===================================== # Arguments @@ -49,7 +50,7 @@ function get_array_count() ( local input for input in "${option_inputs[@]}"; do - if is-empty-string -- "$input"; then + if is-whitespace -- "$input"; then __print_lines '-1' return 1 fi diff --git a/commands.beta/get-line-count b/commands.deprecated/get-line-count similarity index 88% rename from commands.beta/get-line-count rename to commands.deprecated/get-line-count index 9d1e5b558..40f4fbd57 100755 --- a/commands.beta/get-line-count +++ b/commands.deprecated/get-line-count @@ -4,6 +4,7 @@ function get_line_count() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='get-line-count' --bold=' has been deprecated in favor of ' --code='echo-count-lines' # ===================================== # Arguments diff --git a/commands/is-array-count b/commands.deprecated/is-array-count similarity index 87% rename from commands/is-array-count rename to commands.deprecated/is-array-count index dc48aa370..6173a8fbf 100755 --- a/commands/is-array-count +++ b/commands.deprecated/is-array-count @@ -2,6 +2,7 @@ function is_array_count() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-count' --bold=' has been deprecated in favor of ' --code='echo-trim-empty-lines' # ===================================== # Arguments @@ -65,11 +66,8 @@ function is_array_count() ( # ===================================== # Action - if [[ "$(get-array-count -- "${option_inputs[@]}")" -eq $option_size ]]; then - return 0 - else - return 1 - fi + [[ "$(get-array-count -- "${option_inputs[@]}")" -eq $option_size ]] + return ) # fire if invoked standalone diff --git a/commands/is-array-count-ge b/commands.deprecated/is-array-count-ge similarity index 92% rename from commands/is-array-count-ge rename to commands.deprecated/is-array-count-ge index 919dc4a46..bffbe0023 100755 --- a/commands/is-array-count-ge +++ b/commands.deprecated/is-array-count-ge @@ -2,6 +2,7 @@ function is_array_count_ge() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-count-ge' --bold=' has been deprecated in favor of ' --code='echo-trim-empty-lines' # ===================================== # Arguments diff --git a/commands/is-array-empty b/commands.deprecated/is-array-empty similarity index 90% rename from commands/is-array-empty rename to commands.deprecated/is-array-empty index 745fdc7ff..7646cbf55 100755 --- a/commands/is-array-empty +++ b/commands.deprecated/is-array-empty @@ -11,6 +11,7 @@ function is_array_empty() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-empty' --bold=' has been deprecated in favor of ' --code='is-whitespace' # ===================================== # Arguments @@ -63,7 +64,7 @@ function is_array_empty() ( local input for input in "${option_inputs[@]}"; do - if is-nonempty-string -- "$input"; then + if is-not-whitespace -- "$input"; then return 1 fi done diff --git a/commands/is-array-empty-or-partial b/commands.deprecated/is-array-empty-or-partial similarity index 86% rename from commands/is-array-empty-or-partial rename to commands.deprecated/is-array-empty-or-partial index 5eaf51fe9..74acb9db8 100755 --- a/commands/is-array-empty-or-partial +++ b/commands.deprecated/is-array-empty-or-partial @@ -7,6 +7,7 @@ function is_array_empty_or_partial() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-empty-or-partial' --bold=' has been deprecated in favor of ' --code='is-whitespace' --bold=' and ' --code='is-not-whitespace' # ===================================== # Arguments @@ -63,7 +64,7 @@ function is_array_empty_or_partial() ( local input for input in "${option_inputs[@]}"; do - if is-empty-string -- "$input"; then + if is-whitespace -- "$input"; then return 0 fi done diff --git a/commands/is-array-full b/commands.deprecated/is-array-full similarity index 89% rename from commands/is-array-full rename to commands.deprecated/is-array-full index 135769285..e9fe7a892 100755 --- a/commands/is-array-full +++ b/commands.deprecated/is-array-full @@ -7,6 +7,7 @@ function is_array_full() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-full' --bold=' has been deprecated in favor of ' --code='is-not-whitespace' # ===================================== # Arguments @@ -63,7 +64,7 @@ function is_array_full() ( local input for input in "${option_inputs[@]}"; do - if is-empty-string -- "$input"; then + if is-whitespace -- "$input"; then return 1 fi done diff --git a/commands/is-array-full-or-partial b/commands.deprecated/is-array-full-or-partial similarity index 86% rename from commands/is-array-full-or-partial rename to commands.deprecated/is-array-full-or-partial index e511c2db1..39697134f 100755 --- a/commands/is-array-full-or-partial +++ b/commands.deprecated/is-array-full-or-partial @@ -7,6 +7,7 @@ function is_array_full_or_partial() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-full-or-partial' --bold=' has been deprecated in favor of ' --code='is-whitespace' --bold=' and ' --code='is-not-whitespace' # ===================================== # Arguments @@ -59,7 +60,7 @@ function is_array_full_or_partial() ( local input for input in "${option_inputs[@]}"; do - if is-nonempty-string -- "$input"; then + if is-not-whitespace -- "$input"; then return 0 fi done diff --git a/commands/is-array-partial b/commands.deprecated/is-array-partial similarity index 72% rename from commands/is-array-partial rename to commands.deprecated/is-array-partial index 39cc593bb..df262c4f0 100755 --- a/commands/is-array-partial +++ b/commands.deprecated/is-array-partial @@ -8,6 +8,7 @@ function is_array_partial() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-array-partial' --bold=' has been deprecated in favor of ' --code='is-whitespace' --bold=' and ' --code='is-not-whitespace' # ===================================== # Arguments @@ -58,15 +59,15 @@ function is_array_partial() ( # ===================================== # Action - local input has_empty='no' has_nonempty='no' + local input had_empty_string='no' had_nonempty_string='no' for input in "${option_inputs[@]}"; do - if [[ $has_empty == 'no' ]] && is-empty-string -- "$input"; then - has_empty='yes' + if [[ $had_empty_string == 'no' ]] && is-whitespace -- "$input"; then + had_empty_string='yes' fi - if [[ $has_nonempty == 'no' ]] && is-nonempty-string -- "$input"; then - has_nonempty='yes' + if [[ $had_nonempty_string == 'no' ]] && is-not-whitespace -- "$input"; then + had_nonempty_string='yes' fi - if [[ $has_empty == 'yes' && $has_nonempty == 'yes' ]]; then + if [[ $had_empty_string == 'yes' && $had_nonempty_string == 'yes' ]]; then return 0 fi done diff --git a/commands.deprecated/is-dir b/commands.deprecated/is-dir index 814ff7ca5..30b2193dc 100755 --- a/commands.deprecated/is-dir +++ b/commands.deprecated/is-dir @@ -4,6 +4,9 @@ function is_dir() ( source "$DOROTHY/sources/bash.bash" dorothy-warnings add --code='is-dir' --bold=' has been deprecated in favor of ' --code='is-directory' + # ===================================== + # Action + local dir="$1" [[ -d $dir ]] return diff --git a/commands.beta/is-either b/commands.deprecated/is-either similarity index 63% rename from commands.beta/is-either rename to commands.deprecated/is-either index 826bcdb47..6bd9acf08 100755 --- a/commands.beta/is-either +++ b/commands.deprecated/is-either @@ -2,6 +2,10 @@ function is_either() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-either' --bold=' has been deprecated in favor of ' --code='is-needle' + + # ===================================== + # Action local arg for arg in "${@:2}"; do diff --git a/commands.deprecated/is-empty-ls b/commands.deprecated/is-empty-ls new file mode 100755 index 000000000..990ca4c7a --- /dev/null +++ b/commands.deprecated/is-empty-ls @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function is_empty_ls() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-empty-ls' --bold=' has been deprecated in favor of ' --code='is-empty-directory' + + # ===================================== + # Action + + is-empty-directory "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_empty_ls "$@" +fi diff --git a/commands.deprecated/is-empty-string b/commands.deprecated/is-empty-string new file mode 100755 index 000000000..19062aab9 --- /dev/null +++ b/commands.deprecated/is-empty-string @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function is_empty_string() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-empty-string' --bold=' has been deprecated in favor of ' --code='is-whitespace' + + # ===================================== + # Action + + is-whitespace "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_empty_string "$@" +fi diff --git a/commands.beta/is-equal b/commands.deprecated/is-equal similarity index 65% rename from commands.beta/is-equal rename to commands.deprecated/is-equal index 129ac3d05..87524f916 100755 --- a/commands.beta/is-equal +++ b/commands.deprecated/is-equal @@ -2,6 +2,7 @@ function is_equal() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-equal' --bold=' has been deprecated in favor of ' --code='test .. = ..' [[ $1 == "$2" ]] return diff --git a/commands.deprecated/is-exec b/commands.deprecated/is-exec new file mode 100755 index 000000000..a20e7f344 --- /dev/null +++ b/commands.deprecated/is-exec @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +function is_exec() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-exec' --bold=' has been deprecated until there is a need for it, in which case it will be renamed to ' --code='is-executable' --bold=' and will support multiple arguments' + + # ===================================== + # Action + + [[ -x $1 ]] + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_exec "$@" +fi diff --git a/commands.deprecated/is-match b/commands.deprecated/is-match index 880843f15..f6d771152 100755 --- a/commands.deprecated/is-match +++ b/commands.deprecated/is-match @@ -4,10 +4,10 @@ function is_match_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --name='match works' --status=0 \ + eval-tester --name='match works' \ -- is-match 'a +haystack ?' 'a haystack' - eval-tester --name='match works' --status=0 \ + eval-tester --name='match works' \ -- is-match -i 'Z|B' 'abc' eval-tester --name='no match works' --status=1 \ diff --git a/commands.beta/is-neither b/commands.deprecated/is-neither similarity index 63% rename from commands.beta/is-neither rename to commands.deprecated/is-neither index 04010507c..1a79b2999 100755 --- a/commands.beta/is-neither +++ b/commands.deprecated/is-neither @@ -2,6 +2,10 @@ function is_neither() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-either' --bold=' has been deprecated in favor of ' --code='! is-needle' + + # ===================================== + # Action local arg for arg in "${@:2}"; do diff --git a/commands.deprecated/is-nonempty-string b/commands.deprecated/is-nonempty-string new file mode 100755 index 000000000..c5b625857 --- /dev/null +++ b/commands.deprecated/is-nonempty-string @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function is_nonempty_string() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-nonempty-string' --bold=' has been deprecated in favor of ' --code='is-not-whitespace' + + # ===================================== + # Action + + is-not-whitespace "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_nonempty_string "$@" +fi diff --git a/commands/rm-junk b/commands.deprecated/rm-junk similarity index 70% rename from commands/rm-junk rename to commands.deprecated/rm-junk index 2f0999323..727965a80 100755 --- a/commands/rm-junk +++ b/commands.deprecated/rm-junk @@ -2,6 +2,7 @@ function rm_junk() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='rm-junk' --bold=' has been deprecated in favor of ' --code='fs-trim --junk' # ===================================== # Arguments @@ -57,33 +58,8 @@ function rm_junk() ( # ===================================== # Action - # prepare the variables we will use - local path find_args=() action_args=() - local ds_store_args=( - '(' -name '.DS_Store' -or -name '._.DS_Store' ')' - ) - local empty_args=( - '(' -type d -empty ')' - ) - - # add DS_Store args, and empty args if desired - find_args+=("${ds_store_args[@]}") - if [[ $option_empty == 'yes' ]]; then - find_args+=( - -or - "${empty_args[@]}" - ) - fi - - # wrap the find args, and add the actions - action_args+=( - '(' "${find_args[@]}" ')' -delete -print - ) - - # perform the find action on each path - for path in "${option_paths[@]}"; do - find "$path" "${action_args[@]}" - done + fs-trim --junk --empty-directories="$option_empty" -- "$path" + return ) # fire if invoked standalone diff --git a/commands/rm-modules b/commands.deprecated/rm-modules similarity index 87% rename from commands/rm-modules rename to commands.deprecated/rm-modules index 3ca638003..bd29593e5 100755 --- a/commands/rm-modules +++ b/commands.deprecated/rm-modules @@ -3,6 +3,7 @@ function rm_modules() ( source "$DOROTHY/sources/bash.bash" __require_globstar + dorothy-warnings add --code='rm-modules' --bold=' has been deprecated in favor of ' --code='fs-trim --junk' # ===================================== # Arguments @@ -52,10 +53,8 @@ function rm_modules() ( # ===================================== # Action - local path - for path in "${option_paths[@]}"; do - fs-rm --no-confirm --optional -- "$path"/**/{node_modules,pnp,package-lock.json,yarn.lock,.pnp.js,.log} - done + fs-trim --junk --empty-directories="$option_empty" -- "$path" + return ) # fire if invoked standalone diff --git a/commands.beta/rm-svn b/commands.deprecated/rm-svn similarity index 87% rename from commands.beta/rm-svn rename to commands.deprecated/rm-svn index 8189f9434..ee2534d25 100755 --- a/commands.beta/rm-svn +++ b/commands.deprecated/rm-svn @@ -2,6 +2,7 @@ function rm_svn() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='rm-svn' --bold=' has been deprecated in favor of ' --code='find -name .svn -delete -print' # ===================================== # Arguments diff --git a/commands.beta/rm-sync b/commands.deprecated/rm-sync similarity index 87% rename from commands.beta/rm-sync rename to commands.deprecated/rm-sync index 3b692217c..6fa2ae55c 100755 --- a/commands.beta/rm-sync +++ b/commands.deprecated/rm-sync @@ -2,6 +2,7 @@ function rm_sync() ( source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='rm-sync' --bold=' has been deprecated in favor of ' --code='find -name .sync -delete -print' # ===================================== # Arguments diff --git a/commands/choose b/commands/choose index 2ab3b7602..401c507a3 100755 --- a/commands/choose +++ b/commands/choose @@ -516,8 +516,11 @@ function choose_() ( --[no-]confirm-input=[yes|NO] Have the choose menu confirm the user's input (their selection or lack of selection). Defaults to disabled. + --[no-]confirm-cancel=[YES|no] + Have the cancel menu confirm the user's cancellation (whether it will revert to nothing when not required, or to the default if provided and required). Defaults to enabled. + --[no-]confirm=[yes|no] - HAve the prompt not skip any step, requiring solo values to be prompted, default values to be prompted, and selections or their lack of to be confirmed. + Have the prompt not skip any step, requiring solo values to be prompted, default values to be prompted, cancellations to be prompted, and selections or their lack of to be confirmed. --[no-]required=[yes|NO] Do not continue until a selection is made. Disable aborting the prompt. @@ -535,7 +538,7 @@ function choose_() ( Custom timeout value in seconds. QUIRKS: - If you wish to return the index, which is desirable in the case of when multiple values can be identical, use --index or --return='\$INDEX'. + If you wish to return the index, which is desirable in the case of when multiple values can be identical, use [--index]. EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -547,7 +550,7 @@ function choose_() ( local item='' inputs=() tmp=() local option_question=() local option_label='no' option_visual='' option_return='$VALUE' - local defaults_exact=() defaults_fuzzy=() option_confirm_solo='yes' option_confirm_default='yes' option_confirm_input='no' + local defaults_exact=() defaults_fuzzy=() option_confirm_solo='yes' option_confirm_default='yes' option_confirm_input='no' option_confirm_cancel='yes' local option_required='no' option_multi='no' local option_linger='no' option_timeout='' while [[ $# -ne 0 ]]; do @@ -588,10 +591,14 @@ function choose_() ( '--no-confirm-input'* | '--confirm-input'*) option_confirm_input="$(get-flag-value --affirmative --fallback="$option_confirm_input" -- "$item")" ;; + '--no-confirm-cancel'* | '--confirm-cancel'*) + option_confirm_cancel="$(get-flag-value --affirmative --fallback="$option_confirm_cancel" -- "$item")" + ;; '--no-confirm'* | '--confirm'*) option_confirm_solo="$(get-flag-value --affirmative --fallback="$option_confirm_solo" -- "$item")" option_confirm_default="$(get-flag-value --affirmative --fallback="$option_confirm_default" -- "$item")" option_confirm_input="$(get-flag-value --affirmative --fallback="$option_confirm_input" -- "$item")" + option_confirm_cancel="$(get-flag-value --affirmative --fallback="$option_confirm_cancel" -- "$item")" ;; '--no-required'* | '--required'*) option_required="$(get-flag-value --affirmative --fallback="$option_required" -- "$item")" @@ -794,6 +801,19 @@ function choose_() ( if [[ $option_confirm_default == 'no' ]]; then can_skip_prompt='yes' fi + # adjust fallbacks for single vs multi mode + # fallbacks=(...) not actually needed, as never used + if [[ $option_required == 'yes' ]]; then + if [[ $option_multi == 'no' ]]; then + fallbacks_count=1 + fallbacks_indexes=("${defaults_indexes[0]}") + fallbacks_last=0 + else + fallbacks_count="$defaults_count" + fallbacks_indexes=("${defaults_indexes[@]}") + fallbacks_last="$defaults_last" + fi + fi else can_revert_to_defaults='no' if [[ $option_required == 'no' ]]; then @@ -803,18 +823,6 @@ function choose_() ( fi fi - # adjust fallbacks for single vs multi mode - # fallbacks=(...) not actually needed, as never used - if [[ $option_multi == 'no' && $defaults_count -gt 1 ]]; then - fallbacks_count=1 - fallbacks_indexes=("${defaults_indexes[0]}") - fallbacks_last=0 - else - fallbacks_count="$defaults_count" - fallbacks_indexes=("${defaults_indexes[@]}") - fallbacks_last="$defaults_last" - fi - # prepare menu vars local \ commentary='' \ @@ -987,7 +995,7 @@ function choose_() ( if [[ $can_revert_to_defaults == 'yes' ]]; then add_legend_keys 'legend_choose' 'NEXT PREF' 'TAB' # next preference add_legend_keys 'legend_choose' 'PREV PREF' '⇧ TAB' # prior preference - add_legend_keys 'legend_choose' 'RESET' 'Z' # reset preferences + add_legend_keys 'legend_choose' 'RESET' 'Z' 'R' # reset preferences if [[ $option_multi == 'yes' ]]; then add_legend_keys 'legend_choose' 'ALL/NONE' 'T' fi @@ -1211,6 +1219,9 @@ function choose_() ( menu_cursor_confirm_and_cancel=0 fi } + function select_index { + select_only_index "$1" + } function select_defaults { # select only first preference if [[ $defaults_count -ne 0 ]]; then @@ -1237,6 +1248,14 @@ function choose_() ( select_none select_defaults } + function action_fallbacks { + if [[ $fallbacks_count -eq 0 ]]; then + select_none + else + select_none + select_index "${fallbacks_indexes[@]}" + fi + } function action_jump { local index="$1" # jump to number @@ -1983,10 +2002,13 @@ function choose_() ( elif [[ $menu_mode == 'cancel' ]]; then # CANCEL MENU if [[ $key =~ ^(enter|e)$ || ($key == 'space' && $option_multi == 'no') ]]; then - action_revert + action_fallbacks set_menu_mode 'confirmed' break elif [[ $key =~ ^(escape|q)$ ]]; then + if [[ $option_multi == 'no' ]]; then + action_revert + fi set_menu_mode 'choose' fi elif [[ $menu_mode == 'confirm' ]]; then @@ -2009,11 +2031,12 @@ function choose_() ( break fi fi - elif [[ $can_cancel == 'yes' && $key =~ ^(escape|q)^ ]]; then - if [[ $option_confirm_input == 'yes' ]]; then + elif [[ $can_cancel == 'yes' && $key =~ ^(escape|q)$ ]]; then + if [[ $option_confirm_cancel == 'yes' ]]; then + # note that selection has no impact on cancel, as cancel's behaviour for rendering is hard coded set_menu_mode 'cancel' else - action_revert + action_fallbacks set_menu_mode 'confirmed' break fi @@ -2027,7 +2050,7 @@ function choose_() ( set_menu_mode 'confirmed' break fi - elif [[ $key == 'z' ]]; then + elif [[ $key =~ ^(z|r)$ ]]; then action_revert elif [[ $option_multi == 'yes' ]]; then if [[ $key == 't' ]]; then diff --git a/commands/command-exists b/commands/command-exists index 0d456a643..981b5b1cd 100755 --- a/commands/command-exists +++ b/commands/command-exists @@ -10,10 +10,10 @@ function command_exists_test() ( eval-tester --status=1 \ -- command-exists -- this-is-a-non-existent-command command-exists - eval-tester --status=0 \ + eval-tester \ -- command-exists -- command-exists - eval-tester --status=0 \ + eval-tester \ -- command-exists -- command-exists command-missing echo-style --g1="TEST: $0" @@ -35,7 +35,7 @@ function command_exists() ( RETURNS: [0] if all commands are available - [1] if any command was not available + [1] if any command is not available QUIRKS: Returns on first failure. diff --git a/commands/command-missing b/commands/command-missing index da0df82f1..66d976a8a 100755 --- a/commands/command-missing +++ b/commands/command-missing @@ -4,10 +4,10 @@ function command_missing_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --status=0 \ + eval-tester \ -- command-missing -- this-is-a-non-existent-command - eval-tester --status=0 \ + eval-tester \ -- command-missing -- this-is-a-non-existent-command command-missing eval-tester --status=1 \ @@ -35,7 +35,7 @@ function command_missing() ( RETURNS: [0] if ANY command is missing. - [1] if ALL commands were present. + [1] if all commands are present. QUIRKS: Returns on first failure. diff --git a/commands/command-working b/commands/command-working index c9d4c7b82..0f4514e46 100755 --- a/commands/command-working +++ b/commands/command-working @@ -10,10 +10,10 @@ function command_working_test() ( eval-tester --status=3 --stderr="< 'this-is-a-non-existent-command' />[3] not working because it is missing" \ -- env COLOR=no command-working -- this-is-a-non-existent-command command-missing - eval-tester --status=0 \ + eval-tester \ -- command-working -- command-exists - eval-tester --status=0 \ + eval-tester \ -- command-working -- command-exists command-missing echo-style --g1="TEST: $0" diff --git a/commands/contains-line b/commands/contains-line index 1529f0364..ee2a68107 100755 --- a/commands/contains-line +++ b/commands/contains-line @@ -31,8 +31,8 @@ function contains_line() ( echo-lines -- 'one' '' 'three' | contains-line --needle='' # success RETURNS: - [0] if an line contained a line. - [1] if no lines were a line. + [0] if ANY line are a line. + [1] if all lines are not a line. EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/dorothy b/commands/dorothy index 25f6b845f..c301ee190 100755 --- a/commands/dorothy +++ b/commands/dorothy @@ -836,12 +836,6 @@ function dorothy_() ( --success="$(echo-style --success='Staged changed files.')" \ --failure="$(echo-style --error='Failed to stage changed files.')" \ -- stage_changed_files "$DOROTHY" - - eval_helper --quiet --no-wrap \ - --pending="$(echo-style --bold='Removing junk files...')" \ - --success="$(echo-style --success='Removed junk files.')" \ - --failure="$(echo-style --error='Failed to remove junk files.')" \ - -- rm-junk "$DOROTHY" } function ensure_dorothy_configured { diff --git a/commands/down b/commands/down index 440334088..52428cefd 100755 --- a/commands/down +++ b/commands/down @@ -181,13 +181,13 @@ function down_() ( if [[ -n $archive_format || -n $archive_glob ]]; then local url_basename url_basename="$(basename "$url" | echo-trim-special --stdin)" - download_directory="$(fs-temp --directory='down' --directory)" + download_directory="$(fs-temp --directory='down' --directory --touch)" download_file="$url_basename" else download_directory="$directory" - download_file="$file" # can be empty + download_file="$file" # can be empty + __mkdirp "$download_directory" # fs-temp makes directory fi - __mkdirp "$download_directory" # tool helpers function do_aria2c { diff --git a/commands/echo-if-file b/commands/echo-if-file index f84da89cd..bdf454736 100755 --- a/commands/echo-if-file +++ b/commands/echo-if-file @@ -9,7 +9,7 @@ function echo_if_file() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Output inputs that are files. + Output inputs that are files (or symlinks to files). USAGE: echo-if-file [...options] [--] ... diff --git a/commands/echo-regexp b/commands/echo-regexp index 41e825c4a..514a5bed6 100755 --- a/commands/echo-regexp +++ b/commands/echo-regexp @@ -12,7 +12,7 @@ function echo_regexp_test() ( eval-tester --name='match works' --stdout='b' \ -- echo-regexp -i 'Z|B' -- 'abc' - eval-tester --name='no match works' --status=0 \ + eval-tester --name='no match works' \ -- echo-regexp 'Z|Y' -- 'abc' eval-tester --name='no match fails with -q' --status=1 \ @@ -90,10 +90,10 @@ function echo_regexp_test() ( # -------- - eval-tester --name='character classes [[:XXX:]]' --status=0 \ + eval-tester --name='character classes [[:XXX:]]' \ -- echo-regexp -q '^[[:digit:]]+$' -- '012' - eval-tester --name='character classes [[:XXX:][:YYY:]]' --status=0 \ + eval-tester --name='character classes [[:XXX:][:YYY:]]' \ -- echo-regexp -q '^[[:digit:][:lower:]]+$' -- 'z0' # -------- diff --git a/commands/echo-trim-empty-lines b/commands/echo-trim-empty-lines index 8311eb1ba..33d72dcd6 100755 --- a/commands/echo-trim-empty-lines +++ b/commands/echo-trim-empty-lines @@ -10,6 +10,7 @@ function echo_trim_empty_lines() ( cat <<-EOF >/dev/stderr ABOUT: Trims empty lines from . + Companion to [is-not-whitespace], [is-whitespace]. Equivalent to a [echo-strings], [echo-nonempty-strings]. USAGE: echo-trim-empty-lines [...options] [--] ... @@ -43,7 +44,7 @@ function echo_trim_empty_lines() ( # Action function on_line { - if is-nonempty-string -- "$1"; then + if is-not-whitespace -- "$1"; then __print_lines "$1" fi } diff --git a/commands/echo-trim-padding b/commands/echo-trim-padding index 20363f76a..48cc10fe6 100755 --- a/commands/echo-trim-padding +++ b/commands/echo-trim-padding @@ -41,7 +41,7 @@ function echo_trim_padding() ( # this allows data to be echoed as it is received local queue=() started='no' function on_line { - if is-empty-string -- "$1"; then + if is-whitespace -- "$1"; then if [[ $started == 'yes' ]]; then queue+=("$1") fi diff --git a/commands.beta/echo-values b/commands/echo-values similarity index 80% rename from commands.beta/echo-values rename to commands/echo-values index 6b08c4b35..d3bf9f37a 100755 --- a/commands.beta/echo-values +++ b/commands/echo-values @@ -7,7 +7,8 @@ function echo_values() ( cat <<-EOF >/dev/stderr ABOUT: Output each input, that is a value, onto its own line. - Uses [is-value] for the internal check. + Use to trim empty values from a list. + Companion to [is-value], [is-empty-value]. Equivalent to a [echo-nonempty-values], [echo-non-nullish], [echo-not-nullish]. USAGE: echo-values [...options] [--] ... @@ -31,7 +32,7 @@ function echo_values() ( [7] = [z] # trimming using echo-values - echo-lines -- '' 0 a NULL VOID UNDEFINED false z | echo-values --stdin | echo-verbose --stdin + echo-values -- '' 0 a NULL VOID UNDEFINED false z | echo-verbose --stdin [0] = [0] [1] = [a] diff --git a/commands/eval-helper b/commands/eval-helper index 6f78f2913..7579d6f32 100755 --- a/commands/eval-helper +++ b/commands/eval-helper @@ -105,10 +105,11 @@ function eval_helper() ( # Action # terminal - local terminal_device_file="$option_terminal_device_file" + local terminal_device_file="$option_terminal_device_file" output_device_file if [[ -z $terminal_device_file ]]; then terminal_device_file="$(get-terminal-device-file)" fi + output_device_file="$terminal_device_file" # element local element_open='' @@ -123,10 +124,19 @@ function eval_helper() ( # confirm if [[ $option_confirm == 'yes' ]] && ! confirm --positive --ppid=$$ -- 'Confirm execution of the command that is below:' "$element_open"; then - echo-style --notice='Skipped execution of:' ' ' "$element_open" + echo-style --notice1='Skipped execution of:' --code-notice1="$element_open" >"$terminal_device_file" return 0 fi + # output + local output='' + function flush_output { + if [[ -n $output ]]; then + __print_string "$output" >"$output_device_file" + output='' + fi + } + # headers local header='' if [[ -n $option_pending ]]; then @@ -134,20 +144,13 @@ function eval_helper() ( fi if [[ $option_wrap == 'yes' ]]; then header+="$element_open"$'\n' + output_device_file='/dev/stdout' fi if [[ -n $header ]]; then - __print_string "$header" >"$terminal_device_file" + output="$header" + flush_output fi - # output - local output='' - function flush_output { - if [[ -n $output ]]; then - __print_string "$output" >"$terminal_device_file" - output='' - fi - } - # output everything if already inside a revolving door, or if in verbose mode local cmd_status=0 if [[ ${INSIDE_REVOLVING_DOOR-} == 'yes' || $option_quiet == 'no' ]]; then diff --git a/commands/fs-dequarantine b/commands/fs-dequarantine index de6b4dd6a..b6e2822d7 100755 --- a/commands/fs-dequarantine +++ b/commands/fs-dequarantine @@ -9,7 +9,7 @@ function fs_dequarantine() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Remove the quarantine flag from a path. + Remove the quarantine flag from a path. USAGE: fs-dequarantine [--] ... diff --git a/commands/fs-filename b/commands/fs-filename index 4444615b2..46f8fd9d9 100755 --- a/commands/fs-filename +++ b/commands/fs-filename @@ -12,11 +12,14 @@ function fs_filename() ( Gets the filename of a path. USAGE: - fs-filename [--first] [--basename] [--] ... + fs-filename [...options] [--] ... - FLAGS: - --first If the filename has multiple extensions, only the first part is returned. - --basename If a path was returned, only work with the basename. + OPTIONS: + --first + If the filename has multiple extensions, only the first part is returned. + + --basename + If a path was returned, only work with the basename. EXAMPLES: fs-filename -- a.b.c diff --git a/commands/fs-realpath b/commands/fs-realpath index 463931c0f..b70171720 100755 --- a/commands/fs-realpath +++ b/commands/fs-realpath @@ -33,6 +33,12 @@ function fs_realpath() ( --relative-base= If provided, print absolute paths unless paths below DIR + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + QUIRKS: If you don't care about symlinks, you should prefer to use [fs-absolute] instead as it is simpler. Use [--resolve --broken --relative] to help you repair broken relative symlinks. @@ -44,7 +50,7 @@ function fs_realpath() ( } # options - local item option_paths=() option_resolve='yes' option_validate='yes' option_relative='no' option_relative_to='' option_relative_base='' + local item option_paths=() option_resolve='yes' option_validate='yes' option_relative='no' option_relative_to='' option_relative_base='' option_sudo='no' option_user='' option_group='' while [[ $# -ne 0 ]]; do item="$1" shift @@ -63,6 +69,11 @@ function fs_realpath() ( option_relative="$(get-flag-value --affirmative --fallback="$option_relative" -- "$item")" ;; '--path='*) option_paths+=("${item#*=}") ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; '--') option_paths+=("$@") shift $# @@ -81,6 +92,10 @@ function fs_realpath() ( # ===================================== # Action + function __sudo { + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" "$@" + } + function do_gnu_realpath { local gnu_realpath if __command_exists -- grealpath; then @@ -141,7 +156,8 @@ function fs_realpath() ( fi # execute - "$gnu_realpath" "${args[@]}" "$path" + __sudo -- \ + "$gnu_realpath" "${args[@]}" "$path" } function do_gnu_readlink { # don't support unsupported args @@ -194,13 +210,13 @@ function fs_realpath() ( # unless canoniclizing, then symlinks are required return 45 # ENOTSUP 45 Operation not supported else - "$gnu_readlink" "$path" + __sudo -- "$gnu_readlink" "$path" fi else if [[ $option_validate == 'yes' ]]; then - "$gnu_readlink" --canonicalize-existing "$path" + __sudo -- "$gnu_readlink" --canonicalize-existing "$path" else - "$gnu_readlink" --canonicalize-missing "$path" + __sudo -- "$gnu_readlink" --canonicalize-missing "$path" fi fi } @@ -233,15 +249,15 @@ function fs_realpath() ( # -f fetches the absolutely resolved path if [[ $option_resolve == 'yes' && $option_relative == 'yes' && $option_validate == 'no' ]]; then - if [[ -L $path ]]; then - "$fallback_readlink" "$path" + if is-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + __sudo -- "$fallback_readlink" "$path" else return 45 # ENOTSUP 45 Operation not supported fi elif [[ $option_resolve == 'yes' && $option_relative == 'no' && $option_validate == 'yes' ]]; then # readlink will return correct output when valid # if invalid, will output nothing and have a failure exit code - "$fallback_readlink" -f "$path" + __sudo -- "$fallback_readlink" -f "$path" else return 45 # ENOTSUP 45 Operation not supported fi @@ -274,7 +290,7 @@ function fs_realpath() ( if [[ $option_resolve == 'yes' && $option_relative == 'no' && $option_validate == 'yes' ]]; then # realpath will return correct output when valid # if invalid, will output error (unless -q) and have failure exit code - "$fallback_realpath" "$path" + __sudo -- "$fallback_realpath" "$path" else return 45 # ENOTSUP 45 Operation not supported fi @@ -311,9 +327,9 @@ function fs_realpath() ( if [[ $option_relative == 'no' && $option_validate == 'no' ]]; then if [[ $option_resolve == 'no' ]]; then - fish -c 'builtin realpath --no-symlinks "$argv[1]"' -- "$path" + __sudo -- fish -c 'builtin realpath --no-symlinks "$argv[1]"' -- "$path" else - fish -c 'builtin realpath "$argv[1]"' -- "$path" + __sudo -- fish -c 'builtin realpath "$argv[1]"' -- "$path" fi else return 45 # ENOTSUP 45 Operation not supported diff --git a/commands/fs-rm b/commands/fs-rm index d8acfd5ca..9579a661c 100755 --- a/commands/fs-rm +++ b/commands/fs-rm @@ -41,7 +41,7 @@ function fs_rm() ( } # process - local item option_quiet='no' option_paths=() option_optional='no' option_confirm='yes' option_sudo='no' option_trash='no' option_user='' option_group='' + local item option_quiet='no' option_paths=() option_optional='no' option_confirm='yes' option_sudo='no' option_trash='' option_user='' option_group='' while [[ $# -ne 0 ]]; do item="$1" shift @@ -90,141 +90,152 @@ function fs_rm() ( # ===================================== # Dependencies - if [[ $option_trash == 'yes' ]]; then + local trash_and_delete_options=() trash_or_delete_default + if [[ $option_trash == 'yes' || ( -z $option_trash && $option_confirm != 'no' ) ]]; then setup-util-trash --quiet --optional --no-fallback - if __command_missing -- trash; then + fi + if __command_missing -- trash; then + if [[ $option_trash = 'yes' ]]; then echo-style --dim='Moving to trash is not available, falling back to immediate deletion for: ' --code="${option_paths[*]}" >/dev/stderr - option_trash='no' fi + option_trash='no' + trash_and_delete_options+=( + delete 'Delete it' + ) + else + trash_and_delete_options+=( + trash 'Move it to trash' + delete 'Delete it' + ) + fi + if [[ $option_trash = 'yes' ]]; then + trash_or_delete_default='trash' + elif [[ $option_trash = 'no' ]]; then + trash_or_delete_default='delete' + else + trash_or_delete_default=$'trash\ndelete' fi # ===================================== # Action - function eval_wrapper { - while [[ $1 == '--' ]]; do - shift - done - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - sudo-helper --no-wrap="$option_quiet" --quiet="$option_quiet" --inherit --user="$option_user" --group="$option_group" \ - -- "$@" - else - eval-helper --no-wrap="$option_quiet" --quiet="$option_quiet" \ - -- "$@" - fi - } - - function do_confirm_trim { - local path="$1" - - # if not a directory, we don't want to trim - if is-not-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then - return 200 # ECUSTOM 200 Not applicable - fi - - # if preconfirmed, skip the prompt - if [[ $option_confirm == 'no' ]]; then - return 0 - fi - - # if it is purely empty, skip the prompt - if is-empty-ls --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then - return 0 - fi - - # note its structure and size - eval_wrapper -- ls -la "$path" - - # confirm removal - confirm --positive --ppid=$$ -- "$( - echo-style --notice='Trim empty directories?' --bold=" $path " --notice="?" - )" + function __wrap { + sudo-helper --no-wrap="$option_quiet" --quiet="$option_quiet" --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$@" } - function do_confirm_removal { - local path="$1" - - # if preconfirmed, skip the prompt - if [[ $option_confirm == 'no' ]]; then - return 0 - fi - - # we want to prompt - eval_wrapper -- ls -la "$path" - - # is a directory, so output extra information - if [[ -d $path ]]; then - if __command_exists -- dust; then - eval_wrapper -- dust --no-percent-bars "$path" - eval_wrapper -- dust --no-percent-bars --filecount "$path" - elif __command_exists -- du; then - eval_wrapper -- du -ahd1 "$path" - fi + function __rm { + if [[ $option_trash == 'yes' ]]; then + __wrap trash "$path" || : + else + __wrap rm -rf "$path" || : fi - - # confirm removal - confirm --positive --ppid=$$ -- "$( - echo-style --warning='Confirm removal of non-empty' --bold=" $path " --warning="?" - )" } - local CONCLUSION='' + local CONCLUSION function do_rm { - local path="$1" confirm_trim_status confirm_removal_status + local path="$1" title='' body choice - # is the path missing + # is the path already removed? if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then CONCLUSION="$( - echo-style --green="was previously removed." + echo-style --green='was previously removed.' )" return 0 fi - # path exists - # delete empty directories - eval_capture --statusvar=confirm_trim_status -- do_confirm_trim "$path" - if [[ $confirm_trim_status -eq 0 ]]; then - # ignore stderr and do not wrap to prevent illogical cannot restore directory errors - eval_capture --ignore-stderr -- eval_wrapper -- find "$path" -empty -type d -delete - if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + # it is remaining, so prompt on what to do + choice='trim' + while :; do + # is the path is empty or a broken symlink, skip any confirm and remove it + if is-not-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + if is-empty-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path" || is-empty-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + __rm "$path" + if is-present --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + CONCLUSION="$( + echo-style --red='is empty, however it failed to remove.' + )" + return 66 # ENOTEMPTY 66 Directory not empty + fi + CONCLUSION="$( + echo-style --green='was empty, it was removed.' + )" + return 0 + fi + elif is-broken-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + __rm "$path" + if is-present --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + CONCLUSION="$( + echo-style --red='is a broken-symlink, however it failed to remove.' + )" + return 66 # ENOTEMPTY 66 Directory not empty + fi CONCLUSION="$( - echo-style --green="was only empty directories, it has been removed." + echo-style --green='was a broken-symlink, it was removed.' )" return 0 fi - fi - # there are leftovers - # confirm and remove, or no confirm and remove - eval_capture --statusvar=confirm_removal_status -- do_confirm_removal "$path" - if [[ $confirm_removal_status -eq 0 ]]; then - if [[ $option_trash == 'yes' ]]; then - eval_capture -- eval_wrapper -- trash "$path" - elif [[ $option_quiet == 'yes' ]]; then - eval_capture -- eval_wrapper -- rm -rf "$path" - else - eval_capture -- eval_wrapper -- rm -rfv "$path" + # construct body + if [[ -z $title ]]; then + title="$( + echo-style --notice1='The path is non-empty and queued for removal, what should be done?' --newline \ + --code-notice1="$path" + )" fi - fi - # detect successful removal + # refresh body + body="$(echo-style --reset)$(fs-structure --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path")" + + # confirm + if [[ $option_confirm != 'no' ]]; then + choice="$( + choose "$title" "$body" --defaults-exact="$choice" --label -- \ + trim 'Trim its redundant content and re-evaluate' \ + "${trash_and_delete_options[@]}" \ + abort 'Keep it, and abort the requested removal' + )" + fi + + # handle + if [[ $choice = 'trim' ]]; then + fs-trim --confirm="$option_confirm" --no-all="$option_confirm" --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path" + if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + CONCLUSION="$( + echo-style --green='was removed by trimming.' + )" + return 0 + fi + choice="$trash_or_delete_default" + elif [[ $choice = 'abort' || -z $choice ]]; then + CONCLUSION="$( + echo-style --yellow='was kept.' + )" + return 66 # ENOTEMPTY 66 Directory not empty + elif [[ $choice == 'trash' ]]; then + __wrap trash "$path" || : + break + elif [[ $choice == 'delete' ]]; then + __wrap rm -rf "$path" || : + break + fi + done + + # check after trash/delete if is-present --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then CONCLUSION="$( - echo-style --red="is non-empty, it has been kept." + echo-style --red='is non-empty, it failed to remove.' )" return 66 # ENOTEMPTY 66 Directory not empty fi - - # success if removed CONCLUSION="$( - echo-style --green="was non-empty, it was manually removed." + echo-style --green='was non-empty, it was manually removed.' )" + return 0 } function act { local path="$1" title rm_status - if is-present --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then - path="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path")" - fi + path="$(fs-absolute -- "$path")" + CONCLUSION='' if [[ $option_quiet == 'yes' ]]; then do_rm "$path" return @@ -236,13 +247,13 @@ function fs_rm() ( if [[ $option_sudo == 'yes' ]]; then title+=' --sudo' fi - if [[ $option_user == 'yes' ]]; then + if [[ -n $option_user ]]; then title+=" --user=$option_user" fi - if [[ $option_group == 'yes' ]]; then + if [[ -n $option_group ]]; then title+=" --group=$option_group" fi - title+=" $path" + title+=" $(echo-escape-command -- "$path")" echo-style --h2="$title" eval_capture --statusvar=rm_status -- do_rm "$path" if [[ $rm_status -eq 0 ]]; then diff --git a/commands/fs-size b/commands/fs-size deleted file mode 100755 index fd6b0111a..000000000 --- a/commands/fs-size +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash - -function fs_size() ( - source "$DOROTHY/sources/bash.bash" - - # ===================================== - # Arguments - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Output detailed size information about a path. - - USAGE: - fs-size [...options] [--] ... - - OPTIONS: - --quiet - If not provided, size details are wrapped in more information. - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_quiet='no' option_paths=() - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - '--no-verbose'* | '--verbose'*) - option_quiet="$(get-flag-value --non-affirmative --fallback="$option_quiet" -- "$item")" - ;; - '--no-quiet'* | '--quiet'*) - option_quiet="$(get-flag-value --affirmative --fallback="$option_quiet" -- "$item")" - ;; - '--path='*) option_paths+=("${item#*=}") ;; - '--') - option_paths+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; - esac - done - - # check - if [[ ${#option_paths[@]} -eq 0 ]]; then - help 'No s provided.' - fi - - # ===================================== - # Dependencies - - setup-util-dust --quiet - - # ===================================== - # Action - - local CONCLUSION='' - function do_size { - local path="$1" - - # is the path missing - if is-missing -- "$path"; then - CONCLUSION='is missing.' - return 2 # ENOENT 2 No such file or directory - fi - - # note its contents - __print_line - eval-helper --no-quiet --wrap \ - -- ls -la "$path" - __print_line - if [[ -d $path ]]; then - eval-helper --no-quiet --wrap \ - -- dust --no-percent-bars "$path" - __print_line - eval-helper --no-quiet --wrap \ - -- dust --no-percent-bars --filecount "$path" - __print_line - fi - - # note the conclusion - if is-empty-ls -- "$path"; then - CONCLUSION='is an empty directory.' - else - CONCLUSION='is a non-empty directory.' - fi - } - - function act { - local path="$1" title size_status - if [[ $option_quiet == 'yes' ]]; then - do_size "$path" - return - else - title="fs-size $(echo-escape-command -- "$path")" - echo-style --h2="$title" - eval_capture --statusvar=size_status -- do_size "$path" - if [[ $size_status -eq 0 ]]; then - echo-style --g2="$title" " $CONCLUSION" - else - echo-style --e2="$title" " $CONCLUSION" - return "$size_status" - fi - fi - } - - local path - for path in "${option_paths[@]}"; do - act "$path" - done -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - fs_size "$@" -fi diff --git a/commands/fs-structure b/commands/fs-structure index f539a149e..d0a91a889 100755 --- a/commands/fs-structure +++ b/commands/fs-structure @@ -15,8 +15,21 @@ function fs_structure() ( fs-structure [...options] [--] ... OPTIONS: + --no-perms + If specified, don't display permission and ownerships information. + Only applicable when [eza] is used. + --no-time + If specified, don't display time information. + --no-eza + If specified, don't try to use [eza]. + --no-color + If specified, don't use colors. + --sudo - If specified, use sudo when removing the files. + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -25,7 +38,8 @@ function fs_structure() ( } # process - local item option_paths=() option_sudo='no' + local item option_inputs=() option_sudo='no' option_user='' option_group='' option_perms='' option_time='' option_eza='' option_color + option_color="$(get-terminal-color-support --fallback=yes -- "$@")" while [[ $# -ne 0 ]]; do item="$1" shift @@ -34,59 +48,141 @@ function fs_structure() ( '--no-sudo'* | '--sudo'*) option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" ;; - '--path='*) paths+=("${item#*=}") ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--no-perms'* | '--perms'* | '--no-permissions'* | '--permissions'*) + option_perms="$(get-flag-value --affirmative --fallback="$option_perms" -- "$item")" + ;; + '--no-time'* | '--time'*) + option_time="$(get-flag-value --affirmative --fallback="$option_time" -- "$item")" + ;; + '--no-eza'* | '--eza'*) + option_eza="$(get-flag-value --affirmative --fallback="$option_eza" -- "$item")" + ;; + '--no-color'* | '--color'*) : ;; # handled by get-terminal-color-support '--') - option_paths+=("$@") + option_inputs+=("$@") shift $# break ;; '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; + *) option_inputs+=("$item") ;; esac done # check - if [[ ${#option_paths[@]} -eq 0 ]]; then - help 'No s provided.' + if [[ ${#option_inputs[@]} -eq 0 ]]; then + option_inputs+=('.') fi # ===================================== # Action - # prepare - local cmd=() - if [[ $option_sudo == 'yes' ]]; then - cmd+=( - 'sudo-helper' - '--' - ) + # dependencies + if [[ $option_eza != 'no' ]]; then + setup-util-eza --quiet --optional fi - cmd+=( - 'ls' - '-lA' - ) - # -A, --almost-all: do not list implied . and .. - # -l: use a long listing format + # another alternative is lsd: + # lsd -lA --total-size --header + # however it doesn't support removing time, and removing owner/permissions + + # prepare + local eza_cmd=() ls_cmd=() # dust_sizes_cmd=() dust_counts_cmd=() du_cmd=() + if [[ $option_eza != 'no' ]] && __command_exists -- eza; then + # -h, --header: Add a header row to each column. + # -l, --long: Display extended file metadata as a table. + # -A, --almost-all: Equivalent to –all; included for compatibility with ls -A. + # -M, --mounts: how mount details (Linux and Mac only) + # --total-size: show the size of a directory as the size of all files and directories inside (unix only) + eza_cmd+=(eza -hlAM --total-size) + + # hide the permissions? + if [[ $option_perms == 'no' ]]; then + eza_cmd+=(--no-permissions --no-user) + fi + + # hide the time? + if [[ $option_time == 'no' ]]; then + eza_cmd+=(--no-time) + fi - # hide the time - if is-mac; then - # -D format: When printing in the long (-l) format, use format to format the date and time output. The argument format is a string used by trftime(3). Depending on the choice of format string, this may result in a different number of columns in the output. This option overrides the -T option. This option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”). - cmd+=('-D' '') + # disable colors? + if [[ $option_color == 'no' ]]; then + eza_cmd+=('--color=never') + elif [[ $option_color == 'yes' ]]; then + eza_cmd+=('--color=always') + fi else - # --time-style=TIME_STYLE: time/date format with -l; see TIME_STYLE below - cmd+=("--time-style=+''") + # -A, --almost-all: do not list implied . and .. + # -l: use a long listing format + # -h: When used with the -l option, use unit suffixes: Byte, Kilobyte, Megabyte, Gigabyte, Terabyte and Petabyte in order to reduce the number of digits to four or fewer using base 2 for sizes. This option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”). + ls_cmd+=(ls -lAh) + + # hide the time? + if [[ $option_time == 'no' ]]; then + if is-mac; then + # -D format: When printing in the long (-l) format, use format to format the date and time output. The argument format is a string used by trftime(3). Depending on the choice of format string, this may result in a different number of columns in the output. This option overrides the -T option. This option is not defined in IEEE Std 1003.1-2008 (“POSIX.1”). + ls_cmd+=('-D' '') + else + # --time-style=TIME_STYLE: time/date format with -l; see TIME_STYLE below + ls_cmd+=("--time-style=+''") + fi + fi + + # counts + # if __command_exists -- dust; then + # dust_sizes_cmd+=(dust --no-percent-bars) + # dust_counts_cmd+=(dust --no-percent-bars --filecount) + # elif __command_exists -- du; then + # du_cmd+=(du -hd1) + # fi + + # disable colors? + if [[ $option_color == 'no' ]]; then + ls_cmd+=('--color=never') + # dust_sizes_cmd+=('--no-colors') + # dust_counts_cmd+=('--no-colors') + elif [[ $option_color == 'yes' ]]; then + ls_cmd+=('--color=always') + # dust_sizes_cmd+=('--force-colors') + # dust_counts_cmd+=('--force-colors') + fi fi - local path - for path in "${option_paths[@]}"; do + # helpers + function __wrap { + sudo-helper --inherit --no-wrap --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$@" + } + function __act() ( + # subshell to prevent multiple cds conflicting with each other + local path="$1" basename if [[ -d $path ]]; then cd "$path" - "${cmd[@]}" + basename='.' else cd "$(dirname "$path")" basename="$(basename "$path")" - "${cmd[@]}" "$basename" | sd --fixed-strings "$basename" '' fi + if [[ ${#eza_cmd[@]} -ne 0 ]]; then + __wrap "${eza_cmd[@]}" "$basename" + fi + if [[ ${#ls_cmd[@]} -ne 0 ]]; then + __wrap "${ls_cmd[@]}" "$basename" + fi + # if [[ ${#dust_sizes_cmd[@]} -ne 0 ]]; then + # __wrap "${dust_sizes_cmd[@]}" "$basename" + # fi + # if [[ ${#dust_counts_cmd[@]} -ne 0 ]]; then + # __wrap "${dust_counts_cmd[@]}" "$basename" + # fi + # if [[ ${#du_cmd[@]} -ne 0 ]]; then + # __wrap "${du_cmd[@]}" "$basename" + # fi + ) + + local input + for input in "${option_inputs[@]}"; do + __act "$input" done ) diff --git a/commands/fs-temp b/commands/fs-temp index 001c51a66..44ff09b8b 100755 --- a/commands/fs-temp +++ b/commands/fs-temp @@ -44,7 +44,8 @@ function fs_temp() ( When generating a , use this . --touch - If a was provided, then touch the file to ensure it exists. + Unless falsey, then directories will be made. + If truthy, the file, if applicable, will be created. QUIRKS: Unless [--touch] is truthy, then file paths won't be created on the file system. @@ -67,7 +68,7 @@ function fs_temp() ( local option_prefix='' local option_suffix='' local option_extension='' - local option_touch='no' + local option_touch='' while [[ $# -ne 0 ]]; do item="$1" shift @@ -134,7 +135,9 @@ function fs_temp() ( done # ensure the root now exists - __mkdirp "$root" + if [[ $option_touch != 'no' ]]; then + __mkdirp "$root" + fi # if no files, output directory path if [[ ${#option_files[@]} -eq 0 ]]; then diff --git a/commands/fs-trim b/commands/fs-trim new file mode 100755 index 000000000..6ab2f727a --- /dev/null +++ b/commands/fs-trim @@ -0,0 +1,347 @@ +#!/usr/bin/env bash + +function fs_trim_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- fs-trim -- + + eval-tester --name='empty args' --status=22 \ + -- fs-trim -- '' '' + + eval-tester --name='missing' \ + -- fs-trim -- "$DOROTHY/this-doesnt-exist" + + # test working symlinks + local root dir_target dir_symlink file_in_dir_target file_target file_symlink file_in_dir_symlink + root="$(fs-temp --directory='fs-trim' --touch)" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + file_in_dir_target="$(fs-temp --root="$dir_target" --file='file_in_dir_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + file_in_dir_symlink="$(fs-temp --root="$dir_target" --file='file_in_dir_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + symlink-helper --existing="$file_in_dir_target" --symlink="$file_in_dir_symlink" --quiet + + # add data and test no-op trim + __print_line 'sup' >"$file_in_dir_target" + eval-tester --name='trim non-empty dir target should be no-op' \ + -- fs-trim -- "$dir_target" + eval-tester --name='trim non-empty dir symlink should be no-op' \ + -- fs-trim -- "$dir_symlink" + eval-tester --name='trim non-empty dir should be no-op (check)' \ + -- is-present -- "$root" "$dir_target" "$file_target" "$file_in_dir_target" "$dir_symlink" "$file_symlink" "$file_in_dir_symlink" + + # test symlinks + eval-tester --name='trim file symlink should remove its empty target and itself' \ + -- fs-trim -- "$file_symlink" + eval-tester --name='trim file symlink should remove its empty target and itself (check)' \ + -- is-missing -- "$file_target" "$file_symlink" + + # make it empty and test again + printf '' >"$file_in_dir_target" + eval-tester --name='trim root without --all should be no-op' \ + -- fs-trim -- "$root" + eval-tester --name='trim root without --all should be no-op (check)' \ + -- is-present -- "$root" "$dir_target" "$file_in_dir_target" "$dir_symlink" "$file_in_dir_symlink" + eval-tester --name='trim root with --all should trim everything' \ + -- fs-trim --all -- "$root" + eval-tester --name='trim root with --all should trim everything (check)' \ + -- is-missing -- "$root" + + # recreate empty dirs and files + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + file_in_dir_target="$(fs-temp --root="$dir_target" --file='file_in_dir_target' --touch)" + eval-tester --name='trim root that has empty files and empty dirs, with --empty-files, should trim everything' \ + -- fs-trim --empty-files -- "$root" + eval-tester --name='trim root that has empty files and empty dirs, with --empty-files, should trim everything (check)' \ + -- is-missing -- "$root" + + # recreate empty dirs + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + eval-tester --name='trim root that has only empty dirs, should trim everything' \ + -- fs-trim -- "$root" + eval-tester --name='trim root that has only empty dirs, should trim everything (check)' \ + -- is-missing -- "$root" + + echo-style --g1="TEST: $0" + return 0 +) +function fs_trim() ( + source "$DOROTHY/sources/bash.bash" + local junk_filenames=( + '.DS_Store' + '._.DS_Store' + 'Desktop.ini' + 'Thumbs.db' + 'node_modules' + 'pnp' + 'package-lock.json' + 'yarn.lock' + '.pnp.js' + '.log' + ) + local junk_find=() remove_filenames=() item + for item in "${junk_filenames[@]}"; do + item="$(__lowercase_string -- "$item")" + remove_filenames+=("$item") + junk_find+=(-iname "$item" -or) + done + junk_find=("${junk_find[@]:0:${#junk_find[@]}-1}") + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Trim s of empty files and directories, including itself it also became empty. + + USAGE: + fs-trim [...options] [--] ... + + OPTIONS: + --quiet | --no-verbose + If specified, do not confirm for action if none provided. + --confirm + If specified, confirm for action if none provided. + + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + --junk + If provided, paths of these case-insensitive filenames will be removed: ${junk_filenames[*]} + --empty-files + If provided, empty files will be removed. + --broken-symlinks + If provided, broken symlinks will be removed. + --empty-directories + If provided, empty directories will be removed. + + RETURNS: + [0] if all s were trimmed of empty files/directories + [22] if empty arguments are provided + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' option_confirm='' option_all='' option_junk='' option_empty_files='' option_broken_symlinks='' option_empty_directories='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--no-confirm'* | '--confirm'*) + option_confirm="$(get-flag-value --affirmative --fallback="$option_confirm" -- "$item")" + ;; + '--no-all'* | '--all'*) + option_all="$(get-flag-value --affirmative --fallback="$option_all" -- "$item")" + if [[ $option_all == 'yes' ]]; then + option_junk='yes' + option_empty_files='yes' + option_broken_symlinks='yes' + option_empty_directories='yes' + fi + ;; + '--no-junk'* | '--junk'*) + option_junk="$(get-flag-value --affirmative --fallback="$option_junk" -- "$item")" + ;; + '--no-empty-files'* | '--empty-files'*) + option_empty_files="$(get-flag-value --affirmative --fallback="$option_empty_files" -- "$item")" + ;; + '--no-broken-symlinks'* | '--broken-symlinks'*) + option_broken_symlinks="$(get-flag-value --affirmative --fallback="$option_broken_symlinks" -- "$item")" + ;; + '--no-empty-directories'* | '--empty-directories'*) + option_empty_directories="$(get-flag-value --affirmative --fallback="$option_empty_directories" -- "$item")" + ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + option_inputs+=("$(pwd)") + fi + + # ===================================== + # Action + + local selection=() + if [[ $option_junk == 'yes' ]]; then + selection+=('junk') + fi + if [[ $option_empty_files == 'yes' ]]; then + selection+=('files') + fi + if [[ $option_broken_symlinks == 'yes' ]]; then + selection+=('broken') + fi + if [[ $option_empty_directories == 'yes' ]]; then + selection+=('directories') + fi + if [[ ${#selection[@]} -eq 0 ]]; then + selection+=('directories') + option_confirm='yes' + fi + + # helpers + local confirmed='no' + function __confirm { + local title + if [[ $confirmed = 'yes' ]]; then + return 0 + fi + confirmed='yes' + if [[ $option_confirm = 'yes' ]]; then + title="$( + echo-style --notice1='What items do you wish to trim for:' --newline \ + --code-notice1="$(fs-absolute -- "${option_inputs[@]}")" + )" + mapfile -t selection < <( + choose "$title" --multiple --defaults-exact="$(__print_lines "${selection[@]}")" --label -- \ + junk "Junk files: $(echo-style --newline --dim="${junk_filenames[*]}")" \ + files 'Empty files' \ + broken 'Broken symlinks' \ + directories 'Empty directories' + ) + fi + # still apply defaults in no-confirm mode + for item in "${selection[@]}"; do + case "$item" in + 'junk') option_junk='yes' ;; + 'files') option_empty_files='yes' ;; + 'broken') option_broken_symlinks='yes' ;; + 'directories') option_empty_directories='yes' ;; + esac + done + } + function __wrap { + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$@" + } + function do_find { + local path="$1" refresh='yes' + while [[ $refresh = 'yes' ]]; do + refresh='no' + if is-not-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + break + fi + if [[ $option_junk == 'yes' ]] && __wrap find "$path" \( "${junk_find[@]}" \) -delete -print | ifne -n false >/dev/null; then + refresh='yes' + fi + if [[ $option_empty_files == 'yes' ]] && __wrap find "$path" -type f -empty -delete -print | ifne -n false >/dev/null; then + refresh='yes' + fi + if [[ $option_broken_symlinks == 'yes' ]] && __wrap find "$path" -type l -exec fs-trim-symlinks.bash -- {} + | ifne -n false >/dev/null; then + refresh='yes' + fi + if [[ $option_empty_directories == 'yes' ]] && __wrap find "$path" -type d -empty -delete -print | ifne -n false >/dev/null; then + refresh='yes' + fi + done + } + + # action + local input target input_lowercase_filename target_lowercase_filename + for input in "${option_inputs[@]}"; do + # check is invalid + if [[ -z $input ]]; then + return 22 # EINVAL 22 Invalid argument + fi + # just -e is faulty, as -e fails on broken symlinks + if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + # already missing on the filesystem + continue + fi + # confirm the user for action if still ambiguous + __confirm + # check we have something to do + if [[ $option_junk != 'yes' && $option_empty_files != 'yes' && $option_broken_symlinks != 'yes' && $option_empty_directories != 'yes' ]]; then + # no-op, user is aborting essentially + return 0 + fi + # continue + if is-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + # it is a symlink + if is-broken-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + # it is broken, remove it if desired + if [[ $option_broken_symlinks == 'yes' ]]; then + __wrap rm -f "$input" + fi + continue + fi + + # get the target + target="$(fs-realpath -- "$input")" + + # check if it is a junk file + if [[ $option_junk == 'yes' ]]; then + target_lowercase_filename="$(__lowercase_string -- "$(fs-filename -- "$target")")" + input_lowercase_filename="$(__lowercase_string -- "$(fs-filename -- "$input")")" + if is-needle --needle="$input_lowercase_filename" -- "${remove_filenames[@]}" || is-needle --needle="$target_lowercase_filename" -- "${remove_filenames[@]}"; then + __wrap rm -f "$target" "$input" + continue + fi + fi + + # trim target and symlink + if is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$target"; then + # it is a directory or symlink to a directory on the filesystem + do_find "$target" + # remove the symlink if its target was just removed + if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$target"; then + __wrap rm -f "$input" + fi + elif [[ $option_empty_files == 'yes' ]] && is-empty-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$target"; then + __wrap rm -f "$target" "$input" + fi + else + # check if it is a junk file + if [[ $option_junk == 'yes' ]]; then + input_lowercase_filename="$(__lowercase_string -- "$(fs-filename -- "$input")")" + if is-needle --needle="$input_lowercase_filename" -- "${remove_filenames[@]}"; then + __wrap rm -f "$input" + continue + fi + fi + + # trim input target + if is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + # it is a directory or symlink to a directory on the filesystem + do_find "$input" + elif [[ $option_empty_files == 'yes' ]] && is-empty-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + __wrap rm -f "$input" + fi + fi + done +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + fs_trim_test + else + fs_trim "$@" + fi +fi diff --git a/commands/fs-trim-symlinks.bash b/commands/fs-trim-symlinks.bash new file mode 100755 index 000000000..e8b278430 --- /dev/null +++ b/commands/fs-trim-symlinks.bash @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +remove=() +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ -L $1 && ! -e $1 ]]; then + remove+=("$1") + fi + shift +done +if [[ ${#remove[@]} -ne 0 ]]; then + rm -fv "${remove[@]}" +fi +exit 0 diff --git a/commands/get-terminal-color-support b/commands/get-terminal-color-support index 552aae7d2..3d38f4ae9 100755 --- a/commands/get-terminal-color-support +++ b/commands/get-terminal-color-support @@ -30,9 +30,14 @@ function get_terminal_color_support() ( Checks for [--[no-]color[s]=[yes|no]] arguments, as well as [[NO[_]]COLOR] and [CRON], [CRONITOR_EXEC], and [TERM] environment variables. RETURNS: + If in quiet mode: [0] if enabled [1] if disabled [91] if not determined (no fallback). + + If not in quiet mode: + [0] if enabled or disabled + [91] if not determined (no fallback). EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/get-terminal-theme b/commands/get-terminal-theme index 298dd875e..10f3c9b02 100755 --- a/commands/get-terminal-theme +++ b/commands/get-terminal-theme @@ -114,7 +114,7 @@ function get_terminal_theme() ( local bg="$COLORFGBG" if [[ -n $bg ]]; then bg="${bg##*;}" # trim everything prior to the last ; - if [[ $bg == 'default' ]] || (is-digit -- "$bg" && [[ $bg -le 6 || $bg -eq 8 ]]); then + if [[ $bg == 'default' ]] || (is-integer -- "$bg" && [[ $bg -le 6 || $bg -eq 8 ]]); then theme='light' else theme='dark' diff --git a/commands/gocryptfs-helper b/commands/gocryptfs-helper index e187a2782..cad1fea22 100755 --- a/commands/gocryptfs-helper +++ b/commands/gocryptfs-helper @@ -282,7 +282,7 @@ function gocryptfs_helper() ( )" # create - if is-missing -- "$new_vault" || is-empty-ls -- "$new_vault"; then + if is-missing -- "$new_vault" || is-empty-directory -- "$new_vault"; then __mkdirp "$new_vault" echo-style --bold="Creating a new vault vault at [$new_vault] with algorithm [$algorithm]." gocryptfs --init --"$algorithm" "$new_vault" @@ -337,7 +337,7 @@ function gocryptfs_helper() ( echo-style --notice="The new vault [$new_vault] already exists..." --newline \ 'This means a vault upgrade was started but not finished.' --newline \ 'We will continue with this vault, if you wish setup a new one, remove the old one first.' - elif is-missing -- "$new_vault" || is-empty-ls -- "$new_vault"; then + elif is-missing -- "$new_vault" || is-empty-directory -- "$new_vault"; then echo-style --bold="Creating a new vault vault at [$new_vault] with algorithm [$algorithm]." gocryptfs --init --"$algorithm" "$new_vault" act_chown "$new_vault" @@ -371,11 +371,11 @@ function gocryptfs_helper() ( # replace __print_lines '' 'Prepping replacement:' fs-rm --no-confirm -- "$old_plain/" - fs-size -- "$new_plain/" + fs-structure -- "$new_plain/" mount-helper \ -- --unmount --target="$old_plain" \ -- --unmount --target="$new_plain" - fs-size -- "$old_vault" "$new_vault" + fs-structure -- "$old_vault" "$new_vault" # @todo style with var_dump or something echo-style --bold='Confirm the following replacement:' --newline \ --bold+red='Delete:' ' ' --code="$old_vault" --newline \ diff --git a/commands/is-abort b/commands/is-abort index 3de8812cb..b360edbde 100755 --- a/commands/is-abort +++ b/commands/is-abort @@ -28,8 +28,8 @@ function is_abort() ( 143: SIGTERM (Termination signal. Sent to request a process to terminate gracefully.) RETURNS: - [0] if ANY s were an abort - [1] if ALL were not abort + [0] if ANY s are an abort + [1] if all s are not abort EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-admin b/commands/is-admin index 4da4c8562..dd256804a 100755 --- a/commands/is-admin +++ b/commands/is-admin @@ -20,8 +20,8 @@ function is_admin() ( User to check is an administrator. RETURNS: - [0] if all s were an administrator. - [1] if any s were not an administrator. + [0] if all s are an administrator. + [1] if any s are not an administrator. EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -64,7 +64,7 @@ function is_admin() ( fi # act - if is-user-in-group --group="$group" "${option_users[@]}"; then + if is-user-in-group --group="$group" -- "${option_users[@]}"; then return 0 else return 1 diff --git a/commands/is-affirmative b/commands/is-affirmative index b88b71499..7aaa50440 100755 --- a/commands/is-affirmative +++ b/commands/is-affirmative @@ -22,9 +22,9 @@ function __is_affirmative() ( Ignore/skip empty values. RETURNS: - [0] if all s were affirmative - [1] if any was non-affirmative - [91] if invalid values were provided, or no s were provided + [0] if all s are affirmative + [1] if any s are non-affirmative + [91] if invalid values are provided, or no s are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" || return diff --git a/commands/is-arch b/commands/is-arch index be0791e21..f05de4af1 100755 --- a/commands/is-arch +++ b/commands/is-arch @@ -39,11 +39,8 @@ function is_arch() ( # ===================================== # Action - if [[ -f '/etc/arch-release' ]]; then - return 0 - else - return 1 - fi + [[ -f '/etc/arch-release' ]] + return ) # fire if invoked standalone diff --git a/commands/is-bash-version-outdated b/commands/is-bash-version-outdated index 6928ea317..acb25308f 100755 --- a/commands/is-bash-version-outdated +++ b/commands/is-bash-version-outdated @@ -84,11 +84,8 @@ function is_bash_version_outdated() ( echo-style --bold='latest known bash version' ' = ' --invert="$BASH_VERSION_LATEST" echo-style --bold='version outdated' ' = ' --invert="$IS_BASH_VERSION_OUTDATED" fi - if [[ $IS_BASH_VERSION_OUTDATED == 'yes' ]]; then - return 0 - else - return 1 - fi + [[ $IS_BASH_VERSION_OUTDATED == 'yes' ]] + return ) # fire if invoked standalone diff --git a/commands/is-broken-symlink b/commands/is-broken-symlink new file mode 100755 index 000000000..d8b920486 --- /dev/null +++ b/commands/is-broken-symlink @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +function is_broken_symlink_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-broken-symlink -- + + eval-tester --name='empty args' --status=22 \ + -- is-broken-symlink -- '' '' + + eval-tester --name='missing' --status=9 \ + -- is-broken-symlink -- "$DOROTHY/this-doesnt-exist" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-broken-symlink' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-broken-symlink' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-broken-symlink' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-broken-symlink' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' --status=1 \ + -- is-broken-symlink -- "$dir_symlink" + + eval-tester --name='symlink file' --status=1 \ + -- is-broken-symlink -- "$file_symlink" + + eval-tester --name='symlink dir then dir' --status=1 \ + -- is-broken-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' --status=1 \ + -- is-broken-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=1 \ + -- is-broken-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- is-broken-symlink -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- is-broken-symlink -- "$file_symlink" + + eval-tester --name='broken symlink dir then dir' --status=9 \ + -- is-broken-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=9 \ + -- is-broken-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=9 \ + -- is-broken-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + echo-style --g1="TEST: $0" + return 0 +) +function is_broken_symlink() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are a broken symlink. + + USAGE: + is-broken-symlink [...options] [--] ... + + OPTIONS: + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + RETURNS: + [0] if all s were a broken symlink + [1] if any s were a symlink that was not broken symlink + [9] if any s were not a symlink + [22] if empty arguments are provided + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided.' + fi + + # ===================================== + # Action + + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-broken-symlink.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_broken_symlink_test + else + is_broken_symlink "$@" + fi +fi diff --git a/commands/is-broken-symlink.bash b/commands/is-broken-symlink.bash new file mode 100755 index 000000000..c5fbc70d1 --- /dev/null +++ b/commands/is-broken-symlink.bash @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -L $1 ]]; then + # not a symlink + exit 9 # EBADF 9 Bad file descriptor + fi + if [[ -e $1 ]]; then + exit 1 + fi + shift +done +exit 0 diff --git a/commands/is-digit b/commands/is-digit index 4e7a2c5cc..3eed85c3f 100755 --- a/commands/is-digit +++ b/commands/is-digit @@ -4,7 +4,7 @@ function is_digit_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --name='0-9 ARE a digits' --status=0 \ + eval-tester --name='0-9 ARE a digits' \ -- is-digit -- 0 1 2 3 4 5 6 7 8 9 eval-tester --name='decimal IS NOT a digit' --status=1 \ @@ -41,7 +41,8 @@ function is_digit() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Checks if the is a single digit (0-9) + Checks if the is a single digit (0-9). + Companion to [is-number] and [is-integer]. USAGE: is-digit [...options] [--] @@ -51,8 +52,8 @@ function is_digit() ( Verify this is a valid digit RETURNS: - [0] if all s were valid digits - [1] if any s were not valid digits + [0] if all s are valid digits + [1] if any s are not valid digits EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-directory b/commands/is-directory index ad6c7ddc8..a6914ae39 100755 --- a/commands/is-directory +++ b/commands/is-directory @@ -1,5 +1,63 @@ #!/usr/bin/env bash +function is_directory_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-directory -- + + eval-tester --name='empty args' --status=22 \ + -- is-directory -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-directory -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='directory' \ + -- is-directory -- "$DOROTHY" + + eval-tester --name='file' --status=1 \ + -- is-directory -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=1 \ + -- is-directory -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' --status=1 \ + -- is-directory -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='dir then file then invalid' --status=1 \ + -- is-directory -- "$DOROTHY" "$DOROTHY/README.md" '' + + eval-tester --name='dir then invalid then file' --status=22 \ + -- is-directory -- "$DOROTHY" '' "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-directory' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-directory' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-directory' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-directory' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' \ + -- is-directory -- "$dir_symlink" + + eval-tester --name='symlink file' --status=1 \ + -- is-directory -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=1 \ + -- is-directory -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=1 \ + -- is-directory -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_directory() ( source "$DOROTHY/sources/bash.bash" @@ -9,21 +67,23 @@ function is_directory() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if all s are a directory (and not a symlink). + Check if all s are a file, or an unbroken symlink to a file. + Companion to [is-not-directory], [echo-if-directory], [is-empty-directory]. USAGE: is-directory [...options] [--] ... OPTIONS: --sudo - If specified, use sudo on directorysystem interactions. + If specified, use sudo on filesystem interactions. --user= --group= - If specified use this user and/or group for directorysystem interactions. + If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were a directory - [1] if any s were not a directory + [0] if all s are a directory, or an unbroken symlink to a directory + [1] if any s are neither a directory, nor an unbroken symlink to a directory + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -61,20 +121,15 @@ function is_directory() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-directory.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-directory.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-directory.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_directory "$@" + if [[ $* == '--test' ]]; then + is_directory_test + else + is_directory "$@" + fi fi diff --git a/commands/is-directory.bash b/commands/is-directory.bash index b582ca94e..ce013d181 100755 --- a/commands/is-directory.bash +++ b/commands/is-directory.bash @@ -7,7 +7,12 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do - [[ -d $1 && ! -L $1 ]] || exit + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -d $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-empty-directory b/commands/is-empty-directory new file mode 100755 index 000000000..c158d4793 --- /dev/null +++ b/commands/is-empty-directory @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +function is_empty_directory_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + local dir + dir="$(fs-temp --directory='is-empty-directory' --directory --touch)" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-empty-directory -- + + eval-tester --name='empty args' --status=22 \ + -- is-empty-directory -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-empty-directory -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='empty dir is empty' \ + -- is-empty-directory -- "$dir" + + eval-tester --name='empty dirs are empty' \ + -- is-empty-directory -- "$dir" "$dir" + + eval-tester --name='non-empty dir is not empty' --status=1 \ + -- is-empty-directory -- "$DOROTHY" + + eval-tester --name='file is special failure' --status=20 \ + -- is-empty-directory -- "$DOROTHY/README.md" + + eval-tester --name='file then non-empty dir is special failure' --status=20 \ + -- is-empty-directory -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='non-empty dir then file standard failure' --status=1 \ + -- is-empty-directory -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='empty then file then non-empty dir is special failure' --status=20 \ + -- is-empty-directory -- "$dir" "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='empty then non-empty dir then file standard failure' --status=1 \ + -- is-empty-directory -- "$dir" "$DOROTHY" "$DOROTHY/README.md" + + echo-style --g1="TEST: $0" + return 0 +) +function is_empty_directory() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Checks if a is an empty directory, aka a directory without any contents. + + USAGE: + is-empty-directory [...options] [--] ... + + OPTIONS: + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + RETURNS: + [0] if all s are empty + [1] if any s are not empty + [20] if any s is not a directory. + [22] if empty arguments are provided + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift "$#" + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # check + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help "No s provided." + fi + + # ===================================== + # Action + + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-empty-directory.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_empty_directory_test + else + is_empty_directory "$@" + fi +fi diff --git a/commands/is-empty-directory.bash b/commands/is-empty-directory.bash new file mode 100755 index 000000000..b54ebca08 --- /dev/null +++ b/commands/is-empty-directory.bash @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -d $1 ]]; then + exit 20 # ENOTDIR 20 Not a directory + fi + if [[ -n "$(ls -A "$1")" ]]; then + exit 1 + fi + shift +done +exit 0 diff --git a/commands/is-empty-file b/commands/is-empty-file new file mode 100755 index 000000000..8fd30558e --- /dev/null +++ b/commands/is-empty-file @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +function is_empty_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + local dir file + dir="$(fs-temp --directory='is-empty-file' --directory --touch)" + file="$(fs-temp --directory='is-empty-file' --file --touch)" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-empty-file -- + + eval-tester --name='empty args' --status=22 \ + -- is-empty-file -- '' '' + + eval-tester --name='missing' --status=9 \ + -- is-empty-file -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='empty dirs' --status=9 \ + -- is-empty-file -- "$dir" "$dir" + + eval-tester --name='empty files' \ + -- is-empty-file -- "$file" "$file" + + eval-tester --name='non-empty dir' --status=9 \ + -- is-empty-file -- "$DOROTHY" + + eval-tester --name='non-empty file' --status=1 \ + -- is-empty-file -- "$DOROTHY/README.md" + + echo-style --g1="TEST: $0" + return 0 +) +function is_empty_file() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Checks if a is an empty file, aka a file without content, aka a file with zero-length content. + + USAGE: + is-empty-file [...options] [--] ... + + OPTIONS: + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + RETURNS: + [0] if all s are empty file + [1] if any s are not an empty file. + [9] if any s are not a file. + [22] if empty arguments are provided. + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift "$#" + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # check + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help "No s provided." + fi + + # ===================================== + # Action + + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-empty-file.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_empty_file_test + else + is_empty_file "$@" + fi +fi diff --git a/commands/is-empty-file.bash b/commands/is-empty-file.bash new file mode 100755 index 000000000..8605ce148 --- /dev/null +++ b/commands/is-empty-file.bash @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -f $1 ]]; then + # not a file nor symlink to a file + exit 9 # EBADF 9 Bad file descriptor + fi + if [[ -s $1 ]]; then + # not empty + exit 1 + fi + shift +done +exit 0 diff --git a/commands/is-empty-ls b/commands/is-empty-ls deleted file mode 100755 index b90b0dbc0..000000000 --- a/commands/is-empty-ls +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash - -function is_empty_ls() ( - source "$DOROTHY/sources/bash.bash" - - # ===================================== - # Arguments - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Checks if has no contents. - - USAGE: - is-empty-ls [...options] [--] ... - - OPTIONS: - --sudo - If specified, use sudo on filesystem interactions. - --user= - --group= - If specified use this user and/or group for filesystem interactions. - - RETURNS: - [0] if all s were empty. - [1] if any s were not empty. - [2] if any were not a directory. - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_paths=() option_sudo='no' option_user='' option_group='' - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - '--no-sudo'* | '--sudo'*) - option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" - ;; - '--user='*) option_user="${item#*=}" ;; - '--group='*) option_group="${item#*=}" ;; - '--') - option_paths+=("$@") - shift "$#" - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; - esac - done - - # check - if [[ ${#option_paths[@]} -eq 0 ]]; then - help "No s provided." - fi - - # ===================================== - # Action - - # call this again, but inside sudo - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - sudo-helper --inherit --user="$option_user" --group="$option_group" \ - -- is-empty-ls -- "${option_paths[@]}" - return - fi - - local path - for path in "${option_paths[@]}"; do - if [[ ! -d $path ]]; then - echo-error 'A path was was not a directory: ' --code="$path" - return 2 - fi - [[ -z "$(ls -A "$path")" ]] || return # explicit return with [[ required for bash v3 - done - return 0 -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_empty_ls "$@" -fi diff --git a/commands/is-empty-string b/commands/is-empty-string deleted file mode 100755 index d9cb688b6..000000000 --- a/commands/is-empty-string +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -function is_empty_string_test() ( - source "$DOROTHY/sources/bash.bash" - echo-style --h1="TEST: $0" - - eval-tester --name='zero-length is empty' \ - -- is-empty-string -- '' - - eval-tester --name='space is empty' \ - -- is-empty-string -- $' ' - - eval-tester --name='newline is empty' \ - -- is-empty-string -- $'\n' - - eval-tester --name='tab is empty' \ - -- is-empty-string -- $'\n' - - eval-tester --name='whitespace combo is empty' \ - -- is-empty-string -- $'\n\t ' - - eval-tester --name='letters not empty' --status=1 \ - -- is-empty-string -- 'a' - - echo-style --g1="TEST: $0" - return 0 -) -function is_empty_string() ( - source "$DOROTHY/sources/bash.bash" - - # help - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Check if an input is only whitespace characters. - - USAGE: - is-empty-string [...options] [--] - - OPTIONS: - | --string= - Verify this is an empty string - - RETURNS: - [0] if all s were empty strings - [1] if any s were not empty strings - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_inputs=() - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - '--string='*) option_inputs+=("${item#*=}") ;; - '--') - option_inputs+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_inputs+=("$item") ;; - esac - done - - # verify - if [[ ${#option_inputs[@]} -eq 0 ]]; then - help 'No s provided.' - fi - - # ===================================== - # Action - - local value="${option_inputs[*]}" - if [[ $value =~ ^[[:space:]]*$ ]]; then - # value is only whitespace characters - return 0 - else - return 1 - fi -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - if [[ $* == '--test' ]]; then - is_empty_string_test - else - is_empty_string "$@" - fi -fi diff --git a/commands/is-empty-value b/commands/is-empty-value index 67e3e3de7..a2d6438bf 100755 --- a/commands/is-empty-value +++ b/commands/is-empty-value @@ -1,5 +1,27 @@ #!/usr/bin/env bash +function is_empty_value_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-empty-value -- + + eval-tester --name='a single empty string is empty' --status= \ + -- is-empty-value -- '' + + eval-tester --name='empty values are empty' \ + -- is-empty-value -- '' ' ' $'\t' $'\n' $' \t\n' null NULL void VOID undefined UNDEFINED + + eval-tester --name='any non-empty values is standard failure' --status=1 \ + -- is-empty-value -- '' ' null' + + eval-tester --name='any non-empty values is standard failure' --status=1 \ + -- is-empty-value -- '>' + + echo-style --g1="TEST: $0" + return 0 +) function is_empty_value() ( source "$DOROTHY/sources/bash.bash" @@ -10,6 +32,7 @@ function is_empty_value() ( cat <<-EOF >/dev/stderr ABOUT: Check if the input is an empty value. + Companion to [is-value], [echo-values]. Equivalent to a [is-nullish]. USAGE: is-empty-value [...options] [--] @@ -19,8 +42,8 @@ function is_empty_value() ( Verify this is an empty value RETURNS: - [0] if all s were empty values - [1] if any s were not empty values + [0] if all s are empty values + [1] if any s are not empty values EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -54,8 +77,8 @@ function is_empty_value() ( # process local value for value in "${option_inputs[@]}"; do - # check for empty values, or check for an empty string - if [[ -z $value || $value =~ ^(null|NULL|void|VOID|undefined|UNDEFINED)$ ]] || is-empty-string -- "$value"; then + # check for empty values, including empty string and all whitespace + if [[ $value =~ ^([[:space:]]*|null|NULL|void|VOID|undefined|UNDEFINED)$ ]]; then : # all good, conntinue else return 1 # not good @@ -66,5 +89,9 @@ function is_empty_value() ( # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_empty_value "$@" + if [[ $* == '--test' ]]; then + is_empty_value_test + else + is_empty_value "$@" + fi fi diff --git a/commands/is-even b/commands/is-even new file mode 100755 index 000000000..e02ed378e --- /dev/null +++ b/commands/is-even @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +function is_even_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-even -- + + eval-tester --name='even numbers are even' \ + -- is-even -- 2 4 6 8 10 + + eval-tester --name='megative even numbers are even' \ + -- is-even -- -2 -4 -6 -8 -10 + + eval-tester --name='odd numbers are not even' --status=1 \ + -- is-even -- 1 + + eval-tester --name='odd numbers are not even' --status=1 \ + -- is-even -- 1 3 + + eval-tester --name='zero is valid' \ + -- is-even -- 0 -0 + + eval-tester --name='decimals are invalid' --status=22 --ignore-stderr \ + -- is-even -- 2.0 + + eval-tester --name='even then odd is standard failure' --status=1 \ + -- is-even -- 2 3 + + eval-tester --name='even then odd then invalid is invalid failure' --status=22 --ignore-stderr \ + -- is-even -- 2 3 2.0 + + eval-tester --name='even then invalid then odd is invalid failure' --status=22 --ignore-stderr \ + -- is-even -- 2 2.0 3 + + echo-style --g1="TEST: $0" + return 0 +) +function is_even() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + # help + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Checks if the is an even integer. + Companion to [is-odd]. Equivalent to a [is-even-integer]. + + USAGE: + is-even [...options] [--] + + OPTIONS: + + Validate this + + RETURNS: + [0] if all s are even integers + [1] if any s are not even integers + [22] if any s are not integers + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided' + fi + + # verify + if ! is-integer -- "${option_inputs[@]}"; then + help 's must be integers' + fi + + # ===================================== + # Action + + local input + for input in "${option_inputs[@]}"; do + [[ $((input % 2)) -eq 0 ]] || return # explicit return with [[ required for bash v3 + done + return 0 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_even_test + else + is_even "$@" + fi +fi diff --git a/commands/is-file b/commands/is-file index 25e12209f..34c3fb22b 100755 --- a/commands/is-file +++ b/commands/is-file @@ -1,5 +1,63 @@ #!/usr/bin/env bash +function is_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-file -- + + eval-tester --name='empty args' --status=22 \ + -- is-file -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-file -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='directory' --status=1 \ + -- is-file -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-file -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=1 \ + -- is-file -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' --status=1 \ + -- is-file -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='file then dir then invalid' --status=1 \ + -- is-file -- "$DOROTHY/README.md" "$DOROTHY" '' + + eval-tester --name='dir then invalid then dir' --status=22 \ + -- is-file -- "$DOROTHY/README.md" '' "$DOROTHY" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-file' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-file' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-file' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-file' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' --status=1 \ + -- is-file -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-file -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=1 \ + -- is-file -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=1 \ + -- is-file -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_file() ( source "$DOROTHY/sources/bash.bash" @@ -9,7 +67,7 @@ function is_file() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if all s are a file (and not a symlink). + Check if all s are a file, or an unbroken symlink to a file. USAGE: is-file [...options] [--] ... @@ -22,8 +80,9 @@ function is_file() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were a file - [1] if any s were not a file + [0] if all s are a file, or an unbroken symlink to a file + [1] if any s are neither a file, nor an unbroken symlink to a file + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -61,20 +120,15 @@ function is_file() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-file.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-file.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-file.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_file "$@" + if [[ $* == '--test' ]]; then + is_file_test + else + is_file "$@" + fi fi diff --git a/commands/is-file.bash b/commands/is-file.bash index 1b14605e7..b7b649386 100755 --- a/commands/is-file.bash +++ b/commands/is-file.bash @@ -7,7 +7,12 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do - [[ -f $1 && ! -L $1 ]] || exit + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -f $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-generic b/commands/is-generic index 775065626..be6870b5b 100755 --- a/commands/is-generic +++ b/commands/is-generic @@ -4,12 +4,21 @@ function is_generic_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --name='user.local is generic' --status=0 \ - -- is-generic -- 'user.local' + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-generic -- + + eval-tester --name='generics are generic' \ + -- is-generic -- '' $' \t\n' 'user.local' 'USER.LOCAL' 'false' 'null' 'undefined' 'ubuntu' 'root' 'admin' 'super' 'user' 'localhost' eval-tester --name='custom.local is not generic' --status=1 \ -- is-generic -- 'custom.local' + eval-tester --name='mix is not generic' --status=1 \ + -- is-generic -- 'custom.local' 'false' + + eval-tester --name='custom.localhost is not generic' --status=1 \ + -- is-generic -- 'custom.localhost' + echo-style --g1="TEST: $0" return 0 ) @@ -19,6 +28,7 @@ function is_generic() ( # ===================================== # Arguments + # @note if you want a `[0] if any s were generic` then you want to implement a `! is-not-generic` instead function help { cat <<-EOF >/dev/stderr ABOUT: @@ -28,8 +38,8 @@ function is_generic() ( is-generic [--] ... RETURNS: - [0] if ANY s were generic - [1] if no s were generic + [0] if all s are generic + [1] if any s are not generic EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -62,18 +72,16 @@ function is_generic() ( # ===================================== # Action - local value="${option_inputs[*]}" - if is-empty-value -- "$value"; then - return 0 # generic - else - value="$(__lowercase_string -- "$value")" - value="${value%.local}" # trim .local - if [[ $value =~ ^(false|null|undefined|ubuntu|root|admin|super|user|localhost)$ ]]; then - return 0 - else - return 1 - fi - fi + local input + for input in "${option_inputs[@]}"; do + # lowercase + input="$(__lowercase_string -- "$input")" + # trim .local + input="${input%.local}" + # check for generic values + [[ $input =~ ^([[:space:]]*|false|null|undefined|ubuntu|root|admin|super|user|localhost)$ ]] || return # explicit return with [[ required for bash v3 + done + return 0 ) # fire if invoked standalone diff --git a/commands/is-globstar b/commands/is-globstar index ab19cf606..b5e073b9d 100755 --- a/commands/is-globstar +++ b/commands/is-globstar @@ -16,7 +16,7 @@ function is_globstar_test() ( eval-tester --name='star string' --status=1 \ -- is-globstar -- ' /Users/runner/.cache/dorothy/*/unziptar/5241/rg' - eval-tester --name='globstar string' --status=0 \ + eval-tester --name='globstar string' \ -- is-globstar -- ' /Users/runner/.cache/dorothy/**/unziptar/5241/rg' echo-style --g1="TEST: $0" @@ -37,8 +37,8 @@ function is_globstar() ( is-globstar [--] ... RETURNS: - [0] if ANY s were generic - [1] if no s were generic + [0] if ANY s are generic + [1] if no s are generic EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-group b/commands/is-group index d24d73723..8444dc05e 100755 --- a/commands/is-group +++ b/commands/is-group @@ -19,8 +19,8 @@ function is_group() ( Verify this is registered as a group on the system. RETURNS: - [0] if all s were a group on the system. - [1] if any s were not a group on the system. + [0] if all s are a group on the system. + [1] if any s are not a group on the system. [19] if the OS is not supported. EOF if [[ $# -ne 0 ]]; then diff --git a/commands/is-integer b/commands/is-integer index c08372b5b..41e8bcd8f 100755 --- a/commands/is-integer +++ b/commands/is-integer @@ -17,13 +17,13 @@ function is_integer_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --name='zero' --status=0 \ + eval-tester --name='zero' \ -- is-integer -- 0 - eval-tester --name='one' --status=0 \ + eval-tester --name='one' \ -- is-integer -- 1 - eval-tester --name='ten' --status=0 \ + eval-tester --name='ten' \ -- is-integer -- 10 eval-tester --name='decimal' --status=1 \ @@ -32,10 +32,10 @@ function is_integer_test() ( eval-tester --name='triple decimal' --status=1 \ -- is-integer -- 1.1.1 - eval-tester --name='negative' --status=0 \ + eval-tester --name='negative' \ -- is-integer -- -1 - eval-tester --name='negative 10' --status=0 \ + eval-tester --name='negative 10' \ -- is-integer -- -10 eval-tester --name='negative decimal' --status=1 \ @@ -65,7 +65,8 @@ function is_integer() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Checks if the is an integer (non-decimal number) + Checks if the is an integer (non-decimal number). + Companion to [is-number] and [is-digit]. USAGE: is-integer [...options] [--] @@ -75,8 +76,8 @@ function is_integer() ( Verify this is an integer RETURNS: - [0] if all s were integer - [1] if any s were not integers + [0] if all s are integers + [1] if any s are not integers EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-missing b/commands/is-missing index b17737f43..0e33b206d 100755 --- a/commands/is-missing +++ b/commands/is-missing @@ -1,5 +1,62 @@ #!/usr/bin/env bash +function is_missing_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-missing -- + + eval-tester --name='empty args' --status=22 \ + -- is-missing -- '' '' + + eval-tester --name='directory' --status=1 \ + -- is-missing -- "$DOROTHY" + + eval-tester --name='missing' \ + -- is-missing -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='file' --status=1 \ + -- is-missing -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=1 \ + -- is-missing -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='missing then file' --status=1 \ + -- is-missing -- "$DOROTHY/this-doesnt-exist" "$DOROTHY/README.md" + + eval-tester --name='missing then file then invalid' --status=1 \ + -- is-missing -- "$DOROTHY/this-doesnt-exist" "$DOROTHY/README.md" '' + + eval-tester --name='missing then invalid then file' --status=22 \ + -- is-missing -- "$DOROTHY/this-doesnt-exist" '' "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-missing' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-missing' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-missing' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-missing' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' --status=1 \ + -- is-missing -- "$dir_symlink" + + eval-tester --name='symlink file' --status=1 \ + -- is-missing -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=1 \ + -- is-missing -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=1 \ + -- is-missing -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_missing() ( source "$DOROTHY/sources/bash.bash" @@ -9,8 +66,8 @@ function is_missing() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if all s are missing (not a file/directory/symlink). - Opposite of [is-present]. + Check if all s are missing (not a file/directory/symlink/broken-symlink). + Companion to [is-present]. Equivalent to a [is-missing-path], [is-path-missing]. USAGE: is-missing [...options] [--] ... @@ -23,8 +80,9 @@ function is_missing() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were missing - [1] if any s were not missing + [0] if all s are missing (not even a broken symlink) + [1] if any s are not missing + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -62,20 +120,15 @@ function is_missing() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-missing.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-missing.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-missing.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_missing "$@" + if [[ $* == '--test' ]]; then + is_missing_test + else + is_missing "$@" + fi fi diff --git a/commands/is-missing.bash b/commands/is-missing.bash index 5e5a5d302..4e175427a 100755 --- a/commands/is-missing.bash +++ b/commands/is-missing.bash @@ -7,8 +7,13 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi # just -e is faulty, as -e fails on broken symlinks - [[ ! -e $1 && ! -L $1 ]] || exit + if [[ -e $1 || -L $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-needle b/commands/is-needle index 66e0e5064..86efdb76a 100755 --- a/commands/is-needle +++ b/commands/is-needle @@ -27,13 +27,13 @@ function is_needle_test() ( eval-tester --status=1 \ -- is-needle b c -- a 'b b b' - eval-tester --status=0 \ + eval-tester \ -- is-needle a 'b b b' -- a 'b b b' - eval-tester --status=0 \ + eval-tester \ -- is-needle --needle=a --needle='b b b' -- a 'b b b' - eval-tester --status=0 \ + eval-tester \ -- is-needle a a -- a 'b b b' eval-tester --status=1 \ @@ -52,6 +52,7 @@ function is_needle() ( cat <<-EOF >/dev/stderr ABOUT: Check if the exists within the s + Equivalent to a [is-either], [is-neither]. USAGE: is-needle [...options] [--] ... @@ -62,8 +63,8 @@ function is_needle() ( Note that you should always use [--needle=] as just doing will fail if the looks like a flag. RETURNS: - [0] if all s were found within the s - [1] if any was not found + [0] if all s are found within the s + [1] if any s are not found EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-non-affirmative b/commands/is-non-affirmative index b0e07c834..c40fd2b07 100755 --- a/commands/is-non-affirmative +++ b/commands/is-non-affirmative @@ -10,6 +10,7 @@ function __is_non_affirmative() ( cat <<-EOF >/dev/stderr ABOUT: Check if is a non-affirmative value. + Companion to [is-affirmative]. USAGE: is-non-affirmative [...options] [--] ... @@ -22,9 +23,9 @@ function __is_non_affirmative() ( Ignore/skip empty values. RETURNS: - [0] if all s were non-affirmative - [1] if any was affirmative - [91] if invalid values were provided, or no s were provided + [0] if all s are non-affirmative + [1] if any s are affirmative + [91] if invalid values are provided, or no s are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" || return diff --git a/commands/is-nonempty-string b/commands/is-nonempty-string deleted file mode 100755 index 2ef5a96bb..000000000 --- a/commands/is-nonempty-string +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -function is_nonempty_string() ( - source "$DOROTHY/sources/bash.bash" - - # help - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Check if the input is not just whitespace characters. - - USAGE: - is-nonempty-string [--] ... - EOF - if [[ $# -ne 0 ]]; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process our arguments - local item option_inputs=() - while [[ $# -ne 0 ]]; do - item="$1" - shift - case "$item" in - '--help' | '-h') help ;; - --) - option_inputs+=("$@") - shift $# - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_inputs+=("$item") ;; - esac - done - - # adjust - if [[ ${#option_inputs[@]} -eq 0 ]]; then - help 'No s provided.' - fi - local value="${option_inputs[*]}" - - # process - if is-empty-string -- "$value"; then - return 1 - else - return 0 - fi -) - -# fire if invoked standalone -if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_nonempty_string "$@" -fi diff --git a/commands/is-not-directory b/commands/is-not-directory index 18409460c..eeba6bd46 100755 --- a/commands/is-not-directory +++ b/commands/is-not-directory @@ -9,7 +9,10 @@ function is_not_directory() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if all s are not a directory. + Check if all s are neither a directory, nor an unbroken symlink to a directory. + Companion to [is-directory]. + If you want if any are not directory, use [! is-directory -- ...]. + The advantage of using this over [! is-directory -- ...] is that will pass for invalid paths and arguments. USAGE: is-not-directory [...options] [--] ... @@ -22,8 +25,9 @@ function is_not_directory() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were not a directory - [1] if any s was a directory or symlink + [0] if all s were neither a directory, nor an unbroken symlink to a directory + [1] if any s were a directory, or an unbroken symlink to a directory + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -61,17 +65,8 @@ function is_not_directory() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-not-directory.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-not-directory.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-not-directory.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone diff --git a/commands/is-not-directory.bash b/commands/is-not-directory.bash index 0f9b168fe..d80f63786 100755 --- a/commands/is-not-directory.bash +++ b/commands/is-not-directory.bash @@ -7,7 +7,12 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do - [[ ! -d $1 || -L $1 ]] || exit + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ -d $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-not-symlink b/commands/is-not-symlink new file mode 100755 index 000000000..d194cae52 --- /dev/null +++ b/commands/is-not-symlink @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +function is_not_symlink_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-not-symlink -- + + eval-tester --name='empty args' --status=22 \ + -- is-not-symlink -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-not-symlink -- "$DOROTHY/this-doesnt-exist" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-not-symlink' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-not-symlink' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-not-symlink' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-not-symlink' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # #2todo + eval-tester --name='symlink dir' \ + -- is-not-symlink -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-not-symlink -- "$file_symlink" + + eval-tester --name='symlink dir then dir' --status=1 \ + -- is-not-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' --status=1 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=1 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- is-not-symlink -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- is-not-symlink -- "$file_symlink" + + eval-tester --name='broken symlink dir then dir' --status=1 \ + -- is-not-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=1 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=1 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + echo-style --g1="TEST: $0" + return 0 +) +function is_not_symlink() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are not a symlink, broken or otherwise. + + USAGE: + is-not-symlink [...options] [--] ... + + OPTIONS: + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + RETURNS: + [0] if all s were not a symlink + [1] if any s were a symlink + [22] if empty arguments are provided + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided.' + fi + + # ===================================== + # Action + + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-not-symlink.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_not_symlink_test + else + is_not_symlink "$@" + fi +fi diff --git a/commands/is-not-symlink.bash b/commands/is-not-symlink.bash new file mode 100755 index 000000000..8ed999483 --- /dev/null +++ b/commands/is-not-symlink.bash @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ -L $1 ]]; then + exit 1 + fi + shift +done +exit 0 diff --git a/commands/is-not-whitespace b/commands/is-not-whitespace new file mode 100755 index 000000000..5c0909c67 --- /dev/null +++ b/commands/is-not-whitespace @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +function is_not_whitespace_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-not-whitespace -- + + eval-tester --name='zero-length is standard failure' --status=1 \ + -- is-not-whitespace -- '' '' + + eval-tester --name='space is standard failure' --status=1 \ + -- is-not-whitespace -- ' ' ' ' + + eval-tester --name='newline is standard failure' --status=1 \ + -- is-not-whitespace -- $'\n' $'\n' + + eval-tester --name='newline and tab is standard failure' --status=1 \ + -- is-not-whitespace -- $'\n' $'\t' + + eval-tester --name='whitespace combo is standard failure' --status=1 \ + -- is-not-whitespace -- $'\n\t ' + + eval-tester --name='letters and numbers' \ + -- is-not-whitespace -- 'a' 0 + + eval-tester --name='mix is is stsandard failure pt. 1' --status=1 \ + -- is-not-whitespace -- '' 'b' + + eval-tester --name='mix is is stsandard failure pt. 2' --status=1 \ + -- is-not-whitespace -- 'a' '' + + echo-style --g1="TEST: $0" + return 0 +) +function is_not_whitespace() ( + source "$DOROTHY/sources/bash.bash" + + # help + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if the input is not just whitespace characters. + Companion to [is-whitespace], [echo-trim-empty-lines]. Equivalent to a [is-string], [is-blackspace], [is-notempty-string], [is-not-empty-string], [is-not-only-whitespace], [is-non-whitespace]. + + USAGE: + is-not-whitespace [...options] [--] + + OPTIONS: + | --string= + Verify this is a non-empty string + + RETURNS: + [0] if all s are non-whitespace strings + [1] if any s are not non-whitespace strings + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process our arguments + local item option_inputs=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + --) + option_inputs+=("$@") + shift $# + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # adjust + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided.' + fi + + # ===================================== + # Action + + # fails mixed test case: + # [[ ! ${option_inputs[*]} =~ ^[[:space:]]*$ ]] + # return + + local input + for input in "${option_inputs[@]}"; do + if [[ $input =~ ^[[:space:]]*$ ]]; then + return 1 + fi + done + return 0 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_not_whitespace_test + else + is_not_whitespace "$@" + fi +fi diff --git a/commands/is-number b/commands/is-number index 4492e7340..c3a669da9 100755 --- a/commands/is-number +++ b/commands/is-number @@ -4,28 +4,28 @@ function is_number_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - eval-tester --name='zero IS a number' --status=0 \ + eval-tester --name='zero IS a number' \ -- is-number -- 0 - eval-tester --name='one IS a number' --status=0 \ + eval-tester --name='one IS a number' \ -- is-number -- 1 - eval-tester --name='ten IS a number' --status=0 \ + eval-tester --name='ten IS a number' \ -- is-number -- 10 - eval-tester --name='decimal IS a number' --status=0 \ + eval-tester --name='decimal IS a number' \ -- is-number -- 0.1 eval-tester --name='triple decimal IS NOT a number' --status=1 \ -- is-number -- 1.1.1 - eval-tester --name='negative IS a number' --status=0 \ + eval-tester --name='negative IS a number' \ -- is-number -- -1 - eval-tester --name='negative 10 IS a number' --status=0 \ + eval-tester --name='negative 10 IS a number' \ -- is-number -- -10 - eval-tester --name='negative decimal IS a number' --status=0 \ + eval-tester --name='negative decimal IS a number' \ -- is-number -- -1.1 eval-tester --name='negative triple decimal IS NOT a number' --status=1 \ @@ -57,6 +57,7 @@ function is_number() ( cat <<-EOF >/dev/stderr ABOUT: Checks if the is a valid number. + Companion to [is-integer] and [is-digit]. USAGE: is-number [...options] [--] @@ -66,8 +67,8 @@ function is_number() ( Verify this is a valid number RETURNS: - [0] if all s were valid numbers - [1] if any s were not valid numbers + [0] if all s are valid numbers + [1] if any s are not valid numbers EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-odd b/commands/is-odd index a93712dff..b428675b8 100755 --- a/commands/is-odd +++ b/commands/is-odd @@ -1,5 +1,42 @@ #!/usr/bin/env bash +function is_odd_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-odd -- + + eval-tester --name='odd numbers are odd' \ + -- is-odd -- 1 3 5 7 9 11 + + eval-tester --name='megative odd numbers are odd' \ + -- is-odd -- -1 -3 -5 -7 -9 -11 + + eval-tester --name='even numbers are not odd' --status=1 \ + -- is-odd -- 2 + + eval-tester --name='even numbers are not odd' --status=1 \ + -- is-odd -- 2 4 + + eval-tester --name='zero is not odd' --status=1 \ + -- is-odd -- 0 -0 + + eval-tester --name='decimals are invalid' --status=22 --ignore-stderr \ + -- is-odd -- 1.1 + + eval-tester --name='odd then even is standard failure' --status=1 \ + -- is-odd -- 1 2 + + eval-tester --name='odd then even then invalid is invalid failure' --status=22 --ignore-stderr \ + -- is-odd -- 1 2 1.1 + + eval-tester --name='odd then invalid then odd is invalid failure' --status=22 --ignore-stderr \ + -- is-odd -- 1 1.1 2 + + echo-style --g1="TEST: $0" + return 0 +) function is_odd() ( source "$DOROTHY/sources/bash.bash" @@ -10,19 +47,20 @@ function is_odd() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Checks if the is an odd number. + Checks if the is an odd integer. + Companion to [is-even]. Equivalent to a [is-odd-integer]. USAGE: is-odd [...options] [--] OPTIONS: - Verify this is an odd number + Validate this RETURNS: - [0] if all s were odd numbers - [1] if any s were not odd numbers - [2] if any s were not numbers + [0] if all s are odd integers + [1] if any s are not odd integers + [22] if any s are not integers EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -49,12 +87,12 @@ function is_odd() ( # verify if [[ ${#option_inputs[@]} -eq 0 ]]; then - help "No s provided" + help 'No s provided' fi # verify - if ! is-number -- "${option_inputs[@]}"; then - return 2 + if ! is-integer -- "${option_inputs[@]}"; then + help 's must be integers' fi # ===================================== @@ -62,12 +100,16 @@ function is_odd() ( local input for input in "${option_inputs[@]}"; do - [[ "$((input % 2))" -ne 0 ]] || return # explicit return with [[ required for bash v3 + [[ $((input % 2)) -ne 0 ]] || return # explicit return with [[ required for bash v3 done return 0 ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_odd "$@" + if [[ $* == '--test' ]]; then + is_odd_test + else + is_odd "$@" + fi fi diff --git a/commands/is-present b/commands/is-present index 25692edbc..48f809259 100755 --- a/commands/is-present +++ b/commands/is-present @@ -1,5 +1,62 @@ #!/usr/bin/env bash +function is_present_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-present -- + + eval-tester --name='empty args' --status=22 \ + -- is-present -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-present -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='directory' \ + -- is-present -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-present -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' \ + -- is-present -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' \ + -- is-present -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='dir then file then invalid' --status=22 \ + -- is-present -- "$DOROTHY" "$DOROTHY/README.md" '' + + eval-tester --name='dir then invalid then file' --status=22 \ + -- is-present -- "$DOROTHY" '' "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-present' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-present' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-present' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-present' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' \ + -- is-present -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-present -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=1 \ + -- is-present -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=1 \ + -- is-present -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_present() ( source "$DOROTHY/sources/bash.bash" @@ -9,11 +66,11 @@ function is_present() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if all s are present (file/directory/symlink). - Opposite of [is-missing]. + Check if all s are present (file/directory/symlink/broken-symlink). + Companion to [is-missing]. USAGE: - is-presen [...options] [--] ... + is-present [...options] [--] ... OPTIONS: --sudo @@ -23,8 +80,9 @@ function is_present() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were present + [0] if all s were present (even if it is a broken symlink). [1] if any s were not present + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -62,20 +120,15 @@ function is_present() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-present.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-present.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-present.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_present "$@" + if [[ $* == '--test' ]]; then + is_present_test + else + is_present "$@" + fi fi diff --git a/commands/is-present.bash b/commands/is-present.bash index 062a50895..bab63034f 100755 --- a/commands/is-present.bash +++ b/commands/is-present.bash @@ -7,8 +7,13 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi # just -e is faulty, as -e fails on broken symlinks - [[ -e $1 || -L $1 ]] || exit + if ! [[ -e $1 || -L $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-readable b/commands/is-readable index 9657826df..14e1c8a26 100755 --- a/commands/is-readable +++ b/commands/is-readable @@ -1,5 +1,62 @@ #!/usr/bin/env bash +function is_readable_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-readable -- + + eval-tester --name='empty args' --status=22 \ + -- is-readable -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-readable -- "$DOROTHY/this-doesnt-exist" + + eval-tester --name='directory' \ + -- is-readable -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-readable -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' \ + -- is-readable -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' \ + -- is-readable -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='dir then file then missing then invalid' --status=1 \ + -- is-readable -- "$DOROTHY" "$DOROTHY/README.md" "$DOROTHY/this-doesnt-exist" '' + + eval-tester --name='dir then invalid then missing then file' --status=22 \ + -- is-readable -- "$DOROTHY" '' "$DOROTHY/this-doesnt-exist" "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-readable' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-readable' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-readable' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-readable' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' \ + -- is-readable -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-readable -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=1 \ + -- is-readable -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=1 \ + -- is-readable -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_readable() ( source "$DOROTHY/sources/bash.bash" @@ -22,8 +79,9 @@ function is_readable() ( If specified use this user and/or group for filesystem interactions. RETURNS: - [0] if all s were readable - [1] if any s were not readable + [0] if all s are readable + [1] if any s are not readable + [22] if empty arguments are provided EOF if [[ $# -ne 0 ]]; then echo-error "$@" @@ -61,20 +119,15 @@ function is_readable() ( # ===================================== # Action - local input - if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then - for input in "${option_inputs[@]}"; do - sudo-helper --inherit --user="$option_user" --group="$option_group" -- is-readable.bash -- "$input" || return - done - else - for input in "${option_inputs[@]}"; do - is-readable.bash -- "$input" || return - done - fi - return 0 + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-readable.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone if [[ $0 == "${BASH_SOURCE[0]}" ]]; then - is_readable "$@" + if [[ $* == '--test' ]]; then + is_readable_test + else + is_readable "$@" + fi fi diff --git a/commands/is-readable.bash b/commands/is-readable.bash index c9ba9deab..23208a3f6 100755 --- a/commands/is-readable.bash +++ b/commands/is-readable.bash @@ -7,7 +7,12 @@ if [[ $# -eq 0 ]]; then exit 22 # EINVAL 22 Invalid argument fi while [[ $# -ne 0 ]]; do - [[ -r $1 ]] || exit + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -r $1 ]]; then + exit 1 + fi shift done exit 0 diff --git a/commands/is-same b/commands/is-same index 79e8faf4d..c8416ac0d 100755 --- a/commands/is-same +++ b/commands/is-same @@ -89,8 +89,8 @@ function is_same() ( # same structure local first_structure second_structure - first_structure="$(fs-structure -- "$first_path")" - second_structure="$(fs-structure -- "$second_path")" + first_structure="$(fs-structure --no-perms --no-time -- "$first_path")" + second_structure="$(fs-structure --no-perms --no-time -- "$second_path")" if [[ $first_structure != "$second_structure" ]]; then echo-style \ --header1="$first_path" $'\n' \ diff --git a/commands/is-symlink b/commands/is-symlink new file mode 100755 index 000000000..ba9e64ace --- /dev/null +++ b/commands/is-symlink @@ -0,0 +1,133 @@ +#!/usr/bin/env bash + +function is_symlink_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-symlink -- + + eval-tester --name='empty args' --status=22 \ + -- is-symlink -- '' '' + + eval-tester --name='missing' --status=1 \ + -- is-symlink -- "$DOROTHY/this-doesnt-exist" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='is-symlink' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='is-symlink' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='is-symlink' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='is-symlink' --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + eval-tester --name='symlink dir' \ + -- is-symlink -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-symlink -- "$file_symlink" + + eval-tester --name='symlink dir then dir' --status=1 \ + -- is-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' --status=1 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=1 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- is-symlink -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- is-symlink -- "$file_symlink" + + eval-tester --name='broken symlink dir then dir' --status=1 \ + -- is-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=1 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=1 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/this-doesnt-exist" + + echo-style --g1="TEST: $0" + return 0 +) +function is_symlink() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are a symlink, broken or otherwise. + + USAGE: + is-symlink [...options] [--] ... + + OPTIONS: + --sudo + If specified, use sudo on filesystem interactions. + --user= + --group= + If specified use this user and/or group for filesystem interactions. + + RETURNS: + [0] if all s were a symlink + [1] if any s were not a symlink + [22] if empty arguments are provided + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='no' option_user='' option_group='' + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided.' + fi + + # ===================================== + # Action + + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- is-symlink.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_symlink_test + else + is_symlink "$@" + fi +fi diff --git a/commands/is-symlink.bash b/commands/is-symlink.bash new file mode 100755 index 000000000..5468e6d31 --- /dev/null +++ b/commands/is-symlink.bash @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [[ $1 == '--' ]]; then + shift +fi +if [[ $# -eq 0 ]]; then + exit 22 # EINVAL 22 Invalid argument +fi +while [[ $# -ne 0 ]]; do + if [[ -z $1 ]]; then + exit 22 # EINVAL 22 Invalid argument + fi + if [[ ! -L $1 ]]; then + exit 1 + fi + shift +done +exit 0 diff --git a/commands/is-user b/commands/is-user index d3a36668e..35ccf2e80 100755 --- a/commands/is-user +++ b/commands/is-user @@ -20,8 +20,8 @@ function is_user() ( Verify this is registered as a user on the system. RETURNS: - [0] if all s were a user on the system. - [1] if any s were not a user on the system. + [0] if all s are a user on the system. + [1] if any s are not a user on the system. EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-user-in-group b/commands/is-user-in-group index 88c2d2cab..503b72398 100755 --- a/commands/is-user-in-group +++ b/commands/is-user-in-group @@ -22,8 +22,8 @@ function is_user_in_group() ( A user to check is within the s. RETURNS: - [0] if all s were within . - [1] if any s were not within . + [0] if all s are within . + [1] if any s are not within . EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-value b/commands/is-value index a92506d16..ac7ffd106 100755 --- a/commands/is-value +++ b/commands/is-value @@ -10,6 +10,7 @@ function is_value() ( cat <<-EOF >/dev/stderr ABOUT: Check if the is a non-empty value. + Companion to [is-empty-value]. USAGE: is-value [...options] [--] @@ -19,8 +20,8 @@ function is_value() ( Verify this is an empty value RETURNS: - [0] if all s were not empty values. - [1] if any s were an empty value. + [0] if all s are not empty values. + [1] if any s are an empty value. EOF if [[ $# -ne 0 ]]; then echo-error "$@" diff --git a/commands/is-whitespace b/commands/is-whitespace new file mode 100755 index 000000000..b0f5a4cda --- /dev/null +++ b/commands/is-whitespace @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +function is_whitespace_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-whitespace -- + + eval-tester --name='zero-length' \ + -- is-whitespace -- '' '' + + eval-tester --name='space' \ + -- is-whitespace -- ' ' ' ' + + eval-tester --name='newline' \ + -- is-whitespace -- $'\n' $'\n' + + eval-tester --name='newline and tab' \ + -- is-whitespace -- $'\n' $'\t' + + eval-tester --name='whitespace combo' \ + -- is-whitespace -- $'\n\t ' + + eval-tester --name='letters is standard failure' --status=1 \ + -- is-whitespace -- 'a' 'b' + + eval-tester --name='mix is standard failure pt. 1' --status=1 \ + -- is-whitespace -- '' 'b' + + eval-tester --name='mix is standard failure pt. 2' --status=1 \ + -- is-whitespace -- 'a' '' + + echo-style --g1="TEST: $0" + return 0 +) +function is_whitespace() ( + source "$DOROTHY/sources/bash.bash" + + # help + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if an input is only whitespace characters. + Companion to [is-not-whitespace], [echo-trim-empty-lines]. Equivalent to a [is-empty-string], [is-only-whitespace]. + + USAGE: + is-whitespace [...options] [--] + + OPTIONS: + | --string= + Verify this is an empty string + + RETURNS: + [0] if all s are empty strings + [1] if any s are not empty strings + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--string='*) option_inputs+=("${item#*=}") ;; + '--') + option_inputs+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_inputs+=("$item") ;; + esac + done + + # verify + if [[ ${#option_inputs[@]} -eq 0 ]]; then + help 'No s provided.' + fi + + # ===================================== + # Action + + # local input + # for input in "${option_inputs[@]}"; do + # if [[ ! $input =~ ^[[:space:]]*$ ]]; then + # return 1 + # fi + # done + # return 0 + + [[ ${option_inputs[*]} =~ ^[[:space:]]*$ ]] + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_whitespace_test + else + is_whitespace "$@" + fi +fi diff --git a/commands/secret b/commands/secret index 50a668227..df097838a 100755 --- a/commands/secret +++ b/commands/secret @@ -497,21 +497,21 @@ function secret_() ( # section fields local fields_via_section mapfile -t fields_via_section < <(__print_lines "$data" > >(jq -r '.details.sections[]? | select(.fields).fields[]? | select(.t != "" and .v != "") | (.v, .t)')) - if is-array-count-ge --size=2 -- "${fields_via_section[@]}"; then + if [[ ${#fields_via_section[@]} -ge 2 ]]; then __print_lines "${fields_via_section[@]}" fi # name fields local fields_via_name mapfile -t fields_via_name < <(__print_lines "$data" > >(jq -r '.details.fields[]? | select(.name != "" and .value != "") | (.value, .name)')) - if is-array-count-ge --size=2 -- "${fields_via_name[@]}"; then + if [[ ${#fields_via_name[@]} -ge 2 ]]; then __print_lines "${fields_via_name[@]}" fi # designation fields local fields_via_designation mapfile -t fields_via_designation < <(__print_lines "$data" > >(jq -r '.details.fields[]? | select(.designation != "" and .value != "") | (.value, .designation)')) - if is-array-count-ge --size=2 -- "${fields_via_designation[@]}"; then + if [[ ${#fields_via_designation[@]} -ge 2 ]]; then __print_lines "${fields_via_designation[@]}" fi fi @@ -526,7 +526,7 @@ function secret_() ( # fetch available vaults mapfile -t tuples < <(op_grab) - if is-array-empty -- "${tuples[@]}"; then + if [[ ${#tuples[@]} -eq 0 ]]; then echo-error "Failed to fetch a vault:" $'\n' \ "$(echo-verbose -- "${tuples[@]}")" return 1 @@ -544,7 +544,7 @@ function secret_() ( # fetch available items mapfile -t tuples < <(op_grab "$vault") - if is-array-empty -- "${tuples[@]}"; then + if [[ ${#tuples[@]} -eq 0 ]]; then echo-error "Failed to fetch any items:" $'\n' \ "$(echo-verbose -- "${tuples[@]}")" return 1 @@ -568,7 +568,7 @@ function secret_() ( # fetch available items mapfile -t tuples < <(op_grab "$vault" "$item") - if is-array-empty -- "${tuples[@]}"; then + if [[ ${#tuples[@]} -eq 0 ]]; then echo-error "Failed to fetch any items:" $'\n' \ "$(echo-verbose -- "${tuples[@]}")" return 1 @@ -753,15 +753,16 @@ function secret_() ( esac done - if [[ $found == 'yes' ]] && is-array-full -- "${before[@]}" && is-array-full -- "${after[@]}"; then + # esure we have valid arguments, before -- and after -- + if [[ $found == 'yes' ]] && is-not-whitespace -- "${before[@]}" && is-not-whitespace -- "${after[@]}"; then keys=("${before[@]}") args=("${after[@]}") else __print_lines "found = $found" - __print_lines 'after =' - echo-verbose -- "${after[@]}" __print_lines 'before =' echo-verbose -- "${before[@]}" + __print_lines 'after =' + echo-verbose -- "${after[@]}" help "env action requires [--] separator" fi diff --git a/commands/setup-dns b/commands/setup-dns index 748944a43..f3e2311c5 100755 --- a/commands/setup-dns +++ b/commands/setup-dns @@ -519,8 +519,8 @@ function setup_dns() ( sudo-helper -- resolvectl reset-server-features --no-pager || : # apply the changes to their interfaces - mapfile -t interfaces < <(network-interface list) - if is-array-empty -- "${interfaces[@]}"; then + mapfile -t interfaces < <(network-interface list | echo-values --stdin) + if test "${#interfaces[@]}" -eq 0; then echo-style --error1='No interfaces were found.' >/dev/stderr return 1 fi diff --git a/commands/setup-util-qpdf b/commands/setup-util-qpdf new file mode 100755 index 000000000..edb42d572 --- /dev/null +++ b/commands/setup-util-qpdf @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Tools for and transforming and inspecting PDF files +# https://github.com/qpdf/qpdf + +function setup_util_qpdf() ( + source "$DOROTHY/sources/bash.bash" + + # setup + local options=( + --cli='qpdf' + "$@" + BREW='qpdf' + ) + setup-util "${options[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + setup_util_qpdf "$@" +fi diff --git a/commands/setup-util-trash b/commands/setup-util-trash index 49db9a392..fec483a98 100755 --- a/commands/setup-util-trash +++ b/commands/setup-util-trash @@ -1,8 +1,11 @@ #!/usr/bin/env bash +# brew/macos version # https://github.com/ali-rantakari/trash # only provides [trash] binary +# debian/ubuntu version +# https://github.com/andreafrancia/trash-cli # https://packages.debian.org/sid/amd64/trash-cli/filelist # /usr/bin/trash # /usr/bin/trash-empty diff --git a/commands/sparse-vault b/commands/sparse-vault index 872991bff..9c6c7bf92 100755 --- a/commands/sparse-vault +++ b/commands/sparse-vault @@ -115,15 +115,15 @@ function sparse_vault() ( function act_mount { eval-helper --quiet \ - --pending="$(echo-style --bold="Compacting...")" \ - --success="$(echo-style --success="Compacted.")" \ - --failure="$(echo-style --error="Failed to compact.")" \ + --pending="$(echo-style --bold='Compacting...')" \ + --success="$(echo-style --success='Compacted.')" \ + --failure="$(echo-style --error='Failed to compact.')" \ -- hdiutil compact "$option_path" eval-helper --quiet \ - --pending="$(echo-style --bold="Mounting...")" \ - --success="$(echo-style --success="Mounted.")" \ - --failure="$(echo-style --error="Failed to mount.")" \ + --pending="$(echo-style --bold='Mounting...')" \ + --success="$(echo-style --success='Mounted.')" \ + --failure="$(echo-style --error='Failed to mount.')" \ -- hdiutil mount "$option_path" } @@ -151,23 +151,23 @@ function sparse_vault() ( fi eval-helper --quiet \ - --pending="$(echo-style --bold="Creating...")" \ - --success="$(echo-style --success="Created.")" \ - --failure="$(echo-style --error="Failed to create.")" \ + --pending="$(echo-style --bold='Creating...')" \ + --success="$(echo-style --success='Created.')" \ + --failure="$(echo-style --error='Failed to create.')" \ -- hdiutil create "${args[@]}" "$option_path" eval-helper --quiet \ --pending="$(echo-style --bold="Mounting...")" \ - --success="$(echo-style --success="Mounted.")" \ - --failure="$(echo-style --error="Failed to mount.")" \ + --success="$(echo-style --success='Mounted.')" \ + --failure="$(echo-style --error='Failed to mount.')" \ -- hdiutil mount "$option_path" } function act_unmount { eval-helper --quiet \ --pending="$(echo-style --bold="Unmounting...")" \ - --success="$(echo-style --success="Unmount.")" \ - --failure="$(echo-style --error="Failed to unmount.")" \ + --success="$(echo-style --success='Unmount.')" \ + --failure="$(echo-style --error='Failed to unmount.')" \ -- hdiutil unmount "$option_path" } @@ -177,7 +177,7 @@ function sparse_vault() ( if [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" else - echo-style --stderr --error1="Action not yet implemented: " --code-error1="$action" + echo-style --stderr --error1='Action not yet implemented: ' --code-error1="$action" return 78 # ENOSYS 78 Function not implemented fi ) diff --git a/commands/sudo-helper b/commands/sudo-helper index 167e4268f..22a8d085b 100755 --- a/commands/sudo-helper +++ b/commands/sudo-helper @@ -27,6 +27,8 @@ function sudo_helper() ( specify a user to run the command as --group= specify a group to run the command as + --no-sudo | --sudo=no + If specified, and no nor is provided, then sudo will not be used, and the command will be run normally. --inherit if enabled, inherit environment variables include PATH. @@ -39,7 +41,7 @@ function sudo_helper() ( if enabled, [eval-helper] will be used to confirm the execution of the command --local - update sudo configuration to enable access to /usr/local variables + update sudo configuration to enable access to /usr/local paths EOF if [[ $# -ne 0 ]]; then @@ -50,7 +52,7 @@ function sudo_helper() ( # process # option_quiet='no' is an important default to ensure our call to eval-helper can still be interpolated - local item option_cmd=() option_quiet='no' option_reason='' option_user='' option_group='' option_confirm='no' option_wrap='no' option_inherit='no' option_local='' + local item option_cmd=() option_quiet='no' option_reason='' option_user='' option_group='' option_sudo='yes' option_confirm='no' option_wrap='no' option_inherit='no' option_local='' while [[ $# -ne 0 ]]; do item="$1" shift @@ -65,6 +67,9 @@ function sudo_helper() ( '--reason='*) option_reason="${item#*=}" ;; '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" + ;; '--no-inherit'* | '--inherit'*) option_inherit="$(get-flag-value --affirmative --fallback="$option_inherit" -- "$item")" ;; @@ -89,9 +94,6 @@ function sudo_helper() ( # local if [[ -n $option_local ]]; then - # dependencies - setup-util-sd --quiet - # trim problematic paths local system_paths sudoers_line cron_system_line cron_user_line ignore_args=( # these are not needed for [dorothy run] to bootstrap itself @@ -144,8 +146,11 @@ function sudo_helper() ( # -i, --login # Run the shell specified by the target user's password database entry as a login shell. - local run=() home='' - if __command_exists -- sudo; then + local run=() home='' command_style='sudo' + if [[ $option_sudo == 'no' ]]; then + command_style='' + run+=("${option_cmd[@]}") + elif __command_exists -- sudo; then run+=('sudo') if [[ -n $option_user ]]; then @@ -220,6 +225,7 @@ function sudo_helper() ( run+=("${option_cmd[@]}") else # sudo/doas does not exist, probably not needed + command_style='' run+=("${option_cmd[@]}") fi @@ -228,7 +234,7 @@ function sudo_helper() ( # the main thing here though, is that any failure should be detected and cancel in the caller # perhaps requiring --ppid=$$ to be passed to sudo-helper is the way to go, as we do for [confirm] if [[ $option_wrap == 'yes' || $option_confirm == 'yes' || $option_quiet == 'yes' ]]; then - eval-helper --command="$(echo-style --sudo="$(echo-escape-command -- "${option_cmd[@]}")")" --wrap="$option_wrap" --quiet="$option_quiet" --confirm="$option_confirm" -- "${run[@]}" + eval-helper --command="$(echo-style --"$command_style"="$(echo-escape-command -- "${option_cmd[@]}")")" --wrap="$option_wrap" --quiet="$option_quiet" --confirm="$option_confirm" -- "${run[@]}" else "${run[@]}" # eval fi