From 686a1ba88805ac5ffc64a93e6e7262b8ed424cf2 Mon Sep 17 00:00:00 2001 From: Benjamin Lupton Date: Mon, 11 Apr 2022 04:51:11 +0800 Subject: [PATCH] 2022 Q1 (#121) Discussion: - https://github.com/bevry/dorothy/discussions/118 Closes: - https://github.com/bevry/dorothy/issues/120 - https://github.com/bevry/dorothy/issues/7 Most of the work done in: - https://github.com/bevry/dorothy/commit/de2170e6a3471b1c3f55cea1f7ebc032f2755cb3 --- .flake8 | 3 + .github/workflows/dorothy.yml | 23 + .gitignore | 1 - .isort.cfg | 2 + .markdownlint.yaml | 10 + .trunk/.gitignore | 1 + .trunk/trunk.yaml | 14 + .vscode/extensions.json | 6 +- .vscode/settings.json | 9 +- README.md | 2 +- commands/alias-details | 42 - commands/alias-helper | 217 ++ commands/alias-make | 45 - commands/alias-target | 20 - commands/alias-to-symlink | 37 - commands/alias-verify | 19 - commands/android-dev | 10 - commands/array-evens | 14 - commands/array-odds | 14 - commands/array-tuple-select | 19 - commands/ask | 376 +- commands/brew | 23 +- commands/brew-installed | 224 +- commands/btrfs-helper | 397 +-- commands/cert | 198 -- commands/checksum | 235 +- commands/choose | 19 - commands/choose-menu | 376 +- commands/choose-option | 782 ++--- commands/choose-path | 51 +- commands/codec | 10 - commands/command-exists | 62 +- commands/command-missing | 52 +- commands/command-working | 67 + commands/concat-files | 18 - commands/config-helper | 600 ++-- commands/confirm | 453 +-- commands/contains-line | 67 +- commands/contains-string | 7 - commands/contains-word | 12 - commands/cpr | 866 ++--- commands/date-adguard | 45 +- commands/date-iso | 42 +- commands/date-next-year | 60 +- commands/date-unix | 42 +- commands/ddns | 47 - commands/debug-bash | 50 +- commands/debug-docker | 5 +- commands/debug-network | 4 +- commands/deno-cache-clean | 7 - commands/does-url-contain | 7 - commands/dorothy | 759 ++-- commands/down | 340 +- commands/down-zip | 61 - commands/downtime | 4 - commands/echo-affirmative | 48 +- commands/echo-after-separator | 38 +- commands/echo-before-blank | 2 +- commands/echo-before-separator | 42 +- commands/echo-checksum | 102 + commands/echo-clear-line | 125 +- commands/echo-clear-lines | 12 +- commands/echo-color | 73 + commands/echo-count-lines | 16 +- commands/echo-decode-html | 43 + commands/echo-decode-url | 52 + commands/echo-element | 8 +- commands/echo-error | 9 + commands/echo-escape-spaces | 33 + commands/echo-escape-special | 36 + commands/echo-eval | 20 - commands/echo-exit-affirmative | 84 +- commands/echo-exit-code | 72 +- commands/echo-file | 113 +- commands/echo-filenames | 23 +- commands/echo-if-directory | 30 +- commands/echo-if-executable | 31 +- commands/echo-if-file | 29 +- commands/echo-if-nonempty | 29 +- commands/echo-if-path | 27 +- commands/echo-join | 45 +- commands/echo-lines | 129 +- commands/echo-lowercase | 34 +- commands/echo-magnet-hash | 26 +- commands/echo-mkdir | 34 +- commands/echo-no-colors | 24 +- commands/echo-non-affirmative | 48 +- commands/echo-numeric | 46 +- commands/echo-on-empty-stdin | 56 +- commands/echo-or-fail | 29 +- commands/echo-paths-and-basenames | 20 +- commands/echo-quiet | 77 + commands/echo-quote | 40 +- commands/echo-revolving-door | 18 +- commands/echo-sort | 55 + commands/echo-style | 362 +- commands/echo-subpaths | 11 +- commands/echo-trim | 24 +- .../{echo-trim-lines => echo-trim-each-line} | 25 +- commands/echo-values | 31 +- commands/echo-verbose | 38 +- commands/edit-blackblaze | 7 - commands/edit-dns | 60 +- commands/edit-hosts | 41 +- commands/eject-all | 60 +- commands/ensure-trailing-newline | 69 +- commands/ensure-trailing-slash | 93 +- commands/escape-spaces | 4 - commands/escape-special | 7 - commands/eval-collapse | 153 - commands/eval-confirm | 15 - commands/eval-helper | 218 ++ commands/eval-tester | 198 +- commands/expand-path-bash | 2 +- commands/expand-path-zsh | 2 +- commands/fail | 47 +- commands/fail-on-empty-stdin | 18 - commands/fetch | 146 +- commands/ffmpeg-separate | 10 - commands/find-directories | 58 +- commands/find-files | 105 +- commands/find-symlinks | 61 + commands/firefox-reset | 3 - commands/flush-dns | 111 +- commands/font-search | 53 +- commands/forever | 5 - commands/fs-absolute | 137 +- commands/fs-bytes | 50 +- commands/fs-extension | 54 +- commands/fs-filename | 143 +- commands/fs-join | 6 +- commands/fs-kilobytes | 55 +- commands/fs-megabytes | 55 +- commands/fs-mount | 293 +- commands/fs-own | 427 +-- commands/fs-parent | 107 +- commands/fs-realpath | 105 +- commands/fs-rm | 168 +- commands/fs-size | 138 +- commands/fs-speed | 148 +- commands/fs-structure | 90 +- commands/fs-temp | 222 +- commands/fs-unmount | 182 +- commands/fs-volume-join | 2 +- commands/geocode | 80 +- commands/get-app | 111 +- commands/get-array-count | 2 +- commands/get-codec | 62 + commands/get-drive-info | 160 +- commands/get-drives | 58 +- commands/get-flag-value | 103 +- commands/get-hostname | 42 +- commands/get-installer | 399 ++- commands/get-os-theme | 69 + commands/get-profile | 452 +-- commands/get-random-number | 49 + commands/get-size | 250 +- commands/get-url-hostname | 2 +- commands/get-url-upgrade | 4 +- commands/get-volumes | 92 +- commands/gid | 73 +- commands/git-checkout-fresh | 2 +- commands/git-current-branch | 2 +- commands/git-protocol-ensure | 2 +- commands/git-review | 8 +- commands/git-update | 4 +- commands/github-download | 315 ++ commands/github-file | 12 - commands/github-file-download | 16 - commands/github-latest-release | 18 - commands/github-release-file | 57 - commands/github-release-file-download | 31 - commands/github-repo-download | 12 - commands/gocryptfs-helper | 590 ++-- commands/gpg-helper | 607 ++-- commands/gravatar | 69 +- commands/grub-helper | 64 + commands/gtd | 5 - commands/http-ok | 6 - commands/http-status | 24 - commands/icloud-helper | 196 +- commands/ios-dev | 15 - commands/ip-local | 5 - commands/ip-remote | 5 - commands/is-help | 5 - commands/is-help-empty | 5 - commands/is-help-separator | 5 - commands/is-inside | 7 +- commands/is-internet-working | 69 +- commands/is-quiet | 6 - commands/is-same | 200 +- commands/itunes-owners | 165 +- commands/ln-helper | 122 - commands/log-performance | 2 + commands/loop | 6 - commands/mac-addr-new | 4 - commands/mac-addr-refresh | 14 - commands/mac-address-helper | 95 + commands/macos-drive | 115 +- commands/macos-settings | 395 ++- commands/macos-state | 986 +++--- commands/macos-theme | 207 +- commands/mail-sync | 229 +- commands/network-interface | 123 +- commands/nvm-2596 | 2 + commands/ok | 1 + commands/open-app | 86 +- commands/openssl-helper | 412 +++ commands/padd | 26 - commands/pdf-decrypt | 86 +- commands/pipp | 37 - commands/podcast | 91 +- commands/podvideo | 62 +- commands/rand | 22 - commands/read-arrow | 114 - commands/read-key | 148 + commands/redis | 4 - commands/redis-flush | 4 - commands/redis-start | 4 - commands/redis-stop | 4 - commands/repo | 46 - commands/researchgate-rename | 2 +- commands/rm-deno-cache | 49 + commands/rm-junk | 54 +- commands/rm-modules | 54 +- commands/rm-svn | 53 +- commands/rm-sync | 53 +- commands/rm-tmp | 4 - commands/rm-vmware | 76 +- commands/secret | 1220 +++---- commands/seedbox | 786 ++--- commands/select-hosts | 289 +- commands/select-shell | 291 +- commands/set-hostname | 93 +- commands/setup-bin | 210 +- commands/setup-dns | 3055 +++++++++-------- commands/setup-environment-commands | 2 + commands/setup-extras | 84 +- commands/setup-git | 725 ++-- commands/setup-go | 229 +- commands/setup-linux | 137 +- commands/setup-linux-fonts | 117 +- commands/setup-linux-recovery | 4 +- commands/setup-linux-safegraphics | 4 +- commands/setup-mac | 160 +- commands/setup-mac-apps | 323 +- commands/setup-mac-brew | 1206 +++---- commands/setup-mac-clean | 140 +- commands/setup-node | 15 +- commands/setup-python | 19 +- commands/setup-rust | 4 +- commands/setup-util | 2252 ++++++------ commands/setup-util-1password | 5 +- commands/setup-util-apk | 2 +- commands/setup-util-apt | 4 +- commands/setup-util-clojure | 19 - commands/setup-util-deno | 2 +- commands/setup-util-devel | 3 +- commands/setup-util-ghostscript | 9 + commands/setup-util-glab | 2 +- commands/setup-util-gocryptfs | 48 +- commands/setup-util-gsed | 3 +- commands/setup-util-kypton | 6 + commands/setup-util-micro | 2 +- commands/setup-util-ohmyzsh | 2 +- commands/setup-util-prettier | 8 + commands/setup-util-shellcheck | 31 +- commands/setup-util-shfmt | 20 + commands/setup-util-snap | 2 +- commands/setup-util-starship | 2 +- commands/setup-util-strongbox | 6 + commands/setup-util-vim | 8 +- commands/setup-util-vscode | 4 +- commands/setup-utils | 189 +- commands/sharebox | 844 ++--- commands/silent | 3 + commands/silent-stderr | 4 +- commands/silent-stdout | 2 + commands/sparse-vault | 229 +- commands/ssh-helper | 379 +- commands/stderr | 2 + commands/sudo-inherit | 56 +- commands/svg-export | 142 +- commands/symlink-helper | 128 + commands/symlinks | 5 - commands/tmutil-evict | 6 - commands/tmutil-helper | 82 + commands/to-png | 52 +- commands/together | 83 +- commands/travis-token | 9 - commands/trim-audio | 92 +- commands/twitter-advertisers | 14 - commands/twitter-block | 40 - commands/twitter-block-advertisers | 4 - commands/twitter-delete | 50 - commands/twitter-delete-latest | 11 - commands/twitter-helper | 179 + commands/twitter-mute | 40 - commands/twitter-setup | 20 - commands/uid | 71 +- commands/until-success | 62 +- commands/unziptar | 200 +- commands/update-grub-universal | 22 - commands/version-compare | 95 +- commands/version-sort | 4 - commands/video-merge | 132 +- commands/waiter | 57 + commands/wallhaven-helper | 122 +- commands/what-is-listening | 84 +- commands/what-is-my-country | 43 +- commands/what-is-my-dns | 128 +- commands/what-is-my-exposed-dns | 23 - commands/what-is-my-gateway | 71 +- commands/what-is-my-ip | 126 +- commands/what-is-my-resolver | 68 - commands/what-is-running | 41 +- commands/what-is-using | 56 +- commands/xps2pdf | 107 +- commands/ytd-helper | 262 +- config/styles.bash | 2 +- config/theme.bash | 29 + docs/CONFIG.md | 8 + docs/CONTRIBUTING.md | 27 + docs/CONVENTIONS.md | 327 ++ docs/STYLES.MD | 92 + TIPS.md => docs/TIPS.md | 8 +- shellcheckrc | 28 + sources/arrays.bash | 12 +- sources/config.bash | 20 +- sources/edit.sh | 4 +- sources/env.bash | 10 +- sources/get_active_shell.sh | 2 +- sources/nofap.bash | 2 +- sources/ripgrep.bash | 28 +- sources/shims.bash | 74 +- sources/stdinargs.bash | 116 +- sources/strict.bash | 20 +- sources/tty.bash | 24 +- 338 files changed, 23209 insertions(+), 15016 deletions(-) create mode 100644 .flake8 create mode 100644 .github/workflows/dorothy.yml create mode 100644 .isort.cfg create mode 100644 .markdownlint.yaml create mode 100644 .trunk/.gitignore create mode 100644 .trunk/trunk.yaml delete mode 100755 commands/alias-details create mode 100755 commands/alias-helper delete mode 100755 commands/alias-make delete mode 100755 commands/alias-target delete mode 100755 commands/alias-to-symlink delete mode 100755 commands/alias-verify delete mode 100755 commands/android-dev delete mode 100755 commands/array-evens delete mode 100755 commands/array-odds delete mode 100755 commands/array-tuple-select delete mode 100755 commands/cert delete mode 100755 commands/choose delete mode 100755 commands/codec create mode 100755 commands/command-working delete mode 100755 commands/concat-files delete mode 100755 commands/contains-string delete mode 100755 commands/contains-word delete mode 100755 commands/ddns delete mode 100755 commands/deno-cache-clean delete mode 100755 commands/does-url-contain delete mode 100755 commands/down-zip delete mode 100755 commands/downtime create mode 100755 commands/echo-checksum create mode 100755 commands/echo-color create mode 100755 commands/echo-decode-html create mode 100755 commands/echo-decode-url create mode 100755 commands/echo-error create mode 100755 commands/echo-escape-spaces create mode 100755 commands/echo-escape-special delete mode 100755 commands/echo-eval create mode 100755 commands/echo-quiet create mode 100755 commands/echo-sort rename commands/{echo-trim-lines => echo-trim-each-line} (50%) delete mode 100755 commands/edit-blackblaze delete mode 100755 commands/escape-spaces delete mode 100755 commands/escape-special delete mode 100755 commands/eval-collapse delete mode 100755 commands/eval-confirm create mode 100755 commands/eval-helper delete mode 100755 commands/fail-on-empty-stdin delete mode 100755 commands/ffmpeg-separate create mode 100755 commands/find-symlinks delete mode 100755 commands/firefox-reset delete mode 100755 commands/forever create mode 100755 commands/get-codec create mode 100755 commands/get-os-theme create mode 100755 commands/get-random-number create mode 100755 commands/github-download delete mode 100755 commands/github-file delete mode 100755 commands/github-file-download delete mode 100755 commands/github-latest-release delete mode 100755 commands/github-release-file delete mode 100755 commands/github-release-file-download delete mode 100755 commands/github-repo-download create mode 100755 commands/grub-helper delete mode 100755 commands/gtd delete mode 100755 commands/http-ok delete mode 100755 commands/http-status delete mode 100755 commands/ios-dev delete mode 100755 commands/ip-local delete mode 100755 commands/ip-remote delete mode 100755 commands/is-help delete mode 100755 commands/is-help-empty delete mode 100755 commands/is-help-separator delete mode 100755 commands/is-quiet delete mode 100755 commands/ln-helper delete mode 100755 commands/loop delete mode 100755 commands/mac-addr-new delete mode 100755 commands/mac-addr-refresh create mode 100755 commands/mac-address-helper create mode 100755 commands/openssl-helper delete mode 100755 commands/padd delete mode 100755 commands/pipp delete mode 100755 commands/rand delete mode 100755 commands/read-arrow create mode 100755 commands/read-key delete mode 100755 commands/redis delete mode 100755 commands/redis-flush delete mode 100755 commands/redis-start delete mode 100755 commands/redis-stop delete mode 100755 commands/repo create mode 100755 commands/rm-deno-cache delete mode 100755 commands/rm-tmp delete mode 100755 commands/setup-util-clojure create mode 100755 commands/setup-util-ghostscript create mode 100755 commands/setup-util-kypton create mode 100755 commands/setup-util-prettier create mode 100755 commands/setup-util-shfmt create mode 100755 commands/setup-util-strongbox create mode 100755 commands/symlink-helper delete mode 100755 commands/symlinks delete mode 100755 commands/tmutil-evict create mode 100755 commands/tmutil-helper delete mode 100755 commands/travis-token delete mode 100755 commands/twitter-advertisers delete mode 100755 commands/twitter-block delete mode 100755 commands/twitter-block-advertisers delete mode 100755 commands/twitter-delete delete mode 100755 commands/twitter-delete-latest create mode 100755 commands/twitter-helper delete mode 100755 commands/twitter-mute delete mode 100755 commands/twitter-setup delete mode 100755 commands/update-grub-universal delete mode 100755 commands/version-sort create mode 100755 commands/waiter delete mode 100755 commands/what-is-my-exposed-dns delete mode 100755 commands/what-is-my-resolver create mode 100644 config/theme.bash create mode 100644 docs/CONFIG.md create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/CONVENTIONS.md create mode 100644 docs/STYLES.MD rename TIPS.md => docs/TIPS.md (98%) create mode 100644 shellcheckrc diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..34a9a9f1d --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +# Autoformatter friendly flake8 config (all formatting rules disabled) +[flake8] +extend-ignore = E1, E2, E3, E501, W1, W2, W3, W5 diff --git a/.github/workflows/dorothy.yml b/.github/workflows/dorothy.yml new file mode 100644 index 000000000..adeb06f5d --- /dev/null +++ b/.github/workflows/dorothy.yml @@ -0,0 +1,23 @@ +name: dorothy +"on": + - push + - pull_request +jobs: + test: + strategy: + matrix: + os: + - ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - name: "Checkout" + uses: actions/checkout@v2 + - name: "Configure" + shell: 'script -q -e -c "bash {0}"' + run: | + chmod +x ./commands/* + ./commands/dorothy dev + - name: "Test" + shell: 'script -q -e -c "bash {0}"' + run: | + ./commands/dorothy test diff --git a/.gitignore b/.gitignore index 4234a80e0..5f96b88fb 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # generic **/.DS_Store -.vscode/ basic.vim # custom to dorothy diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 000000000..b9fb3f3e8 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile=black diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..fb940393d --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,10 @@ +# Autoformatter friendly markdownlint config (all formatting rules disabled) +default: true +blank_lines: false +bullet: false +html: false +indentation: false +line_length: false +spaces: false +url: false +whitespace: false diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 000000000..7feb17f2d --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1 @@ +*out diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 000000000..b47e9a046 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,14 @@ +version: 0.1 +cli: + version: 0.8.0-beta +lint: + enabled: + - actionlint@1.6.4 + - black-py@21.10b0 + - flake8@3.9.2 + - gitleaks@7.6.1 + - isort@5.9.3 + - markdownlint@0.29.0 + - prettier@2.4.1 + - shellcheck@0.7.2 + - shfmt@3.4.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 07d313d8e..1eff875bc 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,14 +8,14 @@ "eamodio.gitlens", "editorconfig.editorconfig", "esbenp.prettier-vscode", - "foxundermoon.shell-format", "hedinne.popping-and-locking-vscode", "jeff-hykin.better-shellscript-syntax", "sidneys1.gitconfig", "skyapps.fish-vscode", "streetsidesoftware.code-spell-checker", - "timonwong.shellcheck" + "timonwong.shellcheck", + "Trunk.io" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] + "unwantedRecommendations": ["foxundermoon.shell-format"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index b2b27e43b..3acd3d318 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "[shellscript]": { - "editor.defaultFormatter": "foxundermoon.shell-format" + "editor.defaultFormatter": "Trunk.io" }, "[plaintext,mdx]": { "editor.folding": false, @@ -18,9 +18,8 @@ "editor.inlineSuggest.enabled": true, "editor.insertSpaces": false, "editor.renderControlCharacters": true, - "editor.renderIndentGuides": true, "editor.renderWhitespace": "all", - "editor.tabSize": 3, - "files.trimTrailingWhitespace": true, - "workbench.colorTheme": "Popping and Locking" + "editor.tabSize": 4, + "files.trimTrailingWhitespace": true + // "workbench.colorTheme": "Popping and Locking" } diff --git a/README.md b/README.md index 23f12969b..9f94436e9 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ The [`itunes-owners` command](https://github.com/bevry/dorothy/tree/master/comma The [`ios-dev` command](https://github.com/bevry/dorothy/tree/master/commands/ios-dev) lets you easily open the iOS simulator from the terminal. -The [`alias-details`](https://github.com/bevry/dorothy/tree/master/commands/alias-details), [`aliases`](https://github.com/bevry/dorothy/tree/master/commands/aliases), [`aliases-to-symlink`](https://github.com/bevry/dorothy/tree/master/commands/aliases-to-symlink), [`alias-path`](https://github.com/bevry/dorothy/tree/master/commands/alias-path), [`alias-verify`](https://github.com/bevry/dorothy/tree/master/commands/alias-details) commands will help you convert MacOS aliases into symlinks. +The [`alias-helper`](https://github.com/bevry/dorothy/tree/master/commands/alias-helper) will help you manage your macOS aliases, and if desired, convert them into symlinks. ### Media diff --git a/commands/alias-details b/commands/alias-details deleted file mode 100755 index a3fdbd858..000000000 --- a/commands/alias-details +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# inputs -path="$( - ask --required \ - --question="Enter path of the macOS alias file that you want the details for." \ - --default="${1-}" -)" -# @todo if path is a directory, then get details of all aliases inside the directory - -# verify -src="$(alias-verify "$path" || :)" -if test -z "$src"; then - stderr echo-style \ - --bold+red="$path" \ - $'\t' \ - $'\t' --error='← not an alias' - exit 1 -fi - -# target -target="$(alias-target "$path" || :)" -if test -z "$target"; then - stderr echo-style \ - --bold="$src" \ - $'\t' \ - $'\t' --error='← target broken' - exit 1 -fi -if test ! -e "$target"; then - stderr echo-style \ - --bold="$src" \ - --nocolor=$'\t' --color+dim=$'\t→\t' --bold+red="$target" \ - $'\t' --error='← target missing' - exit 1 -fi - -# success -echo-style \ - --bold="$src" \ - --nocolor=$'\t' --color+dim=$'\t→\t' --bold+green="$target" diff --git a/commands/alias-helper b/commands/alias-helper new file mode 100755 index 000000000..5035dcac0 --- /dev/null +++ b/commands/alias-helper @@ -0,0 +1,217 @@ +#!/usr/bin/env bash +source "$DOROTHY/sources/strict.bash" + +function alias-helper() ( + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Wrappers around macOS aliases to make things easier. + + USAGE: + alias-helper + + ACTIONS: + new -- + Makes a new macOS alias file at the pointing to the . + + symlink -- + Converts the macOS alias file with its UNIX symlink equivalent. + + verify -- + Verify the path is a macOS alias file. + + target -- + Output the target if the path is a macOS alias file. + + info -- + Human friendly details about the macOS alias file. + EOF + if test "$#" -ne 0; then + echo-error "$@" + fi + return 22 # Invalid argument + } + + # process + local item action='' args=() + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--') + args+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) + if test -z "$action"; then + action="$item" + else + help "An unrecognised argument was provided: $item" + fi + ;; + esac + done + + # ===================================== + # Actions + + function alias_verify { + local path="$1" + silent-stderr osascript <<-EOF + tell application "Finder" + set theItem to (POSIX file "$path") as alias + if the kind of theItem is "alias" then + get the posix path of ((theItem) as text) + end if + end tell + EOF + } + + function alias_target { + local path="$1" + silent-stderr osascript <<-EOF + tell application "Finder" + set theItem to (POSIX file "$path") as alias + if the kind of theItem is "alias" then + get the POSIX path of ((original item of theItem) as text) + end if + end tell + EOF + } + + function alias_info { + local path="$1" src target + + # verify + src="$(alias_verify "$path" || :)" + if test -z "$src"; then + { + echo-style \ + --bold+red="$path" \ + $'\t' \ + $'\t' --error='← not an alias' + return 22 # EINVAL Invalid argument + } >/dev/stderr + fi + + # target + target="$(aliastarget "$path" || :)" + if test -z "$target"; then + { + stderr echo-style \ + --bold="$src" \ + $'\t' \ + $'\t' --error='← target broken' + return 9 # EBADF Bad file descriptor + } >/dev/stderr + fi + if test ! -e "$target"; then + { + stderr echo-style \ + --bold="$src" \ + --nocolor=$'\t' --color+dim=$'\t→\t' --bold+red="$target" \ + $'\t' --error='← target missing' + return 2 # ENOENT No such file or directory + } >/dev/stderr + fi + + # success + echo-style \ + --bold="$src" \ + --nocolor=$'\t' --color+dim=$'\t→\t' --bold+green="$target" + } + + function alias_new { + local path="$1" target="$2" type target_absolute path_absolute path_directory path_filename + target_absolute="$(fs-absolute "$target")" + path_absolute="$(fs-absolute "$path")" + path_directory="$(dirname "$path_absolute")" + path_filename="$(basename "$path_absolute")" + + # act + if test -d "$target_absolute"; then + type="folder" + elif test -f "$target_absolute"; then + type="file" + else + { + echo-style --error='Invalid path or unsupported type:' ' ' --code="$path" + return 22 # EINVAL Invalid argument + } >/dev/stderr + fi + + if test -f "$path_absolute"; then + fs-rm "$path_absolute" + fi + + osascript <<-EOF + tell application "Finder" + make new alias to ${type} (posix file "$target_absolute") at (posix file "$path_directory") + set name of result to "$path_filename" + end tell + EOF + + # make the alias's permissions the same as the target's + chmod "$(stat -f '%p' "$target_absolute")" "$path_absolute" + #chmod --reference="$targetPath" "$path_absolute" + } + + function alias_symlink { + local path="$1" src target + + # verify alias + src="$(alias_verify "$path" || :)" + if test -z "$src"; then + { + echo-style --bold+red="$path" ' ' --error='<- not an alias' + return 22 # EINVAL Invalid argument + } >/dev/stderr + fi + + # verify target + target="$(alias_target "$path" || :)" + if test -z "$target"; then + { + echo-style --bold="$src" --dim=' → ' --bold+red="$target" ' ' --error='← target broken' + return 9 # EBADF Bad file descriptor + } >/dev/stderr + fi + if test ! -e "$target"; then + { + echo-style --bold="$src" --dim=' → ' --bold+red="$target" ' ' --error='← target missing' + return 2 # ENOENT No such file or directory + } >/dev/stderr + fi + + # convert + if test -f "$target"; then + ln -nfs "$target" "$src" + echo "converted $path -> $target" + elif test -d "$target"; then + ln -nfs "$target" "$src" + echo "converted $path -> $target" + fi + } + + # ===================================== + # Act + + if test "$(type -t "alias_$action")" = 'function'; then + "alias_$action" "${args[@]}" + return "$?" + else + echo-style --error="Action [$action] not yet implemented." >/dev/stderr + return 78 # Function not implemented + fi +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + alias-helper "$@" +fi diff --git a/commands/alias-make b/commands/alias-make deleted file mode 100755 index 840228606..000000000 --- a/commands/alias-make +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# inputs -path="$( - ask --required \ - --question="Enter path where you want the macOS alias file to exist." \ - --default="${1-}" -)" -target="$( - ask --required \ - --question="Enter path where you want the macOS alias file to target." \ - --default="${2-}" -)" - -# prepare -targetAbsolute="$(fs-absolute "$target")" -pathAbsolute="$(fs-absolute "$path")" -pathDirectory="$(dirname "$pathAbsolute")" -pathFilename="$(basename "$pathAbsolute")" - -# act -if test -d "$targetAbsolute"; then - type="folder" -elif test -f "$targetAbsolute"; then - type="file" -else - stderr echo "Invalid path or unsupported type" - exit 22 # Invalid argument -fi - -if test -f "$pathAbsolute"; then - rm -rfi "$pathAbsolute" -fi - -osascript <<-EOF - tell application "Finder" - make new alias to $type (posix file "$targetAbsolute") at (posix file "$pathDirectory") - set name of result to "$pathFilename" - end tell -EOF - -# make the alias's permissions the same as the target's -chmod "$(stat -f '%p' "$targetAbsolute")" "$pathAbsolute" -#chmod --reference="$targetPath" "$pathAbsolute" diff --git a/commands/alias-target b/commands/alias-target deleted file mode 100755 index 2ef443699..000000000 --- a/commands/alias-target +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" -# http://stackoverflow.com/a/1330366/130638 - -# inputs -path="$( - ask --required \ - --question="Enter the path of a macOS alias file to get its target details." \ - --default="${1-}" -)" - -# act -silent-stderr osascript <<-EOF - tell application "Finder" - set theItem to (POSIX file "$path") as alias - if the kind of theItem is "alias" then - get the POSIX path of ((original item of theItem) as text) - end if - end tell -EOF diff --git a/commands/alias-to-symlink b/commands/alias-to-symlink deleted file mode 100755 index b02734bcd..000000000 --- a/commands/alias-to-symlink +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# inputs -path="$( - ask --required \ - --question="Enter the path of the macOS alias file that you want converted into a symlink." \ - --default="${1-}" -)" -# @todo if path is a directory, then replace all aliases inside the directory - -# verify alias -src="$(alias-verify "$path" || :)" -if test -z "$src"; then - stderr echo-style --bold+red="$path" ' ' --error='<- not an alias' - exit 1 -fi - -# verify target -target="$(alias-target "$path" || :)" -if test -z "$target"; then - stderr echo-style --bold="$src" --dim=' → ' --bold+red="$target" ' ' --error='← target broken' - exit 1 -fi -if test ! -e "$target"; then - stderr echo-style --bold="$src" --dim=' → ' --bold+red="$target" ' ' --error='← target missing' - exit 1 -fi - -# convert -if test -f "$target"; then - ln -nfs "$target" "$src" - echo "converted $path -> $target" -elif test -d "$target"; then - ln -nfs "$target" "$src" - echo "converted $path -> $target" -fi diff --git a/commands/alias-verify b/commands/alias-verify deleted file mode 100755 index 8add98429..000000000 --- a/commands/alias-verify +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# inputs -path="$( - ask --required \ - --question="Enter the path to test if it is a macOS alias file." \ - --default="${1-}" -)" - -# act -silent-stderr osascript <<-EOF - tell application "Finder" - set theItem to (POSIX file "$path") as alias - if the kind of theItem is "alias" then - get the posix path of ((theItem) as text) - end if - end tell -EOF diff --git a/commands/android-dev b/commands/android-dev deleted file mode 100755 index fe9d4c333..000000000 --- a/commands/android-dev +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# Android Simulator -app=$(get-app "Android Studio.app") -if test -d "$app"; then - "$app/sdk/tools/emulator" -avd basic -else - echo "Android Studio is not installed" -fi diff --git a/commands/array-evens b/commands/array-evens deleted file mode 100755 index eacc70e19..000000000 --- a/commands/array-evens +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" -source "$DOROTHY/sources/arrays.bash" -requires_array_support 'mapfile' - -# usage: -# mapfile -t values < <(array-evens "${tuples[@]}") - -mapfile -t args < <(echo-lines -- "$@") # split jumbled arguments -for i in "${!args[@]}"; do - if is-even "$i"; then - echo "${args[$i]}" - fi -done diff --git a/commands/array-odds b/commands/array-odds deleted file mode 100755 index 9cdae5707..000000000 --- a/commands/array-odds +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" -source "$DOROTHY/sources/arrays.bash" -requires_array_support 'mapfile' - -# usage: -# mapfile -t values < <(array-odds "${tuples[@]}") - -mapfile -t args < <(echo-lines -- "$@") # split jumbled arguments -for i in "${!args[@]}"; do - if is-odd "$i"; then - echo "${args[$i]}" - fi -done diff --git a/commands/array-tuple-select b/commands/array-tuple-select deleted file mode 100755 index 0d77bd4f8..000000000 --- a/commands/array-tuple-select +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -needle="${1?:'USAGE: array-tuple-select key <[key, value], ...>'}" -found='no' - -# cycle through the tuples -# remember @:0 is the command, @:1 is the needle, @:2:... is the tuples -for item in "${@:2}"; do - if test "$found" = 'yes'; then - echo "$item" - exit 0 - elif test "$item" = "$needle"; then - found='yes' - fi -done - -# needle not found -exit 1 diff --git a/commands/ask b/commands/ask index 04689579f..a6cb28cff 100755 --- a/commands/ask +++ b/commands/ask @@ -5,203 +5,215 @@ source "$DOROTHY/sources/tty.bash" source "$DOROTHY/sources/arrays.bash" may_require_array_support 'mapfile' 'empty' -# ===================================== -# Arguments - -# help -function help() { - cat <<-EOF >/dev/stderr - ABOUT: - Prompt the user for an input value in a clean and robust way. - - USAGE: - ask [...flags] [--flag=... -- ...args] - - FLAGS: - Provide [--question=...] to specify the question that the prompt will be answering. - Provide [--default=...] to specify the default value if no user specified value is entered. - Provide [--confirm] to specify that the prompt should confirm the value before continuing. - Provide [--password] to specify that the prompt should hide the value when entering by using password mode. - Provide [--required] to specify that the prompt should not continue until a value is provided. - Provide [--timeout=...] to specify a custom timeout value in seconds. - Provide [--flag=... -- ...args] to specify a flag to search the arguments for, to set a default value. - EOF - if test "$#" -ne 0; then - echo-style $'\n' --error="ERROR:" $'\n' --red="$(echo-lines -- "$@")" >/dev/stderr - fi - return 22 # Invalid argument -} - -# process -args=() # used with option_flag -option_question='' -option_default='' -option_password='no' -option_required='no' -option_confirm='no' -option_timeout='' -option_flag='' -while test "$#" -ne 0; do - item="$1" - shift - case "$item" in - 'help' | '--help' | '-h') help ;; - '--question='*) option_question="${item:11}" ;; - '--default='*) option_default="${item:10}" ;; - '--timeout='*) option_timeout="${item:10}" ;; - '--flag='*) option_flag="${item:7}" ;; - '--no-password'* | '--password'*) - option_password="$(get-flag-value password --missing="$option_password" -- "$item" | echo-affirmative)" - ;; - '--no-required'* | '--required'*) - option_required="$(get-flag-value required --missing="$option_required" -- "$item" | echo-affirmative)" - ;; - '--no-confirm'* | '--confirm'*) - option_confirm="$(get-flag-value confirm --missing="$option_confirm" -- "$item" | echo-affirmative)" - ;; - '--') - args+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) help "An unrecognised argument was provided: $item" ;; - esac -done - -# prepare flags -flags=('-r') -if test -n "$option_default"; then - result="$option_default" -elif test -n "$option_flag"; then - result="$(get-flag-value "$option_flag" -- "${args[@]}")" -else - result='' -fi +function ask() ( + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Prompt the user for an input value in a clean and robust way. + + USAGE: + ask [...options] + + OPTIONS: + --question= + Specifies the question that the prompt will be answering. + + --default= + Specifies the default value if no user specified value is entered. + + --confirm + Specifies that the prompt should confirm the value before continuing. + + --password + Specifies that the prompt should hide the value when entering by using password mode. + + --required + Specifies that the prompt should not continue until a value is provided. + + --timeout= + Specifies a custom timeout value in seconds. + + --flag= -- <...args> + Specifies a flag to search the arguments for, to set a default value. + EOF + if test "$#" -ne 0; then + echo-error "$@" + fi + return 22 # Invalid argument + } + + # process + local item args=() option_question='' option_default='' option_password='no' option_required='no' option_confirm='no' option_timeout='' option_flag='' + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--question='*) option_question="${item:11}" ;; + '--default='*) option_default="${item:10}" ;; + '--timeout='*) option_timeout="${item:10}" ;; + '--flag='*) option_flag="${item:7}" ;; + '--no-password'* | '--password'*) + option_password="$(get-flag-value password --missing="$option_password" -- "$item" | echo-affirmative)" + ;; + '--no-required'* | '--required'*) + option_required="$(get-flag-value required --missing="$option_required" -- "$item" | echo-affirmative)" + ;; + '--no-confirm'* | '--confirm'*) + option_confirm="$(get-flag-value confirm --missing="$option_confirm" -- "$item" | echo-affirmative)" + ;; + '--') + args+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) help "An unrecognised argument was provided: $item" ;; + esac + done -# helpers -asked='no' -function handle_timeout() { - if is-value "$result"; then - echo "Ask timed out, using fallback value: $result" >/dev/stderr - sleep 5 - echo "$result" - return 0 - elif test "$option_required" = 'no'; then - echo 'Ask timed out, as the field was optional will use no value.' >/dev/stderr - sleep 5 - return 0 + # prepare flags + local result read_flags=('-r') + if test -n "$option_default"; then + result="$option_default" + elif test -n "$option_flag"; then + result="$(get-flag-value "$option_flag" -- "${args[@]}")" else - echo 'Ask timed out, with no fallback.' >/dev/stderr - sleep 5 - return 62 # Timer expired - fi -} -function ask() { - local ec - tty_auto - asked='yes' # not lcoal - if test -n "${1-}"; then - echo "$1" >/dev/tty + result='' fi - while true; do - ec=0 && read "${flags[@]}" -t 300 -r -p "> " result || ec="$?" - if test "$ec" -gt 128; then - return 62 # Timer expired - fi + + # helpers + local asked='no' + function on_timeout { if is-value "$result"; then - break + echo "Ask timed out, using fallback value: $result" >/dev/stderr + sleep 5 + echo "$result" + return 0 elif test "$option_required" = 'no'; then - result='' - break + echo 'Ask timed out, as the field was optional will use no value.' >/dev/stderr + sleep 5 + return 0 + else + echo 'Ask timed out, with no fallback.' >/dev/stderr + sleep 5 + return 62 # Timer expired fi - done - validate -} -function validate() { - local ec choices=() - if is-value "$result"; then - # we have a value, so go for it - if test "$option_confirm" != 'yes'; then - echo "$result" - exit + } + function ask { + local ec + tty_auto + asked='yes' # not lcoal + if test -n "${1-}"; then + echo "$1" >/dev/tty fi - # proceed with confirm - if test "$asked" = 'yes'; then - if test "$option_password" = 'yes'; then - choices+=('existing' 'use the entered password') - else - choices+=('existing' "use the entered value: [$result]") + while true; do + ec=0 && read "${read_flags[@]}" -t 300 -r -p "> " result || ec="$?" + if test "$ec" -gt 128; then + return 62 # Timer expired fi - else - if test "$option_password" = 'yes'; then - choices+=('existing' 'use the preconfigured password') + if is-value "$result"; then + break + elif test "$option_required" = 'no'; then + result='' + break + fi + done + validate + } + function validate { + local ec choice choices=() + if is-value "$result"; then + # we have a value, so go for it + if test "$option_confirm" != 'yes'; then + echo "$result" + return + fi + # proceed with confirm + if test "$asked" = 'yes'; then + if test "$option_password" = 'yes'; then + choices+=('existing' 'use the entered password') + else + choices+=('existing' "use the entered value: [$result]") + fi else - choices+=('existing' "use the preconfigured value: [$result]") + if test "$option_password" = 'yes'; then + choices+=('existing' 'use the preconfigured password') + else + choices+=('existing' "use the preconfigured value: [$result]") + fi fi fi - fi - if test "$asked" = 'yes'; then - choices+=('custom' 'redo the entered value') - else - choices+=('custom' 'enter a value') - fi - if test "$option_required" = 'no'; then - choices+=('none' 'use no value') - fi - - # as need to confirm, adjust the timeout - if test -z "$option_timeout" && (is-value "$result" || test "$option_required" = 'no'); then - # timeout of one minute for confirms of existing values, or optional values - option_timeout=60 - fi + if test "$asked" = 'yes'; then + choices+=('custom' 'redo the entered value') + else + choices+=('custom' 'enter a value') + fi + if test "$option_required" = 'no'; then + choices+=('none' 'use no value') + fi - # ask - ec=0 && choice="$(choose-option \ - --timeout="$option_timeout" \ - --question="$option_question" \ - --label -- "${choices[@]}")" || ec="$?" - - # check - if test "$ec" -eq 62; then - echo "Choose timed out [$ec]." >/dev/stderr - handle_timeout - exit "$?" # exit with the above on success and failure - elif test "$ec" -ne 0; then - echo "Choose failed [$ec]." >/dev/stderr - sleep 5 - exit "$ec" - fi + # as need to confirm, adjust the timeout + if test -z "$option_timeout" && (is-value "$result" || test "$option_required" = 'no'); then + # timeout of one minute for confirms of existing values, or optional values + option_timeout=60 + fi - # handle - if test "$choice" = 'existing'; then - # done, sucess - echo "$result" - exit - elif test "$choice" = 'custom'; then # ask - ec=0 && ask "$option_question" || ec="$?" + ec=0 && choice="$(choose-option \ + --timeout="$option_timeout" \ + --question="$option_question" \ + --label -- "${choices[@]}")" || ec="$?" - # check for failure - if test "$ec" -ne 0; then - # timeout probably - handle_timeout - exit "$?" # exit with the above on success and failure + # check + if test "$ec" -eq 62; then + echo-style --error="Choose timed out: $ec" >/dev/stderr + on_timeout + return "$?" # exit with the above on success and failure + elif test "$ec" -ne 0; then + echo-style --error="Choose failed: $ec" >/dev/stderr + sleep 5 + return "$ec" fi - # done, success - exit - elif test "$choice" = 'none'; then - # done, sucess - echo - exit - else - # unknown error - echo "invalid choice: [$result]" >/dev/stderr - sleep 5 - exit 1 - fi -} + # handle + if test "$choice" = 'existing'; then + # done, sucess + echo "$result" + return + elif test "$choice" = 'custom'; then + # ask + ec=0 && ask "$option_question" || ec="$?" + + # check for failure + if test "$ec" -ne 0; then + # timeout probably + on_timeout + return "$?" # exit with the above on success and failure + fi + + # done, success + return + elif test "$choice" = 'none'; then + # done, sucess + echo + return + else + # unknown error + echo-style --error="Invalid choice: $choice" >/dev/stderr + sleep 5 + return 14 # EFAULT 14 Bad address + fi + } -# act -validate + # act + validate +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + ask "$@" +fi diff --git a/commands/brew b/commands/brew index df5bbe529..7fc184fc5 100755 --- a/commands/brew +++ b/commands/brew @@ -2,6 +2,8 @@ source "$DOROTHY/sources/strict.bash" # we can assume [HOMEBREW_ARCH, HOMEBREW_PREFIX] have already been provided on brew supported systems +# This is an internal command, no need for help handling. + # Without this command, using HOMEBREW_ARCH=x86_64 on Apple Silicon will fail with: # ``` # Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! @@ -13,11 +15,18 @@ source "$DOROTHY/sources/strict.bash" # brew on desired architecture # ``` -if test -x "${HOMEBREW_PREFIX-}/bin/brew"; then - # as we handle brew updating specially and thoroughly, stop brew from doing it on things like installs - export HOMEBREW_NO_AUTO_UPDATE=1 - arch "-${HOMEBREW_ARCH}" "${HOMEBREW_PREFIX}/bin/brew" "$@" -else - stderr echo 'brew not installed' - exit 1 +function brew() ( + if test -x "${HOMEBREW_PREFIX-}/bin/brew"; then + # as we handle brew updating specially and thoroughly, stop brew from doing it on things like installs + env HOMEBREW_NO_AUTO_UPDATE=1 \ + arch "-${HOMEBREW_ARCH}" "${HOMEBREW_PREFIX}/bin/brew" "$@" + else + echo-style --error='Homebrew is not installed.' ' ' --notice="Install it with:" ' ' --code="$(get-installer brew)" >/dev/stderr + return 74 # EPROGUNAVAIL 74 RPC prog. not avail + fi +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + brew "$@" fi diff --git a/commands/brew-installed b/commands/brew-installed index 9bddd4a0b..d86f265d2 100755 --- a/commands/brew-installed +++ b/commands/brew-installed @@ -1,101 +1,111 @@ #!/usr/bin/env bash source "$DOROTHY/sources/strict.bash" -# ===================================== -# Arguments - -# help -function help() { - cat <<-EOF >/dev/stderr - ABOUT: - Outputs the names of the installed packages. - - USAGE: - brew-installed [--requested] [--formula] [--cask] -- [...packages] - - FLAGS: - --requested Output only packages that were manually installed, not packages that were only dependencies. - --formula Output only packages that are formulas. - --cask Output only packages that are casks. - [...packages] If provided, only get details for these packages. Fail if one of them has not yet been installed. - EOF - if test "$#" -ne 0; then - echo-style $'\n' --error="ERROR:" $'\n' --red="$(echo-lines -- "$@")" >/dev/stderr - fi - return 22 # Invalid argument -} +function brew-installed() ( + # ===================================== + # Arguments -# process -brew_packages=() -brew_list=( - 'brew' - 'list' - '--versions' # --versions is necessary to limit to installed packages, as --full-name/-1 doesn't -) -brew_info=( - 'brew' - 'info' -) -deno_script_args=( - '--requested' # the deno script only runs with brew info, which is only run when --requested is provided -) -brew_type='' # empty, formula, cask -option_requested='no' -while test "$#" -ne 0; do - item="$1" - shift - case "$item" in - 'help' | '--help' | '-h') help ;; - '--formula' | '--formulae') brew_type='formula' ;; - '--cask' | '--casks') brew_type='cask' ;; - '--no-requested'* | '--requested'*) option_requested="$( - get-flag-value requested --missing="$option_requested" -- "$item" | echo-affirmative - )" ;; - '--') - brew_packages+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) help "An unrecognised argument was provided: $item" ;; - esac -done - -# add the filter -if test -n "$brew_type"; then - brew_list+=(--"${brew_type}") - brew_info+=(--"${brew_type}") - deno_script_args+=(--"${brew_type}") -fi + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Outputs the names of the installed packages. + + USAGE: + brew-installed [...options] -- [...packages] -# add the packages -if test "${#brew_packages[@]}" -ne 0; then - brew_list+=("${brew_packages[@]}") - brew_info+=( - '--json=v2' - "${brew_packages[@]}" + OPTIONS: + --requested + Output only packages that were manually installed, not packages that were only dependencies. + + --formula + Output only packages that are formulas. + + --cask + Output only packages that are casks. + + ARGUMENTS: + ...packages + If provided, only get details for these packages. Fail if one of them has not yet been installed. + EOF + if test "$#" -ne 0; then + echo-error "$@" + fi + return 22 # Invalid argument + } + + # process + local item requested='no' packages brew_type brew_list_cmd brew_info_cmd deno_script_args + packages=() + brew_type='' # empty, formula, cask + brew_list_cmd=( + 'brew' + 'list' + '--versions' # --versions is necessary to limit to installed packages, as --full-name/-1 doesn't ) -else - brew_info+=( - '--json=v2' - '--installed' + brew_info_cmd=( + 'brew' + 'info' ) -fi + deno_script_args=( + '--requested' # the deno script only runs with brew info, which is only run when --requested is provided + ) + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--formula' | '--formulae') brew_type='formula' ;; + '--cask' | '--casks') brew_type='cask' ;; + '--no-requested'* | '--requested'*) requested="$( + get-flag-value requested --missing="$requested" -- "$item" | echo-affirmative + )" ;; + '--') + packages+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) help "An unrecognised argument was provided: $item" ;; + esac + done -# helpers -function do_brew_list() { - "${brew_list[@]}" | cut -d' ' -f1 | sort | uniq - # returns 1 if one of them was missing - # if you want to check if any were present, do - # test -n "$(brew-installed -- rustup rust || :)" -} -function do_brew_info() { - # ensure deno exists - env QUIET=yes setup-util-deno + # add the filter + if test -n "$brew_type"; then + brew_list_cmd+=("--$brew_type") + brew_info_cmd+=("--$brew_type") + deno_script_args+=("--$brew_type") + fi - # prepare as the .ts extension is essential - deno_script="$(fs-temp --xdg --filename="brew-installed-deno.ts")" - cat <"$deno_script" + # add the packages + if test "${#packages[@]}" -ne 0; then + brew_list_cmd+=("${packages[@]}") + brew_info_cmd+=( + '--json=v2' + "${packages[@]}" + ) + else + brew_info_cmd+=( + '--json=v2' + '--installed' + ) + fi + + # helpers + function do_brew_list { + "${brew_list_cmd[@]}" | cut -d' ' -f1 | sort | uniq + # returns 1 if one of them was missing + # if you want to check if any were present, do + # test -n "$(brew-installed -- rustup rust || :)" + } + function do_brew_info { + local temp_deno_script temp_file lines_in_temp_file + + # ensure deno exists + env QUIET=yes setup-util-deno + + # prepare as the .ts extension is essential + temp_deno_script="$(fs-temp --directory='brew-installed' --file='deno.ts')" + cat <"$temp_deno_script" // parse stdin import { readLines } from "https://deno.land/std/io/bufio.ts"; let data = ""; @@ -140,23 +150,29 @@ if (names.size) { } EOF - # run - "${brew_info[@]}" | deno run --quiet "$deno_script" "${deno_script_args[@]}" -} + # run + "${brew_info_cmd[@]}" | deno run --quiet "$temp_deno_script" "${deno_script_args[@]}" + } -# get names of requested packages -if test "$option_requested" = 'no'; then - do_brew_list -else - # as brew_info doesn't do the missing exit code like brew_list does, we need to do it ourselves - if test "${#brew_packages[@]}" -eq 0; then - do_brew_info + # get names of requested packages + if test "$requested" = 'no'; then + do_brew_list else - temp="$(mktemp)" - do_brew_info | tee "$temp" - lines="$(echo-count-lines --no-inline <"$temp")" - if test "$lines" -ne "${#brew_packages[@]}"; then - exit 1 + # as brew_info doesn't do the missing exit code like brew_list does, we need to do it ourselves + if test "${#packages[@]}" -eq 0; then + do_brew_info + else + temp_file="$(mktemp)" + do_brew_info | tee "$temp_file" + lines_in_temp_file="$(echo-count-lines --no-inline <"$temp_file")" + if test "$lines_in_temp_file" -ne "${#packages[@]}"; then + return 1 + fi fi fi +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + brew-installed "$@" fi diff --git a/commands/btrfs-helper b/commands/btrfs-helper index 851730fd6..704050c33 100755 --- a/commands/btrfs-helper +++ b/commands/btrfs-helper @@ -4,234 +4,241 @@ source "$DOROTHY/sources/ripgrep.bash" source "$DOROTHY/sources/arrays.bash" has_array_support 'mapfile' -# ===================================== -# Arguments - -# prepare -actions=( - drives - drive - mounts - new - add - balance - verify - mounted -) +function btrfs-helper() ( + # ===================================== + # Arguments -# help -function help() { - cat <<-EOF >/dev/stderr - ABOUT: - Wrappers around btrfs to make things easier. + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Wrappers around btrfs to make things easier. - USAGE: - btrfs-helper drives - # ^ Lists all attached drives that are formatted to btrfs. + USAGE: + btrfs-helper - btrfs-helper drive -- - # ^ Gets the primary drive for the btrfs filesystem label. + ACTIONS: + drives + Lists all attached drives that are formatted to btrfs. - btrfs-helper mounts - # ^ Lists all mount points which btrfs formatted drives are attached to. + drive -- + Gets the primary drive for the btrfs filesystem label. - btrfs-helper new - # ^ Prompts you to select a drive to erase and format to btrfs. + mounts + Lists all mount points which btrfs formatted drives are attached to. - btrfs-helper add - # ^ Prompts you to select a drive, and an existing btrfs mount point, to add the drive to. + new + Prompts you to select a drive to erase and format to btrfs. - btrfs-helper balance - # ^ Prompts you to select a btrfs mount point, to run a btrfs raid1 balance on. + add + Prompts you to select a drive, and an existing btrfs mount point, to add the drive to. - btrfs-helper verify -- - # ^ Verifies that the drive count for a btrfs filesystem label is correct. + balance + Prompts you to select a btrfs mount point, to run a btrfs raid1 balance on. - btrfs-helper mounted -- - # ^ Verifies that the btrfs filesystem label is mounted to the expected mount point. - EOF - if test "$#" -ne 0; then - echo-style $'\n' --error="ERROR:" $'\n' --red="$(echo-lines -- "$@")" >/dev/stderr - fi - return 22 # Invalid argument -} - -# process -action='' -action_args=() -while test "$#" -ne 0; do - item="$1" - shift - case "$item" in - 'help' | '--help' | '-h') help ;; - '--action='*) action="${item:9}" ;; - '--') - action_args+=("$@") - shift $# - break - ;; - '--'*) help "An unrecognised flag was provided: $item" ;; - *) - if test -z "$action"; then - action="$item" - else - help "An unrecognised argument was provided: $item" - fi - ;; - esac -done + verify -- + Verifies that the drive count for a btrfs filesystem label is correct. -# ensure valid action -action="$( - choose-option --required \ - --question='What action to perform?' \ - --filter="$action" -- "${actions[@]}" -)" + mounted -- + Verifies that the btrfs filesystem label is mounted to the expected mount point. + EOF + if test "$#" -ne 0; then + echo-error "$@" + fi + return 22 # Invalid argument + } + + # process + local item action='' args=() + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--') + args+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) + if test -z "$action"; then + action="$item" + else + help "An unrecognised argument was provided: $item" + fi + ;; + esac + done + + # ensure valid action + local actions=( + drives + drive + mounts + new + add + balance + verify + mounted + ) + action="$( + choose-option --required \ + --question='What action to perform?' \ + --filter="$action" -- "${actions[@]}" + )" -# ===================================== -# Helpers + # ===================================== + # Helpers -function get_btrfs_mounts { # Get all the mount points which btrfs drives are mounted to - df -Th | awk '($2 == "btrfs") {print $7}' - # lsblk -f | awk '($2 == "btrfs" && $7) {print $7}' -} + function get_btrfs_mounts { + df -Th | awk '($2 == "btrfs") {print $7}' + # lsblk -f | awk '($2 == "btrfs" && $7) {print $7}' + } -function get_btrfs_mount { # Prompt for a specific btrfs mount point - mapfile -t mounts < <(btrfs_mounts) - local question="$1" - choose-option --required \ - --question="$question" \ - -- "${mounts[@]}" -} - -function get_any_drive { + function get_btrfs_mount { + local question="$1" mounts + mapfile -t mounts < <(btrfs_mounts) + choose-option --required \ + --question="$question" \ + -- "${mounts[@]}" + } + # Prompt for a specific drive - local question="$1" - mapfile -t drives < <(get-drives) - choose-option --required \ - --question="$question" \ - -- "${drives[@]}" -} - -function get_btrfs_drives { + function get_any_drive { + local question="$1" drives=() + mapfile -t drives < <(get-drives) + choose-option --required \ + --question="$question" \ + -- "${drives[@]}" + } + # Get all the drives which are formatted to btrfs - lsblk -f | awk '/btrfs/ {print "/dev/"$1}' -} + function get_btrfs_drives { + lsblk -f | awk '/btrfs/ {print "/dev/"$1}' + } -function get_btrfs_drive { # Prompt for a specific btrfs drive - local question="$1" - mapfile -t drives < <(get_btrfs_drives) - choose-option --required \ - --question="$question" \ - -- "${drives[@]}" -} - -function get_btrfs_drive_for_label { + function get_btrfs_drive { + local question="$1" drives=() + mapfile -t drives < <(get_btrfs_drives) + choose-option --required \ + --question="$question" \ + -- "${drives[@]}" + } + # Get the primary drive of a btrfs filesystem label - local label="$1" - blkid -L "$label" -} + function get_btrfs_drive_for_label { + local label="$1" + blkid -L "$label" + } -function get_btrfs_drives_for_label { # Get the drives of a btrfs filesystem label - local label="$1" - sudo btrfs filesystem show "$label" | rg -o 'path ([a-z0-9/]+)' --replace '$1' -} + function get_btrfs_drives_for_label { + local label="$1" + # trunk-ignore(shellcheck/SC2016) + sudo btrfs filesystem show "$label" | rg -o 'path ([a-z0-9/]+)' --replace '$1' + } -function get_btrfs_count_for_label { # Count the drives of a btrfs filesystem label - local label="$1" - sudo btrfs filesystem show "$label" | rg -o 'Total devices ([0-9]+)' --replace '$1' -} - -# ===================================== -# Actions + function get_btrfs_count_for_label { + local label="$1" + # trunk-ignore(shellcheck/SC2016) + sudo btrfs filesystem show "$label" | rg -o 'Total devices ([0-9]+)' --replace '$1' + } + + # ===================================== + # Actions + + function act_mounts { + get_btrfs_mounts + } + + function act_drives { + get_btrfs_drives + } + + function act_new { + local drive label + drive="$( + get_any_drive "Which drive to erase and format as btrfs?" + )" + label="$( + ask --required \ + --question="What label to use for the new btrfs filesystem that will be attached to the drive [$drive]?" + )" + eval-helper --confirm -- sudo mkfs.btrfs -f -L "$label" "$drive" + } -function btrfs_mounts { - get_btrfs_mounts -} - -function btrfs_drives { - get_btrfs_drives -} - -function btrfs_new { - local drive label - drive="$( - get_any_drive "Which drive to erase and format as btrfs?" - )" - label="$( - ask --required \ - --question="What label to use for the new btrfs filesystem that will be attached to the drive [$drive]?" - )" - eval-confirm -- sudo mkfs.btrfs -f -L "$label" "$drive" -} - -function btrfs_add { # Add a drive to a btrfs cluster - local drive mount - echo 'You will now specify a mount point of an existing btrfs cluster, and a drive that you wish to be erased and added to it.' - mount="$(get_btrfs_mount "Which mount point is the btrfs cluster that will house the new drive?")" - drive="$(get_any_drive "Which drive is the one to be erased then added into the btrfs cluster?")" + function act_add { + local drive mount + echo 'You will now specify a mount point of an existing btrfs cluster, and a drive that you wish to be erased and added to it.' + mount="$(get_btrfs_mount "Which mount point is the btrfs cluster that will house the new drive?")" + drive="$(get_any_drive "Which drive is the one to be erased then added into the btrfs cluster?")" - # add the drive to the mount point - eval-confirm -- sudo btrfs device add -f "$drive" "$mount" + # add the drive to the mount point + eval-helper --confirm -- sudo btrfs device add -f "$drive" "$mount" - # balance the drives of the mount point - eval-confirm -- sudo btrfs balance start --background -dconvert=raid1 -mconvert=raid1 "$mount" -} + # balance the drives of the mount point + eval-helper --confirm -- sudo btrfs balance start --background -dconvert=raid1 -mconvert=raid1 "$mount" + } -function btrfs_balance { # Resume a balance - local mount - mount="$(get_btrfs_mount "Which btrfs mount point should we resume/start a btrfs raid1 balance operation on?")" - if echo-eval -- sudo btrfs balance status -v "$mount"; then - # finished - echo-eval -- sudo btrfs balance start --background -dconvert=raid1 -mconvert=raid1 "$mount" - else - # in progress - echo-eval -- sudo btrfs balance resume "$mount" - fi -} + function act_balance { + local mount + mount="$(get_btrfs_mount "Which btrfs mount point should we resume/start a btrfs raid1 balance operation on?")" + if eval-helper -- sudo btrfs balance status -v "$mount"; then + # finished + eval-helper -- sudo btrfs balance start --background -dconvert=raid1 -mconvert=raid1 "$mount" + else + # in progress + eval-helper -- sudo btrfs balance resume "$mount" + fi + } -function btrfs_verify { # Verify the amount of btrfs drives matches the expected ount - local label="$1" expected="$2" actual - actual="$(get_btrfs_count_for_label "$label")" - if test "$actual" -ne "$expected"; then - cat <<-EOF >/dev/stderr - $(echo-style --error="$actual out of $expected drives available") - $(echo-style --notice="Try again when all drives are available.") - EOF - return 1 - fi -} - -function btrfs_mounted { - local label="$1" mount="$2" drive - drive="$(get_btrfs_drive_for_label "$label")" - is-mounted --source="$drive" --target="$mount" -} - -function btrfs_drive { - local label="${1-}" - if test -n "$label"; then - get_btrfs_drive_for_label "$label" + function act_verify { + local label="$1" expected="$2" actual + actual="$(get_btrfs_count_for_label "$label")" + if test "$actual" -ne "$expected"; then + cat <<-EOF >/dev/stderr + $(echo-style --error="$actual out of $expected drives available") + $(echo-style --notice="Try again when all drives are available.") + EOF + return 1 + fi + } + + function act_mounted { + local label="$1" mount="$2" drive + drive="$(get_btrfs_drive_for_label "$label")" + is-mounted --source="$drive" --target="$mount" + } + + function act_drive { + local label="${1-}" + if test -n "$label"; then + get_btrfs_drive_for_label "$label" + else + get_btrfs_drive 'Which btrfs drive to select?' + fi + } + + # ===================================== + # Act + + if test "$(type -t "act_$action")" = 'function'; then + "act_$action" "${args[@]}" + return "$?" else - get_btrfs_drive 'Which btrfs drive to select?' + echo-style --error="Action [$action] not yet implemented." >/dev/stderr + return 78 # Function not implemented fi -} - -# ===================================== -# Act +) -if test "$(type -t "btrfs_$action")" = 'function'; then - "btrfs_$action" "${action_args[@]}" - exit "$?" -else - echo-style --error="Action [$action] not yet implemented." >/dev/stderr - exit 78 # Function not implemented +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + btrfs-helper "$@" fi diff --git a/commands/cert b/commands/cert deleted file mode 100755 index dbb3b4874..000000000 --- a/commands/cert +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# Adjusting TLS formats - -# Vault -# https://www.vaultproject.io/api/secret/pki/index.html#generate-certificate - -# Detection -# https://serverfault.com/a/9717/63348 -# https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions -# https://stackoverflow.com/a/47765718/130638 -# https://techjourney.net/how-to-decrypt-an-enrypted-ssl-rsa-private-key-pem-key/ - -# Utilities -# https://support.ssl.com/Knowledgebase/Article/View/19/0/der-vs-crt-vs-cer-vs-pem-certificates-and-how-to-convert-them -# https://www.sslshopper.com/article-most-common-openssl-commands.html - -# Definitions -# https://en.wikipedia.org/wiki/PKCS_12 -# https://en.wikipedia.org/wiki/X.690 ber+cer+der -# https://en.wikipedia.org/wiki/X.509 - -# To convert from binary to Base64: -# certutil -encode filename.cer newfilename.cer - -# To convert from Base64 to binary: -# certutil -decode filename.cer newfilename.cer - -# openssl rsa -in ssl.key.secure -out ssl.key - -# decode csr -# openssl req -in server.csr -noout -text - -# cert + key to pkcs12 bundle -# openssl pkcs12 -export -clcerts -in consul.crt -inkey consul.key -out consul.p12 -# openssl pkcs12 -export -inkey cert_key_pem.txt -in cert_key_pem.txt -out cert_key.p12 - -# pem to der -# openssl x509 -in cert.crt -outform der -out cert.der - -# der to pem -# openssl x509 -in cert.crt -inform der -outform pem -out cert.pem - -# pkcs12 to pem -# openssl pkcs12 -in cert_key.p12 -out cert_key.pem -nodes - -# MacOS -# https://sdqali.in/blog/2012/06/05/managing-security-certificates-from-the-console---on-windows-mac-os-x-and-linux/ -# security add-certificate foo.crt -# security add-trusted-cert foo.crt -# security find-certificates -a -e foo@bar.com - -# Encryption and Decryption -# https://security.stackexchange.com/q/36358/110805 -# openssl enc -in ciphertext -out binarytext -d -a -# openssl rsautl -decrypt -in binarytext -inkey private.pem - -# Action -action="$( - choose-option \ - --question='What action would you like to do?' \ - --filter="$1" -- inspect combine import extract encrypt decrypt -)" - -# Inspect -if test "$action" = "inspect"; then - if silent-stderr openssl x509 -in "$2" -noout -text; then - echo 'is x509 certificate' - elif silent-stderr openssl rsa -in "$2" -noout -text; then - echo 'is rsa private key' - elif silent-stderr openssl rsa -pubin -in "$2" -noout -text; then - echo 'is rsa public key' - elif silent-stderr openssl req -in "$2" -noout -text; then - echo 'is certificate signing request' - elif silent-stderr openssl pkcs12 -info -in "$2"; then - echo 'is pkcs#12 bundle' - fi -fi - -# Encrypt -if is-either "$action" "combine" "bundle"; then - key_file="$( - ask --required --confirm \ - --question="Specify the private key file (.key)" \ - --default="${2-}" - )" - cert_file="$( - ask --required --confirm \ - --question="Specify the certificate file (.crt)" \ - --default="${3-}" - )" - ca_file="$( - ask --required --confirm \ - --question="Specify the certificate authority file (.ca)" \ - --default="${4-}" - )" - bundle_file="$( - ask --required --confirm \ - --question="Specify the output bundle file (.p12)" \ - --default="${5-}" - )" - pass="$( - ask --required --confirm --password \ - --question="Specify the password to use" \ - --default="${6-}" - )" - - if test -f "$bundle_file"; then - rm "$bundle_file" - fi - - # -nokeys don't output private keys. - # -nodes don't encrypt private keys - # consul requres private+cert+ca combo - openssl pkcs12 -export -CAfile "$ca_file" -inkey "$key_file" -in "$cert_file" -password "pass:$pass" -out "$bundle_file" - if confirm-positive --ppid=$$ -- "Would you like to import this bundle?"; then - cert import "$bundle_file" "$cert_file" "$ca_file" "$pass" - fi -fi - -# Import into keychain -if test "$action" = "import"; then - bundle_file="$( - ask --required --confirm \ - --question="Specify the bundle file (.p12)" \ - --default="${2-}" - )" - cert_file="$( - ask --required --confirm \ - --question="Specify the certificate file (.crt)" \ - --default="${3-}" - )" - ca_file="$( - ask --required --confirm \ - --question="Specify the certificate authority file (.ca)" \ - --default="${4-}" - )" - pass="$( - ask --required --confirm --password \ - --question="Specify the password to use" \ - --default="${5-}" - )" - - security import "$bundle_file" -P "$pass" - security add-trusted-cert -d -r trustAsRoot "$cert_file" - security add-trusted-cert -d -r trustAsRoot "$ca_file" -fi - -# Extract -if test "$action" = "extract"; then - cert_file="$( - ask --required --confirm \ - --question="Specify the certificate file (.crt)" \ - --default="${2-}" - )" - openssl x509 -in "$cert_file" -pubkey -noout -fi - -# Encrypt -if test "$action" = "encrypt"; then - key_type=""$( - choose-option --required --confirm \ - --question='Specify the key type' \ - --filter="${2-}" - ) - key_file="$( - ask --required --confirm \ - --question="Specify the key file" \ - --default="${3-}" - )" - - if test "$key_type" = "public"; then - openssl rsautl -encrypt -inkey "$key_file" -pubin - elif test "$key_type" = "private"; then - openssl rsautl -encrypt -inkey "$key_file" - fi -fi - -# Decrypt -if test "$action" = "decrypt"; then - key_type=""$( - choose-option --required --confirm \ - --question='Specify the key type' \ - --filter="${2-}" - ) - key_file="$( - ask --required --confirm \ - --question="Specify the key file" \ - --default="${3-}" - )" - - if test "$key_type" = "public"; then - openssl rsautl -decrypt -inkey "$key_file" -pubin - elif test "$key_type" = "private"; then - openssl rsautl -decrypt -inkey "$key_file" - fi -fi diff --git a/commands/checksum b/commands/checksum index d08020d68..c58d39113 100755 --- a/commands/checksum +++ b/commands/checksum @@ -3,100 +3,153 @@ source "$DOROTHY/sources/strict.bash" source "$DOROTHY/sources/arrays.bash" requires_array_support 'mapfile' 'empty' -# dependencies -env QUIET=yes setup-util-pv - -# algorithms -algorithms=() -for alg in 'md5sum' 'shasum' 'sha256sum'; do - if command-exists "$alg"; then - algorithms+=("$alg") - fi -done - -# help -if is-help-separator "$@"; then - cat <<-EOF >/dev/stderr - ABOUT: - Get the checksum for a path, with progress updates if it takes a while. - - USAGE: - checksum [...flags] -- # checksums the current directory - checksum [...flags] -- <...path> # checksums each path provided - - FLAGS: - Provide [--summary] to summarise a directory as a single checksum. - Provide [--relative] to use relative paths instead of absolute paths. - Provide [--untitled] to not display the path that the checksums are for. - Provide [--algorithm=...] to specify a filter to select the checksum algorithm by. - - ALGORITHMS: - We have determined the following algorithms are available on your system: - "${algorithms[*]}" \ - - QUIRKS: - [--relative] does not respect pwd, as such [--relative] is only useful when in [--no-summary] mode. - EOF - exit 22 # Invalid argument -fi - -# options -mapfile -t options < <(echo-before-separator "$@") -option_relative="$(get-flag-value relative --missing=no -- "${options[@]}" | echo-affirmative)" -option_untitled="$(get-flag-value untitled --missing=no -- "${options[@]}" | echo-affirmative)" -option_summary="$(get-flag-value summary --missing=no -- "${options[@]}" | echo-affirmative)" -option_algorithm="$(get-flag-value algorithm -- "${options[@]}")" -option_algorithm="$( - choose-option --required \ - --question='Which checksum algorithm do you wish to use?' \ - --filter="$option_algorithm" -- "${algorithms[@]}" -)" -mapfile -t option_paths < <(echo-after-separator "$@") - -# process -paths=() -if test "${#option_paths[@]}" -eq 0; then - paths+=("$(pwd)") -else - for arg in "${option_paths[@]}"; do - paths+=("$(fs-absolute "$arg")") +function checksum() ( + local item algorithms=() + for item in 'md5sum' 'shasum' 'sha256sum'; do + if command-exists "$item"; then + algorithms+=("$item") + fi done -fi -# helpers -function do_checksum_of_stdin_with_filename { - local path="$1" relative="${2:-"$option_relative"}" - if test "$relative" = 'yes'; then - path="$(basename "$path")" - fi - "$option_algorithm" | { - IFS=' ' read -ra hash_dot - if test "$option_untitled" = 'yes'; then - echo "${hash_dot[0]}" - else - echo "${hash_dot[0]} $path" + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Get the checksum for a path, with progress updates if it takes a while. + + USAGE: + checksum [...options] + Checksums the current directory. + + checksum [...options] -- <...paths> + Checksums each path provided. + + OPTIONS: + --summary + Summarises the directory as a single checksum. + + --relative + Use relative paths instead of absolute paths. + + --untitled + Do not display the path that the checksums are for. + + --algorithm= + Forces a specific algorithm to be used. + + ALGORITHMS: + We have determined the following algorithms are available on your system: + $(echo-lines --indent=' ' "${algorithms[@]}") + + QUIRKS: + [--relative] does not respect pwd, as such [--relative] is only useful when in [--no-summary] mode. + EOF + if test "$#" -ne 0; then + echo-error "$@" fi + return 22 # Invalid argument } -} -function do_checksum_of_file_or_directory { - # shows progress, works on files, and directories, and symlinks, and expands paths - # -L show symlinks too - local path="$1" relative="${2:-"$option_relative"}" file - find -L "$path" -type f | sort | while read -r file; do - pv --delay-start 5 "$file" | do_checksum_of_stdin_with_filename "$file" "$relative" + + # process + local item path paths=() relative='no' untitled='no' summary='no' algorithm='' + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-relative'* | '--relative'*) + relative="$(get-flag-value relative --missing="$relative" -- "$item" | echo-affirmative)" + ;; + '--no-untitled'* | '--untitled'*) + untitled="$(get-flag-value untitled --missing="$untitled" -- "$item" | echo-affirmative)" + ;; + '--no-summary'* | '--summary'*) + summary="$(get-flag-value summary --missing="$summary" -- "$item" | echo-affirmative)" + ;; + '--algorithm='*) algorithm="${item:12}" ;; + '--') + for path in "$@"; do + _paths+=("$(fs-absolute "$path")") + done + shift "$#" + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) help "An unrecognised argument was provided: $item" ;; + esac done -} - -# act -for path in "${paths[@]}"; do - if test -f "$path" -o "$option_summary" != 'yes'; then - # file, or itemise - do_checksum_of_file_or_directory "$path" - else - # directory and summarise - # force --relative, as otherwise full paths in checksums will ruin comparison - f="$(mktemp)" - do_checksum_of_file_or_directory "$path" 'yes' | tee "$f" | do_checksum_of_stdin_with_filename "$path" - rm "$f" + + # ensure algorithm + algorithm="$( + choose-option --required \ + --question='Which checksum algorithm do you wish to use?' \ + --filter="$algorithm" -- "${algorithms[@]}" + )" + + # ensure paths + if test "${#_paths[@]}" -eq 0; then + paths+=("$(pwd)") fi -done + + # ===================================== + # Dependencies + + env QUIET=yes setup-util-pv + + # ===================================== + # Action + + # helpers + function do_checksum_of_stdin_with_filename { + local path="$1" use_relative="$2" hash_dot + + # relative + if test "$use_relative" = 'yes'; then + path="$(basename "$path")" + fi + + # algorithm + "$algorithm" | { + IFS=' ' read -ra hash_dot + if test "$untitled" = 'yes'; then + echo "${hash_dot[0]}" + else + echo "${hash_dot[0]} $path" + fi + } + } + function do_checksum_of_file_or_directory { + local path="$1" use_relative="$2" file + + # shows progress, works on files, and directories, and symlinks, and expands paths + # -L show symlinks too + find -L "$path" -type f | sort | while read -r file; do + pv --delay-start 5 "$file" | do_checksum_of_stdin_with_filename "$file" "$use_relative" + done + } + function do_checksum_of_paths { + local path temp_file use_relative="${2:-"$relative"}" + for path in "$@"; do + if test -f "$path" -o "$summary" != 'yes'; then + # file, or itemise + do_checksum_of_file_or_directory "$path" "$use_relative" + else + # directory and summarise + # force --relative, as otherwise full paths in checksums will ruin comparison + temp_file="$(mktemp)" + do_checksum_of_file_or_directory "$path" 'yes' | tee "$temp_file" | do_checksum_of_stdin_with_filename "$path" "$use_relative" + rm "$temp_file" + fi + done + } + + # act + do_checksum_of_paths "${paths[@]}" +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + checksum "$@" +fi diff --git a/commands/choose b/commands/choose deleted file mode 100755 index 3e44d89ab..000000000 --- a/commands/choose +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -source "$DOROTHY/sources/strict.bash" - -# you probably don't want to use this -# you probably want ask, or choose-option - -# timeout -# pid="$BASHPID" -# timeout="${TIMEOUT:-3600}" # timeout one hour -# ( sleep "$timeout" && kill "$pid" ) & -# ^ this works for timeout, but prevents the select from returning - -# choose -select key in "$@"; do - test -n "$key" && break -done >/dev/tty - -# send result -echo "$key" diff --git a/commands/choose-menu b/commands/choose-menu index e82712160..91d89f3f6 100755 --- a/commands/choose-menu +++ b/commands/choose-menu @@ -1,216 +1,238 @@ #!/usr/bin/env bash source "$DOROTHY/sources/strict.bash" source "$DOROTHY/sources/tty.bash" +source "$DOROTHY/sources/arrays.bash" +requires_array_support 'mapfile' # NOTES: -# - [--default] and [--required] do not make sense here, as those are for high-level handling, this is just menu navigation +# - [--required] do not make sense here, as those are for high-level handling, this is just menu navigation # QUIRKS: # - if there are more options than [$LINES - header], then this will fall apart # TODOS: -# - [ ] adopt modern arg and help and invalid arg handling # - [ ] limit the options output to [$LINES - header] # - [ ] if one gets to $LINES, and there are truncated values, then "scroll" downwards # - [ ] support $COLUMNS - if a menu item is larger than the column, then it will show all of it when active -# validate -if is-help-separator "$@"; then - cat <<-EOF >/dev/stderr - ABOUT: - Display a menu that the user can navigate using the keyboard. +function choose-menu() ( + # ===================================== + # Arguments - USAGE: - choose-menu [...flags] -- <...items> + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Display a menu that the user can navigate using the keyboard. - RETURNS: - the index of the result + USAGE: + choose-menu [...options] -- ... - FLAGS: - Provide [--question=...] to specify the question that the prompt will be answering. - Provide [--multi] to specify that multiple menu items should be able to be selected. - Provide [--timeout=...] to specify a custom timeout value in seconds. - EOF - exit 22 # Invalid argument -fi + RETURNS: + The index of the result -# now that we have ensured separator exists, we don't need empty, only mapfile -source "$DOROTHY/sources/arrays.bash" -requires_array_support 'mapfile' + OPTIONS: + --question= + Question to display as the prompt. -# fetch options in a way that works with multi-line values -options=() -while [ $# -gt 0 ]; do - if [ "$1" = '--' ]; then - shift - break - fi - options+=("$1") - shift -done -option_question="$(get-flag-value question -- "${options[@]}")" -option_timeout="$(get-flag-value timeout -- "${options[@]}")" -option_multi="$(get-flag-value multi --missing=no -- "${options[@]}" | echo-affirmative)" -option_required="$(get-flag-value required --missing=no -- "${options[@]}" | echo-affirmative)" - -# prepare choices -choices=("$@") - -# ensure we have items -if test "${#choices[@]}" -eq 0; then - { - echo-style --error="No items were provided. See [$0 --help] for usage." - sleep 5 - exit 22 # Invalid argument - } >/dev/stderr -elif is-array-partial "${choices[@]}"; then - { - echo-style --error="Empty items were provided. See [$0 --help] for usage." - echo-verbose "${choices[@]}" - sleep 5 - exit 22 # Invalid argument - } >/dev/stderr -fi - -# prepare -tty_auto -cursor=0 -count="${#choices[@]}" -last="$((count - 1))" -action='' -mapfile -t selections < <(get-array "$count") - -# commence -while test "$action" != 'done'; do - # question - if test -n "$option_question"; then - echo "$option_question" >/dev/tty - fi + --multi + Multiple menu items can be selected. - # show the menu - for i in "${!choices[@]}"; do - if test "$i" -eq "$cursor"; then - echo -n ">" >/dev/tty - else - echo -n ' ' >/dev/tty + --timeout= + Custom timeout value in seconds. + EOF + if test "$#" -ne 0; then + echo-error "$@" fi - if test "${selections[$i]-}" = 'yes'; then - echo -n "*" >/dev/tty - else - echo -n ' ' >/dev/tty - fi - choice="${choices[$i]}" - echo "$choice" >/dev/tty + return 22 # Invalid argument + } + + # process + local item='' items=() option_question='' option_timeout='' option_multi='no' option_required='no' + while test "$#" -ne 0; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--question='*) option_question="${item:11}" ;; + '--timeout='*) option_timeout="${item:10}" ;; + '--no-multi'* | '--multi'*) option_multi="$( + get-flag-value multi --missing="$option_multi" -- "$item" | echo-affirmative + )" ;; + '--no-required'* | '--required'*) option_required="$( + get-flag-value required --missing="$option_required" -- "$item" | echo-affirmative + )" ;; + '--') + items+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) help "An unrecognised argument was provided: $item" ;; + esac done - # handle the response - ec=0 && action="$(read-arrow --timeout="$option_timeout")" || ec="$?" - - # check - if test "$ec" -eq 62; then - echo "Input timed out [$ec]." >/dev/stderr - sleep 5 - exit "$ec" - elif test "$ec" -ne 0; then - echo "Input failed [$ec]." >/dev/stderr - # some other failure - sleep 5 - exit "$ec" + # ensure we have items + if test "${#items[@]}" -eq 0; then + { + echo-style --error="No options were provided. See [$0 --help] for usage." + sleep 5 + exit 22 # Invalid argument + } >/dev/stderr + elif is-array-partial "${items[@]}"; then + { + echo-style --error="Empty options were provided. See [$0 --help] for usage." + echo-verbose "${items[@]}" + sleep 5 + exit 22 # Invalid argument + } >/dev/stderr fi - # handle special cases and remaps - # such as numbers, wasd, and vim movers - if is-digit "$action"; then - # number jump - if test "$action" -le 1; then - cursor=0 - elif test "$action" -le "$count"; then - cursor="$((action - 1))" - else - cursor="$last" + # prepare + local count last selections=() + count="${#items[@]}" + last="$((count - 1))" + mapfile -t selections < <(get-array "$count") + + # commence + tty_auto + local index choice='' ec=0 cursor=0 action='' + while test "$action" != 'done'; do + # question + if test -n "$option_question"; then + echo "$option_question" >/dev/tty fi - action='space' - elif test "$action" = "left" -o "$action" = "h" -o "$action" = "k" -o "$action" = "a" -o "$action" = "w"; then - action='up' - elif test "$action" = "right" -o "$action" = "l" -o "$action" = "j" -o "$action" = "d" -o "$action" = "s"; then - action='down' - fi - # control key - if test "$action" = "up"; then - if test "$cursor" -ne 0; then - cursor="$((cursor - 1))" + # show the menu + for index in "${!items[@]}"; do + if test "$index" -eq "$cursor"; then + echo -n ">" >/dev/tty + else + echo -n ' ' >/dev/tty + fi + if test "${selections[index]-}" = 'yes'; then + echo -n "*" >/dev/tty + else + echo -n ' ' >/dev/tty + fi + choice="${items[index]}" + echo "$choice" >/dev/tty + done + + # handle the response + ec=0 && action="$(read-key --timeout="$option_timeout")" || ec="$?" + + # check + if test "$ec" -eq 62; then + echo "Input timed out [$ec]." >/dev/stderr + sleep 5 + exit "$ec" + elif test "$ec" -ne 0; then + echo "Input failed [$ec]." >/dev/stderr + # some other failure + sleep 5 + exit "$ec" fi - elif test "$action" = "down"; then - if test "$cursor" -ne "$last"; then - cursor="$((cursor + 1))" + + # handle special cases and remaps + # such as numbers, wasd, and vim movers + if is-digit "$action"; then + # number jump + if test "$action" -le 1; then + cursor=0 + elif test "$action" -le "$count"; then + cursor="$((action - 1))" + else + cursor="$last" + fi + action='space' + elif test "$action" = "left" -o "$action" = "h" -o "$action" = "k" -o "$action" = "a" -o "$action" = "w"; then + action='up' + elif test "$action" = "right" -o "$action" = "l" -o "$action" = "j" -o "$action" = "d" -o "$action" = "s"; then + action='down' fi - elif test "$action" = "home"; then - cursor=0 - elif test "$action" = "end"; then - cursor="$last" - elif test "$action" = "backspace"; then - # unselect everything - for i in "${!choices[@]}"; do - selections[$i]='' - done - elif test "$action" = "all" -a "$option_multi" = 'yes'; then - # select everything - for i in "${!choices[@]}"; do - selections[$i]='yes' - done - elif test "$action" = 'tab'; then - # select and move to next line - selections[$cursor]='yes' - if test "$cursor" -eq "$last"; then + + # control key + if test "$action" = "up"; then + if test "$cursor" -ne 0; then + cursor="$((cursor - 1))" + fi + elif test "$action" = "down"; then + if test "$cursor" -ne "$last"; then + cursor="$((cursor + 1))" + fi + elif test "$action" = "home"; then cursor=0 - elif test "$cursor" -lt "$last"; then - cursor="$((cursor + 1))" - fi - elif test "$action" = 'space'; then - # toggle single - if test "${selections[$cursor]}" = 'yes'; then - selections[$cursor]='' - else - selections[$cursor]='yes' + elif test "$action" = "end"; then + cursor="$last" + elif test "$action" = "backspace"; then + # unselect everything + for index in "${!items[@]}"; do + selections[index]='' + done + elif test "$action" = "all" -a "$option_multi" = 'yes'; then + # select everything + for index in "${!items[@]}"; do + selections[index]='yes' + done + elif test "$action" = 'tab'; then + # select and move to next line + selections[cursor]='yes' + if test "$cursor" -eq "$last"; then + cursor=0 + elif test "$cursor" -lt "$last"; then + cursor="$((cursor + 1))" + fi + elif test "$action" = 'space'; then + # toggle single + if test "${selections[cursor]}" = 'yes'; then + selections[cursor]='' + else + selections[cursor]='yes' + if test "$option_multi" != 'yes'; then + break + fi + fi + elif test "$action" = "enter"; then if test "$option_multi" != 'yes'; then - break + selections[cursor]='yes' fi - fi - elif test "$action" = "enter"; then - if test "$option_multi" != 'yes'; then - selections[$cursor]='yes' - fi - break - elif test "$action" = "escape"; then - # todo implement --required with --multi fallback properly here - if test "$option_required" = 'no'; then break + elif test "$action" = "escape"; then + # todo implement --required with --multi fallback properly here + if test "$option_required" = 'no'; then + break + fi fi - fi - # no break, so repeat the menu - tty_clear -done - -# if break, then no clear occured, so clear it here -tty_clear + # no break, so repeat the menu + tty_clear + done -# if multi with no selection then ask for everything -# todo implement --required properly here -if is-array-empty "${selections[@]}" && test "$option_multi" = 'yes'; then - if confirm-positive --ppid=$$ -- "You exited without selecting anything, do you wish to select all?" >/dev/tty; then - for i in "${!choices[@]}"; do - selections[$i]='yes' - done - fi + # if break, then no clear occured, so clear it here tty_clear -fi -# output the custom selections -for i in "${!selections[@]}"; do - selection="${selections[$i]}" - if test "$selection" = 'yes'; then - echo "$i" + # if multi with no selection then ask for everything + # todo implement --required properly here + if is-array-empty "${selections[@]}" && test "$option_multi" = 'yes'; then + if confirm-positive --ppid=$$ -- "You exited without selecting anything, do you wish to select all?" >/dev/tty; then + for index in "${!items[@]}"; do + selections[index]='yes' + done + fi + tty_clear fi -done + + # output the custom selections + local index selection + for index in "${!selections[@]}"; do + selection="${selections[index]}" + if test "$selection" = 'yes'; then + echo "$index" + fi + done +) + +# fire if invoked standalone +if test "$0" = "${BASH_SOURCE[0]}"; then + choose-menu "$@" +fi diff --git a/commands/choose-option b/commands/choose-option index 94319760e..50794b03e 100755 --- a/commands/choose-option +++ b/commands/choose-option @@ -4,429 +4,433 @@ source "$DOROTHY/sources/shims.bash" source "$DOROTHY/sources/arrays.bash" may_require_array_support 'mapfile' 'empty' -# ===================================== -# Arguments - -# help -function help() { - cat <<-EOF >/dev/stderr - ABOUT: - Prompt the user to select a value from a list of values, in a clean and robust way. - - USAGE: - choose-option - [--question=] - [--filter=] - [--label[=first]] - [--return='\$label'] - [--visual='\$label [\$value]'] - [--required] - [--multi] - [--confirm] - -- <[value]...> - - If you wish to show a question above the menu: - --question= -- <...> - - If you wish to filter the value and/or labels, use: - --filter= -- <...> - - If you wish to prevent using the escape key to provide no selection, use: - --required -- <...> - - If you wish to allow multiple selections: - --multi -- <...> - - If you wish to confirm a possibly arbitary selection, use: - --confirm -- <...> - - If you wish to pass values and labels, use: - --label -- <[value, label]...>