diff --git a/.github/workflows/dorothy-workflow.yml b/.github/workflows/dorothy-workflow.yml index f498cc016..248db21ba 100644 --- a/.github/workflows/dorothy-workflow.yml +++ b/.github/workflows/dorothy-workflow.yml @@ -163,7 +163,7 @@ jobs: strategy: fail-fast: false matrix: - runner: [ubuntu-24.04, ubuntu-22.04, macos-15, macos-14, macos-12] + runner: [ubuntu-24.04, ubuntu-22.04, macos-15, macos-14, macos-13] # ubuntu-20.04 not supported, echo-wait fails: https://github.com/bevry/dorothy/actions/runs/9705310169/job/26787151094#step:2:1346 runs-on: ${{ matrix.runner }} steps: @@ -178,7 +178,7 @@ jobs: strategy: fail-fast: false matrix: - runner: [macos-15, macos-14, macos-12] + runner: [macos-15, macos-14, macos-13] runs-on: ${{ matrix.runner }} steps: - name: 'Uninstall Homebrew' @@ -235,17 +235,16 @@ jobs: elif command -v urpmi; then # for mageia, prefer over fedora as mageia also contains dnf # https://wiki.mageia.org/en/Cauldron - # --allowerasing needed due to: https://github.com/bevry/dorothy/actions/runs/6033044029/job/16369147940 + # https://github.com/bevry/dorothy/actions/runs/6033044029/job/16369147940 + # https://github.com/bevry/dorothy/actions/runs/6033557632/job/16370418074 # urpmi --auto-update --auto - # dnf check-update --assumeyes - # dnf upgrade --assumeyes --refresh --best --allowerasing - # ^ disabled as always fails urpmi.update -a - # ^ this fails too: https://github.com/bevry/dorothy/actions/runs/6033557632/job/16370418074 urpmi --auto bash curl elif command -v dnf; then # for fedora - dnf --assumeyes --refresh --best --allowerasing install bash curl + # dnf check-update --assumeyes + # dnf upgrade --assumeyes --refresh --best --allowerasing + dnf install --assumeyes --refresh --best --allowerasing bash curl elif command -v xbps-install; then # for void linux xbps-install --sync --update --yes xbps diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 941acbb08..3127254d5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,14 +1,15 @@ # https://features.trunk.io/check/p/trunk-doesnt-work-in-nested-repo version: 0.1 cli: - version: 1.21.0 + version: 1.22.8 plugins: sources: - id: trunk - ref: v1.4.5 + ref: v1.6.5 uri: https://github.com/trunk-io/plugins runtimes: enabled: + - node@18.12.1 - go@1.21.0 - python@3.10.8 # - node@18.12.1 <-- there's only a single Node.js file: echo-url-coder.js @@ -20,20 +21,22 @@ actions: - trunk-upgrade-available lint: enabled: - - actionlint@1.6.27 - - black@24.3.0 - - checkov@3.2.48 - - flake8@7.0.0 + - cspell@8.16.0 + - bandit@1.7.10 + - actionlint@1.7.4 + - black@24.10.0 + - checkov@3.2.314 + - flake8@7.1.1 - git-diff-check@SYSTEM - - gitleaks@8.18.2 + - gitleaks@8.21.2 - isort@5.13.2 - - markdownlint@0.39.0 - - prettier@3.2.5 - - ruff@0.3.4 + - markdownlint@0.43.0 + - prettier@3.4.0 + - ruff@0.8.0 - shellcheck@0.10.0 - shfmt@3.7.0 - - taplo@0.8.1 - - trufflehog@3.71.0 + - taplo@0.9.3 + - trufflehog@3.84.1 - yamllint@1.35.1 disabled: - eslint # it can't find the bevry config extensions for some reason diff --git a/.vscode/workspace.code-workspace b/.vscode/workspace.code-workspace index 46e12af1d..38e536ec5 100644 --- a/.vscode/workspace.code-workspace +++ b/.vscode/workspace.code-workspace @@ -10,25 +10,8 @@ } ], "settings": { - "cSpell.words": [ - "balupton", - "Bash", - "Builtins", - "doas", - "dotfile", - "dotfiles", - "Mageia", - "molleweide", - "pacman", - "POSIX", - "Roadmap", - "Subshells", - "sumitrai", - "xbps", - "Xonsh", - "Zsh", - "Bevry" - ], - "deno.enable": true + "prettier.enable": false, + "cSpell.enabled": true, + "deno.enable": true } } diff --git a/LICENSE.md b/LICENSE.md index a07b6fa0c..6046fc2b0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -4,11 +4,11 @@ Unless stated otherwise all works are: -- Copyright © [Benjamin Lupton](https://balupton.com) +- Copyright © [Benjamin Lupton](https://balupton.com) and licensed under: -- [Reciprocal Public License 1.5](http://spdx.org/licenses/RPL-1.5.html) +- [Reciprocal Public License 1.5](http://spdx.org/licenses/RPL-1.5.html) ## Reciprocal Public License 1.5 (RPL1.5) diff --git a/README.md b/README.md index c81df16ca..1e2011285 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,15 @@ Dorothy is a dotfile ecosystem featuring: -- 🐚 seamless support for [Bash](), [Zsh](https://en.wikipedia.org/wiki/Z_shell), [Fish](), [Nu](https://www.nushell.sh), [Xonsh](https://xon.sh), [Elvish](https://elv.sh), [Dash](https://wiki.archlinux.org/title/Dash), [KSH](https://en.wikipedia.org/wiki/KornShell) -- 🩻 seamless support for multiple operating systems and architectures -- πŸ‘©β€πŸ’» seamless support for your favorite terminal and GUI editors -- 🦾 automatic configuration of your environment variables for what you have installed on your system -- πŸ“¦ automatic installation and updating of your specified packages -- 🌳 automatic Git, SSH, and GPG configuration based on what your system supports and your configuration -- β˜„οΈ hundreds of [commands](https://github.com/bevry/dorothy/tree/master/commands) to improve your productivity -- βš™οΈ completely extensible and configurable with your own user repository -- πŸ¦Έβ€β™€οΈ all this together, allows you to go from zero to hero within minutes, instead of days, on a brand new machine +- 🐚 seamless support for [Bash](), [Zsh](https://en.wikipedia.org/wiki/Z_shell), [Fish](), [Nu](https://www.nushell.sh), [Xonsh](https://xon.sh), [Elvish](https://elv.sh), [Dash](https://wiki.archlinux.org/title/Dash), [KSH](https://en.wikipedia.org/wiki/KornShell) +- 🩻 seamless support for multiple operating systems and architectures +- πŸ‘©β€πŸ’» seamless support for your favorite terminal and GUI editors +- 🦾 automatic configuration of your environment variables for what you have installed on your system +- πŸ“¦ automatic installation and updating of your specified packages +- 🌳 automatic Git, SSH, and GPG configuration based on what your system supports and your configuration +- β˜„οΈ hundreds of [commands](https://github.com/bevry/dorothy/tree/master/commands) to improve your productivity +- βš™οΈ completely extensible and configurable with your own user repository +- πŸ¦Έβ€β™€οΈ all this together, allows you to go from zero to hero within minutes, instead of days, on a brand new machine ## Introduction @@ -157,9 +157,9 @@ xbps-install --sync bash curl Now that the prerequisites are installed, Dorothy's intelligent dependency management will be enabled, so you can skip this section. Dorothy's complete requisites for its core experience are as follows: -- [`bash`](https://release-monitoring.org/project/166/), [`curl`](https://release-monitoring.org/project/381/): required for initiation -- [`grep`](https://release-monitoring.org/project/1251/), [`git`](https://git-scm.com/downloads), [`awk`](https://release-monitoring.org/project/868/): required for installation -- [`jq`](https://jqlang.github.io/jq/download/), [`deno`](https://deno.com/#installation): required for advanced configuration and regular expression processing +- [`bash`](https://release-monitoring.org/project/166/), [`curl`](https://release-monitoring.org/project/381/): required for initiation +- [`grep`](https://release-monitoring.org/project/1251/), [`git`](https://git-scm.com/downloads), [`awk`](https://release-monitoring.org/project/868/): required for installation +- [`jq`](https://jqlang.github.io/jq/download/), [`deno`](https://deno.com/#installation): required for advanced configuration and regular expression processing If the automated installation of any failed, [post an issue](https://github.com/bevry/dorothy/issues) including details of your environment, and use their links for alternative installation methods. If you are downloading their binaries straight from GitHub, you can unzip with `tar -xvf `, make a discoverable binary directory with `mkdir -p ~/.local/bin`, move the binary there with `mv ~/.local/bin`, and make the binaries executable with `chmod +x ~/.local/bin/*`. @@ -217,9 +217,9 @@ If packages are failing to install, [go back to the "Prerequisites" section](htt If your shell doesn't recognize any of the Dorothy commands (you get a command not found error, or an undefined/unbound variable error), then it could be that: -- Your shell is not running as a login shell. [Verify that your Terminal is running the shell as a login shell.](https://github.com/bevry/dorothy/blob/master/docs/dorothy/dorothy-not-loading.md) -- Dorothy did not configure itself for the shell you use. Re-run the Dorothy installation process, and be sure to configure Dorothy for your shell. -- Your login shell is not one of the Dorothy supported shells. [Create an issue requesting support for your shell.](https://github.com/bevry/dorothy/issues) +- Your shell is not running as a login shell. [Verify that your Terminal is running the shell as a login shell.](https://github.com/bevry/dorothy/blob/master/docs/dorothy/dorothy-not-loading.md) +- Dorothy did not configure itself for the shell you use. Re-run the Dorothy installation process, and be sure to configure Dorothy for your shell. +- Your login shell is not one of the Dorothy supported shells. [Create an issue requesting support for your shell.](https://github.com/bevry/dorothy/issues) If you see unrecognised symbols, you probably require fonts. Once Dorothy is loaded, run `setup-util-noto-emoji` which installs [Noto Emoji](https://github.com/googlefonts/noto-emoji), a font for enabling emojis inside your terminal. For rendering glyphs, run `setup-util-nerd-fonts` which will prompt you for which [Nerd Font](https://www.nerdfonts.com/font-downloads) to install. You may need to update your terminal preferences to leverage these installed fonts. @@ -229,22 +229,22 @@ If you see unrecognised symbols, you probably require fonts. Once Dorothy is loa Dorothy installs itself to `$DOROTHY`, which defaults to the [XDG](https://wiki.archlinux.org/title/XDG_Base_Directory) location of `~/.local/share/dorothy`, and consists of the following: -- [`commands` directory](https://github.com/bevry/dorothy/tree/master/commands) contains executable commands of super-stable quality, they are actively used within the Dorothy core and by the users of Dorothy. -- [`commands.beta` directory](https://github.com/bevry/dorothy/tree/master/commands.beta) contains executable commands of beta quality, these are commands that require more usage or possible breaking changes before promotion to `commands`. -- [`config` directory](https://github.com/bevry/dorothy/tree/master/config) contains default configuration -- [`sources` directory](https://github.com/bevry/dorothy/tree/master/sources) contains scripts that are loaded into the shell environment -- [`themes` directory](https://github.com/bevry/dorothy/tree/master/themes) contains themes that you can select via the `DOROTHY_THEME` environment variable -- [`user` directory](https://github.com/balupton/dotfiles) is your own github repository for your custom configuration +- [`commands` directory](https://github.com/bevry/dorothy/tree/master/commands) contains executable commands of super-stable quality, they are actively used within the Dorothy core and by the users of Dorothy. +- [`commands.beta` directory](https://github.com/bevry/dorothy/tree/master/commands.beta) contains executable commands of beta quality, these are commands that require more usage or possible breaking changes before promotion to `commands`. +- [`config` directory](https://github.com/bevry/dorothy/tree/master/config) contains default configuration +- [`sources` directory](https://github.com/bevry/dorothy/tree/master/sources) contains scripts that are loaded into the shell environment +- [`themes` directory](https://github.com/bevry/dorothy/tree/master/themes) contains themes that you can select via the `DOROTHY_THEME` environment variable +- [`user` directory](https://github.com/balupton/dotfiles) is your own github repository for your custom configuration For each shell that you configured during the Dorothy installation (can be reconfigured via the `dorothy install` command), the configured shell performs the following steps when you open a new shell instance via your terminal: 1. The shell loads Dorothy's initialization script: - - [Elvish](https://elv.sh) loads our [`init.elv`](https://github.com/bevry/dorothy/blob/master/init.elv) script - - [Fish]() loads our [`init.fish`](https://github.com/bevry/dorothy/blob/master/init.fish) script - - [Nu](https://www.nushell.sh) loads our [`init.nu`](https://github.com/bevry/dorothy/blob/master/init.nu) script - - [Xonsh](https://xon.sh) loads our [`init.xsh`](https://github.com/bevry/dorothy/blob/master/init.xsh) script - - POSIX shells ([Bash](), [Zsh](https://en.wikipedia.org/wiki/Z_shell), [Dash](https://wiki.archlinux.org/title/Dash), [KSH](https://en.wikipedia.org/wiki/KornShell), etc) load our [`init.sh`](https://github.com/bevry/dorothy/blob/master/init.sh) script + - [Elvish](https://elv.sh) loads our [`init.elv`](https://github.com/bevry/dorothy/blob/master/init.elv) script + - [Fish]() loads our [`init.fish`](https://github.com/bevry/dorothy/blob/master/init.fish) script + - [Nu](https://www.nushell.sh) loads our [`init.nu`](https://github.com/bevry/dorothy/blob/master/init.nu) script + - [Xonsh](https://xon.sh) loads our [`init.xsh`](https://github.com/bevry/dorothy/blob/master/init.xsh) script + - POSIX shells ([Bash](), [Zsh](https://en.wikipedia.org/wiki/Z_shell), [Dash](https://wiki.archlinux.org/title/Dash), [KSH](https://en.wikipedia.org/wiki/KornShell), etc) load our [`init.sh`](https://github.com/bevry/dorothy/blob/master/init.sh) script 1. The initialization script will: @@ -276,10 +276,10 @@ This is the foundation that enables Dorothy's hundreds of commands to work acros Your user configuration goes to the XDG location of `~/.local/config/dorothy` which Dorothy symlinks to `~/.local/share/dorothy/user`, your user configuration consists of the following: -- `commands` directory, for public commands -- `commands.local` directory, for private commands (git ignored by default) -- `config` directory, for public configuration -- `config.local` directory, for private configuration (git ignored by default) +- `commands` directory, for public commands +- `commands.local` directory, for private commands (git ignored by default) +- `config` directory, for public configuration +- `config.local` directory, for private configuration (git ignored by default) The order of preference within Dorothy is `(commands|config).local` first, then `(commands|config)`, then Dorothy's own `(commands|config)` then everything else. @@ -289,67 +289,67 @@ You can find the various configuration files that are available to you by browsi Use these sources to find inspiration for your own user commands and configuration. -- [Dorothy's `commands` directory](https://github.com/bevry/dorothy/tree/master/commands) for super-stable commands with up to date conventions. -- [Dorothy's `commands.beta` directory](https://github.com/bevry/dorothy/tree/master/commands.beta) for beta-quality commands with possibly outdated conventions. -- [Dorothy's `docs` directory](https://github.com/bevry/dorothy/tree/master/docs) containing tips and tricks for Dorothy, and various shells, such as [enabling private user configurations](https://github.com/bevry/dorothy/blob/master/docs/dorothy/private-configuration.md), and the [manual to assisted](https://github.com/bevry/dorothy/blob/master/docs/dorothy/manual-to-assisted.md) philosophy of Dorothy -- Dorothy User Configurations: - - [@balupton](https://github.com/balupton) / [dotfiles](https://github.com/balupton/dotfiles): uses Fish as login shell, plenty of Bash commands - - [@molleweide](https://github.com/molleweide) / [dotfiles](https://github.com/molleweide/dotfiles): uses Zsh as login shell, plenty of Bash commands, kmonad user - - [@jondpenton](https://github.com/jondpenton) / [dotfiles](https://github.com/jondpenton/dotfiles): uses Nu as login shell, plenty of Nu commands - - [See more Dorothy User Configurations](https://github.com/stars/balupton/lists/dorothy-user-configurations) - - To feature your own Dorothy User Configuration, send a pull request. +- [Dorothy's `commands` directory](https://github.com/bevry/dorothy/tree/master/commands) for super-stable commands with up to date conventions. +- [Dorothy's `commands.beta` directory](https://github.com/bevry/dorothy/tree/master/commands.beta) for beta-quality commands with possibly outdated conventions. +- [Dorothy's `docs` directory](https://github.com/bevry/dorothy/tree/master/docs) containing tips and tricks for Dorothy, and various shells, such as [enabling private user configurations](https://github.com/bevry/dorothy/blob/master/docs/dorothy/private-configuration.md), and the [manual to assisted](https://github.com/bevry/dorothy/blob/master/docs/dorothy/manual-to-assisted.md) philosophy of Dorothy +- Dorothy User Configurations: + - [@balupton](https://github.com/balupton) / [dotfiles](https://github.com/balupton/dotfiles): uses Fish as login shell, plenty of Bash commands + - [@molleweide](https://github.com/molleweide) / [dotfiles](https://github.com/molleweide/dotfiles): uses Zsh as login shell, plenty of Bash commands, kmonad user + - [@jondpenton](https://github.com/jondpenton) / [dotfiles](https://github.com/jondpenton/dotfiles): uses Nu as login shell, plenty of Nu commands + - [See more Dorothy User Configurations](https://github.com/stars/balupton/lists/dorothy-user-configurations) + - To feature your own Dorothy User Configuration, send a pull request. After installing Dorothy, there will now a plethora of commands available to you. You can invoke any stable command with `--help` to learn more about it. The most prominent commands are noted below. Stable commands: -- [`setup-system`](https://github.com/bevry/dorothy/tree/master/commands/setup-system) +- [`setup-system`](https://github.com/bevry/dorothy/tree/master/commands/setup-system) - - `setup-system install` correctly setup your system to your prompted preferences - - `setup-system update` correctly update your system to your existing preferences + - `setup-system install` correctly setup your system to your prompted preferences + - `setup-system update` correctly update your system to your existing preferences This is done via these commands: - - [`setup-linux`](https://github.com/bevry/dorothy/tree/master/commands/setup-linux) correctly setup your Linux system, and its various packaging systems, as desired - - [`setup-mac`](https://github.com/bevry/dorothy/tree/master/commands/setup-mac) correctly setup your macOS system, including its homebrew and Mac App Store installations, as desired - - [`setup-bin`](https://github.com/bevry/dorothy/tree/master/commands/setup-bin) correctly setup available CLI utilities from installed GUI Applications - - [`setup-git`](https://github.com/bevry/dorothy/tree/master/commands/setup-git) correctly setup Git on your system, including your profile, SSH, GPG, and 1Password configurations, as desired. + - [`setup-linux`](https://github.com/bevry/dorothy/tree/master/commands/setup-linux) correctly setup your Linux system, and its various packaging systems, as desired + - [`setup-mac`](https://github.com/bevry/dorothy/tree/master/commands/setup-mac) correctly setup your macOS system, including its homebrew and Mac App Store installations, as desired + - [`setup-bin`](https://github.com/bevry/dorothy/tree/master/commands/setup-bin) correctly setup available CLI utilities from installed GUI Applications + - [`setup-git`](https://github.com/bevry/dorothy/tree/master/commands/setup-git) correctly setup Git on your system, including your profile, SSH, GPG, and 1Password configurations, as desired. Related commands: - - [`gpg-helper`](https://github.com/bevry/dorothy/tree/master/commands/gpg-helper) interact with your GPG keys - - [`ssh-helper`](https://github.com/bevry/dorothy/tree/master/commands/ssh-helper) interact with your SSH keys + - [`gpg-helper`](https://github.com/bevry/dorothy/tree/master/commands/gpg-helper) interact with your GPG keys + - [`ssh-helper`](https://github.com/bevry/dorothy/tree/master/commands/ssh-helper) interact with your SSH keys - - [`setup-go`](https://github.com/bevry/dorothy/tree/master/commands/setup-go) correctly setup GoLang on your system if desired or if required for your desired packages - - [`setup-node`](https://github.com/bevry/dorothy/tree/master/commands/setup-node) correctly setup Node.js on your system if desired or if required for your desired packages - - [`setup-python`](https://github.com/bevry/dorothy/tree/master/commands/setup-python) correctly setup Python on your system if desired or if required for your desired packages - - [`setup-ruby`](https://github.com/bevry/dorothy/tree/master/commands/setup-ruby) correctly setup Ruby on your system if desired or if required for your desired packages - - [`setup-rust`](https://github.com/bevry/dorothy/tree/master/commands/setup-rust) correctly setup Rust on your system if desired or if required for your desired packages - - [`setup-utils`](https://github.com/bevry/dorothy/tree/master/commands/setup-utils) correctly setup your selected `setup-util-*` utilities as desired + - [`setup-go`](https://github.com/bevry/dorothy/tree/master/commands/setup-go) correctly setup GoLang on your system if desired or if required for your desired packages + - [`setup-node`](https://github.com/bevry/dorothy/tree/master/commands/setup-node) correctly setup Node.js on your system if desired or if required for your desired packages + - [`setup-python`](https://github.com/bevry/dorothy/tree/master/commands/setup-python) correctly setup Python on your system if desired or if required for your desired packages + - [`setup-ruby`](https://github.com/bevry/dorothy/tree/master/commands/setup-ruby) correctly setup Ruby on your system if desired or if required for your desired packages + - [`setup-rust`](https://github.com/bevry/dorothy/tree/master/commands/setup-rust) correctly setup Rust on your system if desired or if required for your desired packages + - [`setup-utils`](https://github.com/bevry/dorothy/tree/master/commands/setup-utils) correctly setup your selected `setup-util-*` utilities as desired -- [`setup-util`](https://github.com/bevry/dorothy/tree/master/commands/setup-util) is an intelligent wrapper around every package system, allowing a cross-compatible way to install, upgrade, and uninstall utilities. +- [`setup-util`](https://github.com/bevry/dorothy/tree/master/commands/setup-util) is an intelligent wrapper around every package system, allowing a cross-compatible way to install, upgrade, and uninstall utilities. It is used by the hundreds of `setup-util-*` commands, which enable installing a utility as easy as invoking `setup-util-` If you don't know which command you need to call, you can use [`get-installer`](https://github.com/bevry/dorothy/tree/master/commands/get-installer) to get which command you will need to invoke to install a utility/binary/application. -- [`setup-shell`](https://github.com/bevry/dorothy/tree/master/commands/setup-shell) correctly configure your desired shell to be your default shell. +- [`setup-shell`](https://github.com/bevry/dorothy/tree/master/commands/setup-shell) correctly configure your desired shell to be your default shell. By default, your terminal application will use the login shell configured for the system, as well as maintain a whitelist of available shells that can function as login shells. -- [`edit`](https://github.com/bevry/dorothy/tree/master/commands/edit) quickly open a file in your preferred editor, respecting terminal, SSH, and desktop environments. +- [`edit`](https://github.com/bevry/dorothy/tree/master/commands/edit) quickly open a file in your preferred editor, respecting terminal, SSH, and desktop environments. -- [`down`](https://github.com/bevry/dorothy/tree/master/commands/down) download a file with the best available utility on your computer. +- [`down`](https://github.com/bevry/dorothy/tree/master/commands/down) download a file with the best available utility on your computer. -- [`github-download`](https://github.com/bevry/dorothy/tree/master/commands/github-download) download files from GitHub without the tedium. +- [`github-download`](https://github.com/bevry/dorothy/tree/master/commands/github-download) download files from GitHub without the tedium. -- [`secret`](https://github.com/bevry/dorothy/tree/master/commands/secret) stops you from leaking your env secrets to the world when a malicious program sends your shell environment variables to a remote server. Instead, `secret` will use 1Password to securely expose your secrets to just the command that needs them. Specifically: +- [`secret`](https://github.com/bevry/dorothy/tree/master/commands/secret) stops you from leaking your env secrets to the world when a malicious program sends your shell environment variables to a remote server. Instead, `secret` will use 1Password to securely expose your secrets to just the command that needs them. Specifically: - - secrets are fetched directly from 1Password, with a short lived session - - secrets are cached securely for speed and convenience, only root/sudo has access to the cache (cache can be made optional if you want) - - secrets are not added to the global environment, only the secrets that are desired for the command are loaded for the command's environment only + - secrets are fetched directly from 1Password, with a short lived session + - secrets are cached securely for speed and convenience, only root/sudo has access to the cache (cache can be made optional if you want) + - secrets are not added to the global environment, only the secrets that are desired for the command are loaded for the command's environment only -- [`setup-dns`](https://github.com/bevry/dorothy/tree/master/commands/setup-dns) correctly configures your systems DNS to your preferences +- [`setup-dns`](https://github.com/bevry/dorothy/tree/master/commands/setup-dns) correctly configures your systems DNS to your preferences A large security concern these days of using the internet, is the leaking, and potential of modification of your DNS queries. A DNS query is what turns `google.com` to say `172.217.167.110`. With un-encrypted DNS (the default), your ISP, or say that public Wifi provider, can intercept these queries to find out what websites you are visiting, and they can even rewrite these queries, to direct you elsewhere. This is how many public Wifi providers offer their service for free, by selling the data they collect on you, or worse. @@ -357,68 +357,68 @@ Stable commands: Dorothy supports configuring your DNS to encrypted DNS via the [`setup-dns`](https://github.com/bevry/dorothy/tree/master/commands/setup-dns) command, which includes installation and configuration for any of these: - - AdGuard Home - - Cloudflared - - DNSCrypt + - AdGuard Home + - Cloudflared + - DNSCrypt Related commands: - - [`flush-dns`](https://github.com/bevry/dorothy/tree/master/commands/flush-dns) lets you easily flush your DNS anytime, any system. - - [`setup-hosts`](https://github.com/bevry/dorothy/tree/master/commands/setup-hosts) lets you easily select from a variety of HOSTS files for security and privacy, while maintaining your customizations. + - [`flush-dns`](https://github.com/bevry/dorothy/tree/master/commands/flush-dns) lets you easily flush your DNS anytime, any system. + - [`setup-hosts`](https://github.com/bevry/dorothy/tree/master/commands/setup-hosts) lets you easily select from a variety of HOSTS files for security and privacy, while maintaining your customizations. -- [`mount-helper`](https://github.com/bevry/dorothy/tree/master/commands/mount-helper) lets you easily, correctly, and safely mount, unmount, automount, various devices, filesystems, network shares, gocryptfs vaults, etc, on any system. +- [`mount-helper`](https://github.com/bevry/dorothy/tree/master/commands/mount-helper) lets you easily, correctly, and safely mount, unmount, automount, various devices, filesystems, network shares, gocryptfs vaults, etc, on any system. Related commands: - - [`get-devices`](https://github.com/bevry/dorothy/tree/master/commands/get-devices) cross-platform fetching and filtering of select and complete device information - - [`gocryptfs-helper`](https://github.com/bevry/dorothy/tree/master/commands/gocryptfs-helper) helpers for [GoCryptFS](https://github.com/rfjakob/gocryptfs) - - [`what-is-using`](https://github.com/bevry/dorothy/tree/master/commands/gocryptfs-helper) find out what is using a path so that you can unmount it safely + - [`get-devices`](https://github.com/bevry/dorothy/tree/master/commands/get-devices) cross-platform fetching and filtering of select and complete device information + - [`gocryptfs-helper`](https://github.com/bevry/dorothy/tree/master/commands/gocryptfs-helper) helpers for [GoCryptFS](https://github.com/rfjakob/gocryptfs) + - [`what-is-using`](https://github.com/bevry/dorothy/tree/master/commands/gocryptfs-helper) find out what is using a path so that you can unmount it safely -- Dorothy also provides commands for writing commands, such as: +- Dorothy also provides commands for writing commands, such as: - - [`bash.bash`](https://github.com/bevry/dorothy/tree/master/sourcces/bash.bash) for a Bash strict mode that actually works, and various shims/polyfills - - [`ask`](https://github.com/bevry/dorothy/tree/master/commands/ask), [`confirm`](https://github.com/bevry/dorothy/tree/master/commands/confirm), and [`choose`](https://github.com/bevry/dorothy/tree/master/commands/choose) for prompting the user for input - - [`echo-style`](https://github.com/bevry/dorothy/tree/master/commands/echo-style), [`echo-error`](https://github.com/bevry/dorothy/tree/master/commands/echo-error), [`echo-verbose`](https://github.com/bevry/dorothy/tree/master/commands/echo-verbose), and [`eval-helper`](https://github.com/bevry/dorothy/tree/master/commands/eval-helper) for output styling - - Dozens of `echo-*`, `fs-*`, `get-*`, and `is-*` helpers + - [`bash.bash`](https://github.com/bevry/dorothy/tree/master/sourcces/bash.bash) for a Bash strict mode that actually works, and various shims/polyfills + - [`ask`](https://github.com/bevry/dorothy/tree/master/commands/ask), [`confirm`](https://github.com/bevry/dorothy/tree/master/commands/confirm), and [`choose`](https://github.com/bevry/dorothy/tree/master/commands/choose) for prompting the user for input + - [`echo-style`](https://github.com/bevry/dorothy/tree/master/commands/echo-style), [`echo-error`](https://github.com/bevry/dorothy/tree/master/commands/echo-error), [`echo-verbose`](https://github.com/bevry/dorothy/tree/master/commands/echo-verbose), and [`eval-helper`](https://github.com/bevry/dorothy/tree/master/commands/eval-helper) for output styling + - Dozens of `echo-*`, `fs-*`, `get-*`, and `is-*` helpers Beta commands: -- [`mail-sync`](https://github.com/bevry/dorothy/tree/master/commands.beta/mail-sync) helps you migrate all your emails from one cloud provider to another. +- [`mail-sync`](https://github.com/bevry/dorothy/tree/master/commands.beta/mail-sync) helps you migrate all your emails from one cloud provider to another. ### macOS Stable commands: -- [`alias-helper`](https://github.com/bevry/dorothy/tree/master/commands/alias-helper) helps you manage your macOS aliases, and if desired, convert them into symlinks. -- [`macos-drive`](https://github.com/bevry/dorothy/tree/master/commands/macos-drive) helps you turn a macOS installer into a bootable USB drive. -- [`macos-installer`](https://github.com/bevry/dorothy/tree/master/commands/macos-installer) fetches the latest macOS installer. -- [`sparse-vault`](https://github.com/bevry/dorothy/tree/master/commands/sparse-vault) lets you easily, and for free, create secure encrypted password-protected vaults on your mac, for securing those super secret data. +- [`alias-helper`](https://github.com/bevry/dorothy/tree/master/commands/alias-helper) helps you manage your macOS aliases, and if desired, convert them into symlinks. +- [`macos-drive`](https://github.com/bevry/dorothy/tree/master/commands/macos-drive) helps you turn a macOS installer into a bootable USB drive. +- [`macos-installer`](https://github.com/bevry/dorothy/tree/master/commands/macos-installer) fetches the latest macOS installer. +- [`sparse-vault`](https://github.com/bevry/dorothy/tree/master/commands/sparse-vault) lets you easily, and for free, create secure encrypted password-protected vaults on your mac, for securing those super secret data. Beta commands: -- [`eject-all`](https://github.com/bevry/dorothy/tree/master/commands.beta/eject-all) eject all removable drives safely. -- [`icloud-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/icloud-helper) can free up space for time machine by evicting local iCloud caches. -- [`itunes-owners`](https://github.com/bevry/dorothy/tree/master/commands.beta/itunes-owners) generates a table of who legally owns what inside your iTunes Media Library β€” which is useful for debugging certain iTunes Store authorization issues, which can occur upon backup restorations. -- [`macos-settings`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-settings) helps configure macOS to your preferred system preferences. -- [`macos-state`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-state) helps you backup and restore your various application and system preferences, from time machine backups, local directories, and sftp locations. This makes setting up clean installs easy, as even the configuration is automated. And it also helps you never forget an important file, like your env secrets ever again. -- [`macos-theme`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-theme) helps you change your macOS theme to your preference, including your wallpaper and editor. -- [`tmutil-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/tmutil-helper) can free up space for bootcamp by evicting local Time Machine caches. +- [`eject-all`](https://github.com/bevry/dorothy/tree/master/commands.beta/eject-all) eject all removable drives safely. +- [`icloud-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/icloud-helper) can free up space for time machine by evicting local iCloud caches. +- [`itunes-owners`](https://github.com/bevry/dorothy/tree/master/commands.beta/itunes-owners) generates a table of who legally owns what inside your iTunes Media Library β€” which is useful for debugging certain iTunes Store authorization issues, which can occur upon backup restorations. +- [`macos-settings`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-settings) helps configure macOS to your preferred system preferences. +- [`macos-state`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-state) helps you backup and restore your various application and system preferences, from time machine backups, local directories, and sftp locations. This makes setting up clean installs easy, as even the configuration is automated. And it also helps you never forget an important file, like your env secrets ever again. +- [`macos-theme`](https://github.com/bevry/dorothy/tree/master/commands.beta/macos-theme) helps you change your macOS theme to your preference, including your wallpaper and editor. +- [`tmutil-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/tmutil-helper) can free up space for bootcamp by evicting local Time Machine caches. ### media Beta commands: -- [`convert-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/convert-helper) convert one media format to another -- [`get-codec`](https://github.com/bevry/dorothy/tree/master/commands.beta/get-codec) gets the codec of a media file -- [`is-audio-mono`](https://github.com/bevry/dorothy/tree/master/commands.beta/is-audio-mono) checks if an audio file is mono -- [`is-audio-stereo`](https://github.com/bevry/dorothy/tree/master/commands.beta/is-audio-stereo) checks if an audio file is stereo -- [`pdf-decrypt`](https://github.com/bevry/dorothy/tree/master/commands.beta/pdf-decrypt) will mass decrypt encrypted PDFs. -- [`pdf-decrypt`](https://github.com/bevry/dorothy/tree/master/commands.beta/pdf-encrypt) decrypts a PDF file -- [`svg-export`](https://github.com/bevry/dorothy/tree/master/commands.beta/svg-export) converts an SVG image into a desired image format -- [`video-merge`](https://github.com/bevry/dorothy/tree/master/commands.beta/video-merge) will merge multiple video files in a directory together into a single video file. -- [`wallhaven-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/wallhaven-helper) download your wallpaper collections from [Wallhaven](https://wallhaven.cc) -- [`xps2pdf`](https://github.com/bevry/dorothy/tree/master/commands.beta/xps2pdf) will convert a legacy XPS document into a modern PDF document. -- [`ytd-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/ytd-helper) helps you download videos from the internet with simplified options. +- [`convert-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/convert-helper) convert one media format to another +- [`get-codec`](https://github.com/bevry/dorothy/tree/master/commands.beta/get-codec) gets the codec of a media file +- [`is-audio-mono`](https://github.com/bevry/dorothy/tree/master/commands.beta/is-audio-mono) checks if an audio file is mono +- [`is-audio-stereo`](https://github.com/bevry/dorothy/tree/master/commands.beta/is-audio-stereo) checks if an audio file is stereo +- [`pdf-decrypt`](https://github.com/bevry/dorothy/tree/master/commands.beta/pdf-decrypt) will mass decrypt encrypted PDFs. +- [`pdf-decrypt`](https://github.com/bevry/dorothy/tree/master/commands.beta/pdf-encrypt) decrypts a PDF file +- [`svg-export`](https://github.com/bevry/dorothy/tree/master/commands.beta/svg-export) converts an SVG image into a desired image format +- [`video-merge`](https://github.com/bevry/dorothy/tree/master/commands.beta/video-merge) will merge multiple video files in a directory together into a single video file. +- [`wallhaven-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/wallhaven-helper) download your wallpaper collections from [Wallhaven](https://wallhaven.cc) +- [`xps2pdf`](https://github.com/bevry/dorothy/tree/master/commands.beta/xps2pdf) will convert a legacy XPS document into a modern PDF document. +- [`ytd-helper`](https://github.com/bevry/dorothy/tree/master/commands.beta/ytd-helper) helps you download videos from the internet with simplified options. ## Community @@ -434,20 +434,20 @@ Join the [Bevry Software community](https://discord.gg/nQuXddV7VP) to stay up-to #### Authors -- [Benjamin Lupton](https://balupton.com) β€” Accelerating collaborative wisdom. +- [Benjamin Lupton](https://balupton.com) β€” Accelerating collaborative wisdom. #### Maintainers -- [Benjamin Lupton](https://balupton.com) β€” Accelerating collaborative wisdom. +- [Benjamin Lupton](https://balupton.com) β€” Accelerating collaborative wisdom. #### Contributors -- [Benjamin Lupton](https://github.com/balupton) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=balupton 'View the GitHub contributions of Benjamin Lupton on repository bevry/dorothy') -- [Bevry Team](https://github.com/BevryMe) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=BevryMe 'View the GitHub contributions of Bevry Team on repository bevry/dorothy') -- [BJReplay](https://github.com/BJReplay) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=BJReplay 'View the GitHub contributions of BJReplay on repository bevry/dorothy') -- [molleweide](https://github.com/molleweide) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=molleweide 'View the GitHub contributions of molleweide on repository bevry/dorothy') -- [Nutchanon Ninyawee](https://github.com/wasdee) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=wasdee 'View the GitHub contributions of Nutchanon Ninyawee on repository bevry/dorothy') -- [Sumit Rai](https://github.com/sumitrai) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=sumitrai 'View the GitHub contributions of Sumit Rai on repository bevry/dorothy') +- [Benjamin Lupton](https://github.com/balupton) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=balupton 'View the GitHub contributions of Benjamin Lupton on repository bevry/dorothy') +- [Bevry Team](https://github.com/BevryMe) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=BevryMe 'View the GitHub contributions of Bevry Team on repository bevry/dorothy') +- [BJReplay](https://github.com/BJReplay) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=BJReplay 'View the GitHub contributions of BJReplay on repository bevry/dorothy') +- [molleweide](https://github.com/molleweide) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=molleweide 'View the GitHub contributions of molleweide on repository bevry/dorothy') +- [Nutchanon Ninyawee](https://github.com/wasdee) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=wasdee 'View the GitHub contributions of Nutchanon Ninyawee on repository bevry/dorothy') +- [Sumit Rai](https://github.com/sumitrai) β€” [view contributions](https://github.com/bevry/dorothy/commits?author=sumitrai 'View the GitHub contributions of Sumit Rai on repository bevry/dorothy') ### Finances @@ -461,31 +461,31 @@ Join the [Bevry Software community](https://discord.gg/nQuXddV7VP) to stay up-to #### Sponsors -- [Andrew Nesbitt](https://nesbitt.io) β€” Software engineer and researcher -- [Codecov](https://codecov.io) β€” Empower developers with tools to improve code quality and testing. -- [Frontend Masters](https://FrontendMasters.com) β€” The training platform for web app engineering skills – from front-end to full-stack! πŸš€ -- [Poonacha Medappa](https://poonachamedappa.com) -- [Rob Morris](https://github.com/Rob-Morris) -- [Sentry](https://sentry.io) β€” Real-time crash reporting for your web apps, mobile apps, and games. -- [Syntax](https://syntax.fm) β€” Syntax Podcast +- [Andrew Nesbitt](https://nesbitt.io) β€” Software engineer and researcher +- [Codecov](https://codecov.io) β€” Empower developers with tools to improve code quality and testing. +- [Frontend Masters](https://FrontendMasters.com) β€” The training platform for web app engineering skills – from front-end to full-stack! πŸš€ +- [Poonacha Medappa](https://poonachamedappa.com) +- [Rob Morris](https://github.com/Rob-Morris) +- [Sentry](https://sentry.io) β€” Real-time crash reporting for your web apps, mobile apps, and games. +- [Syntax](https://syntax.fm) β€” Syntax Podcast #### Donors -- [Andrew Nesbitt](https://nesbitt.io) -- [Balsa](https://balsa.com) -- [Chad](https://opencollective.com/chad8) -- [Codecov](https://codecov.io) -- [entroniq](https://gitlab.com/entroniq) -- [Frontend Masters](https://FrontendMasters.com) -- [Jean-Luc Geering](https://github.com/jlgeering) -- [Michael Duane Mooring](https://mdm.cc) -- [Mohammed Shah](https://github.com/smashah) -- [Mr. Henry](https://mrhenry.be) -- [Poonacha Medappa](https://poonachamedappa.com) -- [Rob Morris](https://github.com/Rob-Morris) -- [Sentry](https://sentry.io) -- [ServieJS](https://github.com/serviejs) -- [Syntax](https://syntax.fm) +- [Andrew Nesbitt](https://nesbitt.io) +- [Balsa](https://balsa.com) +- [Chad](https://opencollective.com/chad8) +- [Codecov](https://codecov.io) +- [entroniq](https://gitlab.com/entroniq) +- [Frontend Masters](https://FrontendMasters.com) +- [Jean-Luc Geering](https://github.com/jlgeering) +- [Michael Duane Mooring](https://mdm.cc) +- [Mohammed Shah](https://github.com/smashah) +- [Mr. Henry](https://mrhenry.be) +- [Poonacha Medappa](https://poonachamedappa.com) +- [Rob Morris](https://github.com/Rob-Morris) +- [Sentry](https://sentry.io) +- [ServieJS](https://github.com/serviejs) +- [Syntax](https://syntax.fm) @@ -495,10 +495,10 @@ Join the [Bevry Software community](https://discord.gg/nQuXddV7VP) to stay up-to Unless stated otherwise all works are: -- Copyright © [Benjamin Lupton](https://balupton.com) +- Copyright © [Benjamin Lupton](https://balupton.com) and licensed under: -- [Reciprocal Public License 1.5](http://spdx.org/licenses/RPL-1.5.html) +- [Reciprocal Public License 1.5](http://spdx.org/licenses/RPL-1.5.html) diff --git a/commands.beta/convert-helper b/commands.beta/convert-helper index bdb318323..bf8423089 100755 --- a/commands.beta/convert-helper +++ b/commands.beta/convert-helper @@ -82,7 +82,7 @@ function convert_helper() ( EXAMPLE: convert-helper --podaudio -- *.wav EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -90,7 +90,7 @@ function convert_helper() ( # process local item files=() action='' option_stereo='' option_mono='' option_timestamp='' option_delete='no' option_transcribe='no' option_trim_trailing_silence='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -134,9 +134,9 @@ function convert_helper() ( cd "$dir" || return filename="$(fs-filename -- "$path")" extension="$(fs-extension -- "$path")" - if test -n "$option_timestamp"; then + if [[ -n $option_timestamp ]]; then # prefix filename with timestamp - if test "$option_timestamp" = 'yes'; then + if [[ $option_timestamp == 'yes' ]]; then filename="$(date-helper --8601 | echo-regexp -g ':' '-') $filename" else filename="$(date-helper --8601 | echo-regexp -g ':' '-') $option_timestamp" @@ -144,67 +144,67 @@ function convert_helper() ( cp "$path" "$filename.$extension" path="$filename.$extension" fi - if test "$option_trim_trailing_silence" = 'yes'; then + if [[ $option_trim_trailing_silence == 'yes' ]]; then outfile="$filename [trimmed].m4a" run ffmpeg -y -i "$path" -af 'silenceremove=stop_periods=-1:stop_duration=0.5:stop_threshold=-60dB' "$outfile" - if test "$option_delete" = 'yes'; then - fs-rm --no-confirm --quiet -- "$path" + if [[ $option_delete == 'yes' ]]; then + fs-rm --quiet --no-confirm -- "$path" fi filename="$filename [trimmed]" path="$outfile" fi - if test "$action" = 'podaudio'; then - if test -z "$option_stereo" -a -z "$option_mono"; then + if [[ $action == 'podaudio' ]]; then + if [[ -z $option_stereo && -z $option_mono ]]; then if is-audio-stereo -- "$path"; then use_stereo='yes' else use_mono='yes' fi fi - if test "$use_stereo" = 'yes'; then + if [[ $use_stereo == 'yes' ]]; then outfile="$filename [aac_he_v2] [48k] [stereo].m4a" run ffmpeg -y -i "$path" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 48k "$outfile" fi - if test "$use_mono" = 'yes'; then + if [[ $use_mono == 'yes' ]]; then outfile="$filename [aac_he_v1] [48k] [mono].m4a" run ffmpeg -y -i "$path" -ac 1 -c:a libfdk_aac -profile:a aac_he -b:a 48k "$outfile" fi - elif test "$action" = 'podvideo'; then + elif [[ $action == 'podvideo' ]]; then outfile="$filename [h264].mp4" run ffmpeg -y -i "$path" -c:v libx264 -c:a libfdk_aac "$outfile" - elif test "$action" = 'alac'; then + elif [[ $action == 'alac' ]]; then outfile="$filename.m4a" run ffmpeg -y -vn -i "$path" -c:a alac "$outfile" - elif test "$action" = 'split'; then + elif [[ $action == 'split' ]]; then run ffmpeg -y -i "$path" -map 0:0 -map 0:2 -c copy "$filename [00+02].mov" run ffmpeg -y -i "$path" -map 0:1 -map 0:3 -c copy "$filename [01+03].mov" - # elif test "$action" = 'trim'; then + # elif [[ "$action" = 'trim' ]]; then # # Trim superfluous audio streams from a video file # help 'trim action not yet finished' # # run ffmpeg -i "$input" -c copy -an "$output" # # run ffmpeg -i "$input" -c copy -map 0:v -map "0:a:$stream" "$output" - # elif test "$action" = 'thumbnail'; then + # elif [[ "$action" = 'thumbnail' ]]; then # help 'thumbnail action not yet finished' # # outfile="$filename.jpg" # # run ffmpeg -y -i "$path" -vf "thumbnail" -frames:v 1 "$outfile" # # run ffmpeg -i "$1" -i "$2" -map 0 -map 1 -c copy -c:v:1 avif -disposition:v:1 attached_pic out.webm - elif test "$action" = 'png'; then + elif [[ $action == 'png' ]]; then outfile="$filename.png" run sips -s format png "$path" --out "$outfile" - elif test -n "$action"; then + elif [[ -n $action ]]; then help 'Unknown action' fi - if test "$option_transcribe" = 'yes'; then + if [[ $option_transcribe == 'yes' ]]; then outfile="$filename.srt" whisper --model base --language English --output_format srt "$path" fi - if test "$option_delete" = 'yes'; then - fs-rm --no-confirm --quiet -- "$path" + if [[ $option_delete == 'yes' ]]; then + fs-rm --quiet --no-confirm -- "$path" fi done ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then convert_helper "$@" fi diff --git a/commands/echo-affirmative b/commands.beta/echo-affirmative similarity index 87% rename from commands/echo-affirmative rename to commands.beta/echo-affirmative index 9d35e2c80..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] [--] ... @@ -45,7 +44,7 @@ function echo_affirmative() ( # exit status: 91 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -58,9 +57,9 @@ function echo_affirmative() ( function on_input { local status eval_capture --statusvar=status -- is-affirmative -- "$1" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then __print_lines 'yes' - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then __print_lines 'no' else echo-style --error="[$1] is neither affirmative or non-affirmative" >/dev/stderr @@ -68,7 +67,7 @@ function echo_affirmative() ( fi } function on_finish { - if test "$had_invalid_input" = yes; then + if [[ $had_invalid_input == yes ]]; then return 91 # ENOMSG 91 No message of desired type fi } @@ -77,6 +76,6 @@ function echo_affirmative() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_affirmative "$@" fi diff --git a/commands.beta/echo-escape-special b/commands.beta/echo-escape-special index bfb09a1b7..e663c6cdb 100755 --- a/commands.beta/echo-escape-special +++ b/commands.beta/echo-escape-special @@ -28,7 +28,7 @@ function echo_escape_special() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -42,6 +42,6 @@ function echo_escape_special() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_special "$@" fi diff --git a/commands.beta/echo-exit-affirmative b/commands.beta/echo-exit-affirmative index 9943768ba..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" @@ -28,7 +30,7 @@ function echo_exit_affirmative() ( echo-exit-affirmative --fallback=no -- waiter 0 --status=2 outputs: no EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -37,7 +39,7 @@ function echo_exit_affirmative() ( # process local rand="$RANDOM" local item option_cmd=() option_fallback="$rand" - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -53,7 +55,7 @@ function echo_exit_affirmative() ( done # check - if test "${#option_cmd[@]}" -eq 0; then + if [[ ${#option_cmd[@]} -eq 0 ]]; then help "No was provided." fi @@ -62,11 +64,11 @@ function echo_exit_affirmative() ( local cmd_status eval_capture --statusvar=cmd_status -- "${option_cmd[@]}" - if test "$cmd_status" -eq 0; then + if [[ $cmd_status -eq 0 ]]; then __print_lines 'yes' - elif test "$cmd_status" -eq 1; then + elif [[ $cmd_status -eq 1 ]]; then __print_lines 'no' - elif test "$option_fallback" != "$rand"; then + elif [[ $option_fallback != "$rand" ]]; then __print_lines "$option_fallback" else return "$cmd_status" @@ -74,6 +76,6 @@ function echo_exit_affirmative() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_exit_affirmative "$@" fi diff --git a/commands.beta/echo-exit-status b/commands.beta/echo-exit-status index b42c56746..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" @@ -24,7 +26,7 @@ function echo_exit_status() ( # process local item cmd=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +41,7 @@ function echo_exit_status() ( done # check - if test "${#cmd[@]}" -eq 0; then + if [[ ${#cmd[@]} -eq 0 ]]; then help 'No was provided.' fi @@ -52,6 +54,6 @@ function echo_exit_status() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_exit_status "$@" fi diff --git a/commands.beta/echo-html-coder.recode b/commands.beta/echo-html-coder.recode index f0e6d39d6..5d43c3fec 100755 --- a/commands.beta/echo-html-coder.recode +++ b/commands.beta/echo-html-coder.recode @@ -1,6 +1,6 @@ #!/usr/bin/env bash # recode decoding fails with: recode: Untranslatable input in step `ISO-10646-UCS-2..ANSI_X3.4-1968' -if test "$1" = 'decode'; then +if [[ $1 == 'decode' ]]; then recode html..ascii <<<"$2" else recode ascii..html <<<"$2" diff --git a/commands.beta/echo-html-coder.textutil b/commands.beta/echo-html-coder.textutil index eee7bf991..137ec50b6 100755 --- a/commands.beta/echo-html-coder.textutil +++ b/commands.beta/echo-html-coder.textutil @@ -1,6 +1,6 @@ #!/usr/bin/env bash # this doesn't work as expected -if test "$1" = 'decode'; then +if [[ $1 == 'decode' ]]; then textutil -convert html -format txt -inputencoding UTF-8 -stdin -stdout <<<"$2" else textutil -convert txt -format html -inputencoding UTF-8 -stdin -stdout <<<"$2" diff --git a/commands.beta/echo-html-coder.xmlstarlet b/commands.beta/echo-html-coder.xmlstarlet index 8c7778e5e..a5d7a1729 100755 --- a/commands.beta/echo-html-coder.xmlstarlet +++ b/commands.beta/echo-html-coder.xmlstarlet @@ -1,6 +1,6 @@ #!/usr/bin/env bash # xmlstarlet is too old to be available -if test "$1" = 'decode'; then +if [[ $1 == 'decode' ]]; then xmlstarlet unesc "$2" else xmlstarlet esc "$1" diff --git a/commands.beta/echo-if-directory b/commands.beta/echo-if-directory deleted file mode 100755 index 1afce8c1b..000000000 --- a/commands.beta/echo-if-directory +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -function echo_if_directory() ( - source "$DOROTHY/sources/stdinargs.bash" - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Output inputs that are directories. - - USAGE: - echo-if-directory [...options] [---] ... - echo-lines ... | echo-if-directory [...options] - - EXAMPLE: - - mkdir dir - touch file - - echo-if-file -- dir file directory - - dir - # exit status: 0 - - echo-lines -- dir file missing | echo-if-directory --stdin - - dir - # exit status: 0 - - EOF - if test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - function on_input { - if test -n "$1" -a -d "$1"; then - __print_lines "$1" - fi - } - - stdinargs "$@" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_if_directory "$@" -fi diff --git a/commands.beta/echo-if-executable b/commands.beta/echo-if-executable deleted file mode 100755 index c86c2f457..000000000 --- a/commands.beta/echo-if-executable +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -function echo_if_executable() ( - source "$DOROTHY/sources/stdinargs.bash" - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Output inputs that are executable paths. - - USAGE: - echo-if-file [...options] [--] ... - echo-lines ... | echo-if-file [...options] - - OPTIONS: - $(stdinargs_options_help --) - - EXAMPLE: - - touch executable - chmod +x executable - touch file - - echo-if-file -- executable file missing - - executable - # exit status: 0 - - echo-lines -- executable file missing | echo-if-file --stdin - - executable - # exit status: 0 - - EOF - if test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - function on_input { - if test -n "$1" -a -x "$1"; then - __print_lines "$1" - fi - } - - stdinargs "$@" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_if_executable "$@" -fi diff --git a/commands.beta/echo-if-path b/commands.beta/echo-if-path deleted file mode 100755 index 325fa6722..000000000 --- a/commands.beta/echo-if-path +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -function echo_if_path() ( - source "$DOROTHY/sources/stdinargs.bash" - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Output inputs that are existing paths. - - USAGE: - echo-if-path [...options] [--] ... - echo-lines ... | echo-if-path [...options] - - OPTIONS: - $(stdinargs_options_help --) - - EXAMPLES: - - echo-if-path -- $HOME - - $HOME - # exit status: 0 - - echo-lines -- $HOME | echo-if-path --stdin - - $HOME - # exit status: 0 - - EOF - if test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - function on_input { - if is-present -- "$1"; then - __print_lines "$1" - fi - } - - stdinargs "$@" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_if_path "$@" -fi diff --git a/commands.beta/echo-last-line b/commands.beta/echo-last-line index e35c5f0be..a7761e5ff 100755 --- a/commands.beta/echo-last-line +++ b/commands.beta/echo-last-line @@ -7,6 +7,6 @@ function echo_last_line() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_last_line "$@" fi diff --git a/commands.beta/echo-mkdir b/commands.beta/echo-mkdir index 216b0f952..e6c7c0fff 100755 --- a/commands.beta/echo-mkdir +++ b/commands.beta/echo-mkdir @@ -29,7 +29,7 @@ function echo_mkdir() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -37,7 +37,7 @@ function echo_mkdir() ( # process our own arguments, delegate everything else to stdinargs local item option_quiet='' option_sudo='no' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -51,7 +51,7 @@ function echo_mkdir() ( '--no-sudo'* | '--sudo'*) option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -62,7 +62,7 @@ function echo_mkdir() ( done # construct command - if test "$option_sudo" = 'yes'; then + if [[ $option_sudo == 'yes' ]]; then function __mkdir { sudo-helper --reason='Your sudo/root/login password is required to ensure the directory exists:' -- mkdir -p "$@" } @@ -76,8 +76,8 @@ function echo_mkdir() ( # Action function on_line { - if test -d "$1" || __mkdir "$1"; then - if test "$option_quiet" != 'yes'; then + if [[ -d $1 ]] || __mkdir "$1"; then + if [[ $option_quiet != 'yes' ]]; then fs-absolute -- "$1" fi return 0 @@ -90,6 +90,6 @@ function echo_mkdir() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_mkdir "$@" fi diff --git a/commands/echo-non-affirmative b/commands.beta/echo-non-affirmative similarity index 88% rename from commands/echo-non-affirmative rename to commands.beta/echo-non-affirmative index 8543afc55..602f581c6 100755 --- a/commands/echo-non-affirmative +++ b/commands.beta/echo-non-affirmative @@ -45,7 +45,7 @@ function echo_non_affirmative() ( # exit status: 91 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -58,9 +58,9 @@ function echo_non_affirmative() ( function on_line { local non_affirmative_status eval_capture --statusvar=non_affirmative_status -- is-non-affirmative -- "$1" - if test "$non_affirmative_status" -eq 0; then + if [[ $non_affirmative_status -eq 0 ]]; then __print_lines 'yes' - elif test "$non_affirmative_status" -eq 1; then + elif [[ $non_affirmative_status -eq 1 ]]; then __print_lines 'no' else echo-style --error="[$1] is neither affirmative or non-affirmative" >/dev/stderr @@ -69,7 +69,7 @@ function echo_non_affirmative() ( } function on_finish { - if test "$had_invalid_input" = yes; then + if [[ $had_invalid_input == yes ]]; then return 91 # ENOMSG 91 No message of desired type fi } @@ -78,6 +78,6 @@ function echo_non_affirmative() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_non_affirmative "$@" fi diff --git a/commands.beta/echo-nonflags b/commands.beta/echo-nonflags index 09610ec51..1033d689e 100755 --- a/commands.beta/echo-nonflags +++ b/commands.beta/echo-nonflags @@ -38,7 +38,7 @@ function echo_nonflags() ( code -a --b c # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -54,6 +54,6 @@ function echo_nonflags() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_nonflags "$@" fi diff --git a/commands.beta/echo-nothing-or-fail b/commands.beta/echo-nothing-or-fail index 7e0ee42b5..beba5a10e 100755 --- a/commands.beta/echo-nothing-or-fail +++ b/commands.beta/echo-nothing-or-fail @@ -35,7 +35,7 @@ function echo_nothing_or_fail() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -56,6 +56,6 @@ function echo_nothing_or_fail() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_nothing_or_fail "$@" fi diff --git a/commands.beta/echo-numeric b/commands.beta/echo-numeric index 944317084..e2af6f9b4 100755 --- a/commands.beta/echo-numeric +++ b/commands.beta/echo-numeric @@ -38,7 +38,7 @@ function echo_numeric() ( # exit status: 0, it was ignored due to || : EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -54,7 +54,7 @@ function echo_numeric() ( fi } function on_finish { - if test "$had_an_invalid_input" = yes; then + if [[ $had_an_invalid_input == yes ]]; then return 22 # EINVAL 22 Invalid argument fi } @@ -63,6 +63,6 @@ function echo_numeric() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_numeric "$@" fi diff --git a/commands.beta/echo-revolving-screen b/commands.beta/echo-revolving-screen index ceaa0d9d1..32054f05d 100755 --- a/commands.beta/echo-revolving-screen +++ b/commands.beta/echo-revolving-screen @@ -9,7 +9,7 @@ function echo_revolving_screen() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Continously clear the output of a command, showing only the latest output, then clearing it upon completion. + Continuously clear the output of a command, showing only the latest output, then clearing it upon completion. USAGE: (echo-lines -- 1 2; sleep 2; echo-lines -- 3 4; sleep 2) | echo-revolving-door [...options] @@ -19,7 +19,7 @@ function echo_revolving_screen() ( --columns= The number of columns to display. If not provided, the terminal's columns will be used. If the terminal's columns cannot be determined, or if <= 0, then the full line will be displayed. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function echo_revolving_screen() ( # process local item option_lines='' option_columns='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -40,14 +40,14 @@ function echo_revolving_screen() ( done # determine columns - if test -z "$option_lines" -o -z "$option_columns"; then + if [[ -z $option_lines || -z $option_columns ]]; then local terminal_size=() mapfile -t terminal_size < <(get-terminal-lines-and-columns || :) - if test "${#terminal_size[@]}" -eq 2; then - if test -z "$option_lines"; then + if [[ ${#terminal_size[@]} -eq 2 ]]; then + if [[ -z $option_lines ]]; then option_lines="${terminal_size[0]}" fi - if test -z "$option_columns"; then + if [[ -z $option_columns ]]; then option_columns="${terminal_size[1]}" fi fi @@ -56,7 +56,7 @@ function echo_revolving_screen() ( # ===================================== # Action - if test -z "$option_lines" -o -z "$option_columns" || test "$option_lines" -le 0 -o "$option_columns" -le 0; then + if [[ -z $option_lines || -z $option_columns || $option_lines -le 0 || $option_columns -le 0 ]]; then cat else local terminal_device_file clear_screen @@ -64,19 +64,19 @@ function echo_revolving_screen() ( clear_screen=$'\e[H\e[J' local input total_lines=0 status wrapped lines=0 - while IFS= read -r input || test -n "$input"; do + while IFS= read -r input || [[ -n $input ]]; do eval_capture --statusvar=status --outputvar=wrapped \ -- gfold --width="$option_columns" <<<"$input" #-- echo-wrap --width="$option_columns" -- "$input" - if test "$status" -ne 0; then + if [[ $status -ne 0 ]]; then echo-error "$wrapped" return "$status" fi # count lines lines="$(echo-count-lines -- "$wrapped")" total_lines="$((total_lines + lines))" - if test "$total_lines" -ge "$option_lines"; then - if test "$lines" -ge "$option_lines"; then + if [[ $total_lines -ge $option_lines ]]; then + if [[ $lines -ge $option_lines ]]; then # this line is very long and wraps across all lines of our screen, this is an edge case # use a coreutil to trim the excess lines __print_lines "${clear_screen}${wrapped}" | head -n "$option_lines" @@ -96,6 +96,6 @@ function echo_revolving_screen() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_revolving_screen "$@" fi diff --git a/commands.beta/echo-segment b/commands.beta/echo-segment index 6d38294e4..841897d16 100755 --- a/commands.beta/echo-segment +++ b/commands.beta/echo-segment @@ -24,6 +24,6 @@ function echo_segment() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_segment "$@" fi diff --git a/commands.beta/echo-sort b/commands.beta/echo-sort index 7ba706297..d86811ccd 100755 --- a/commands.beta/echo-sort +++ b/commands.beta/echo-sort @@ -28,7 +28,7 @@ function echo_sort() ( 2 3 - ALTERATIVES: + ALTERNATIVES: # use [sort] directly for more advanced usage echo-lines -- 3 2 1 | sort @@ -43,7 +43,7 @@ function echo_sort() ( 1.1 1.2 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -63,6 +63,6 @@ function echo_sort() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_sort "$@" fi diff --git a/commands.beta/echo-truncate-lines b/commands.beta/echo-truncate-lines new file mode 100755 index 000000000..0fdacd7df --- /dev/null +++ b/commands.beta/echo-truncate-lines @@ -0,0 +1,138 @@ +#!/usr/bin/env bash + +function echo_truncate_lines_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + { + printf '%s' $'1\n2\n' + } | eval-tester --stdout=1 \ + -- echo-truncate-lines 1 --stdin + + { + printf '%s' $'1\n2\n' + } | eval-tester --stdout=$'1\n2' \ + -- echo-truncate-lines 2 --stdin + + { + printf '%s' $'1\n2\n' + } | eval-tester --stdout=$'1\n2' \ + -- echo-truncate-lines 3 --stdin + + { + printf '%s' $'1\n2\na' + } | eval-tester --stdout=$'1\n2' \ + -- echo-truncate-lines 2 --stdin + + { + printf '%s' $'1\n2\na' + } | eval-tester --stdout=$'1\n2\na' \ + -- echo-truncate-lines 3 --stdin + + echo-style --g1="TEST: $0" + return 0 +) +function echo_truncate_lines() ( + source "$DOROTHY/sources/stdinargs.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Truncate the if it reaches more than . + + USAGE: + echo-truncate-lines [...options] [--] + echo-lines ... | echo-truncate-lines [...options] + + OPTIONS: + | --limit= + The amount of lines to truncate to. + + $(stdinargs_options_help --) + + QUIRKS: + This does not wrap lines. If you want wrapped lines, send to echo-wrap first, then pipe to echo-truncate-lines. + + Only a single argument is supported, as multiple arguments is ambiguous, do you want to truncate for each argument, or the total? + + Inlines are counted as whole lines, however they will remain inline. + + EXAMPLE: + + echo-truncate-lines 2 -- $'1\n2\n3\n4\n' + + 1 + 2 + # 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 item option_limit='' option_args=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--limit='*) option_limit="${item#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + *) + if [[ -z $option_limit ]]; then + option_limit="$item" + else + option_args+=("$item") + fi + ;; + esac + done + + # check + if [[ -z $option_limit ]]; then + help 'No provided.' + fi + + # ===================================== + # Action + + local result='' lines=0 + function on_line { + if [[ $lines -ge $option_limit ]]; then + return 210 # ECUSTOM 210 Processing complete, exit early + fi + lines="$((lines + 1))" + result+="$1"$'\n' + } + function on_inline { + if [[ $lines -ge $option_limit ]]; then + return 210 # ECUSTOM 210 Processing complete, exit early + fi + lines="$((lines + 1))" + result+="$1" + } + function on_finish { + __print_lines "$result" + } + stdinargs --max-args=1 "${option_args[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + echo_truncate_lines_test + else + echo_truncate_lines "$@" + fi +fi diff --git a/commands.beta/echo-with-empty-fallback b/commands.beta/echo-with-empty-fallback new file mode 100755 index 000000000..851bca78b --- /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 stdinargs 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 59% rename from commands.beta/echo-if-empty rename to commands.beta/echo-with-whitespace-fallback index 1f9e46975..eb4ae5602 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,25 +21,22 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -47,13 +45,13 @@ function echo_if_empty() ( # process our own arguments, delegate everything else to stdinargs local rand="$RANDOM" local item option_fallback="$rand" option_args=() - while test "$#" -ne 0; do + 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 + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -61,7 +59,7 @@ function echo_if_empty() ( ;; '--'*) option_args+=("$item") ;; *) - if test "$option_fallback" = "$rand"; then + if [[ $option_fallback == "$rand" ]]; then option_fallback="$item" else option_args+=("$item") @@ -70,9 +68,9 @@ function echo_if_empty() ( esac done - # checck for expected - if test "$option_fallback" = "$rand"; then - help "Missing required argument: " + # check for expected + if [[ $option_fallback == "$rand" ]]; then + 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" @@ -94,6 +92,6 @@ function echo_if_empty() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_if_empty "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + echo_with_whitespace_fallback "$@" fi diff --git a/commands.beta/edit-dns b/commands.beta/edit-dns index adb286600..0f36b48be 100755 --- a/commands.beta/edit-dns +++ b/commands.beta/edit-dns @@ -16,7 +16,7 @@ function edit_dns() ( USAGE: edit-dns EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -24,7 +24,7 @@ function edit_dns() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -51,6 +51,6 @@ function edit_dns() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then edit_dns "$@" fi diff --git a/commands.beta/edit-hosts b/commands.beta/edit-hosts index ef6b67138..0c5652a73 100755 --- a/commands.beta/edit-hosts +++ b/commands.beta/edit-hosts @@ -14,7 +14,7 @@ function edit_hosts() ( USAGE: edit-hosts EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function edit_hosts() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,6 +39,6 @@ function edit_hosts() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then edit_hosts "$@" fi diff --git a/commands.beta/eject-all b/commands.beta/eject-all index b083372bf..9a16f0eaf 100755 --- a/commands.beta/eject-all +++ b/commands.beta/eject-all @@ -14,7 +14,7 @@ function eject_all() ( USAGE: eject-all EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function eject_all() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -38,7 +38,7 @@ function eject_all() ( echo-style --h1='eject-all' echo-style --h2='before' - ls /Volumes + fs-structure -- /Volumes echo-style --g2='before' eval-helper --quiet \ @@ -48,13 +48,13 @@ function eject_all() ( -- osascript -e 'tell application "Finder" to eject (every disk whose ejectable is true)' echo-style --h2='after' - ls /Volumes + fs-structure -- /Volumes echo-style --g2='after' echo-style --h1='eject-all' ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then eject_all "$@" fi diff --git a/commands.beta/ensure-trailing-newline b/commands.beta/ensure-trailing-newline index e1a17ca08..768495a68 100755 --- a/commands.beta/ensure-trailing-newline +++ b/commands.beta/ensure-trailing-newline @@ -14,7 +14,7 @@ function ensure_trailing_newline() ( USAGE: ensure-trailing-newline [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function ensure_trailing_newline() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -38,7 +38,7 @@ function ensure_trailing_newline() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help "No s provided" fi @@ -47,9 +47,9 @@ function ensure_trailing_newline() ( local path result=0 for path in "${option_paths[@]}"; do - if test -f "$path"; then - if test "$(tail -n1 <"$path" | wc -l)" -eq 0; then - if test -w "$path"; then + if [[ -f $path ]]; then + if [[ "$(tail -n1 <"$path" | wc -l)" -eq 0 ]]; then + if [[ -w $path ]]; then __print_line >>"$path" else echo-style --error1='File is not writable: ' --code-error1="$path" >/dev/stderr @@ -65,6 +65,6 @@ function ensure_trailing_newline() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then ensure_trailing_newline "$@" fi diff --git a/commands.beta/ensure-trailing-slash b/commands.beta/ensure-trailing-slash index 15e72e63d..4f1206a42 100755 --- a/commands.beta/ensure-trailing-slash +++ b/commands.beta/ensure-trailing-slash @@ -15,7 +15,7 @@ function ensure_trailing_slash() ( USAGE: ensure-trailing-slash [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function ensure_trailing_slash() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function ensure_trailing_slash() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s was provided.' fi @@ -48,13 +48,13 @@ function ensure_trailing_slash() ( local path length for path in "${option_paths[@]}"; do - if test -d "$path"; then + if [[ -d $path ]]; then if [[ $path != */ ]]; then __print_lines "$path/" else __print_lines "$path" fi - elif test -f "$path"; then + elif [[ -f $path ]]; then if [[ $path == */ ]]; then length="${#path}" __print_lines "${path:0:length-1}" @@ -69,6 +69,6 @@ function ensure_trailing_slash() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then ensure_trailing_slash "$@" fi diff --git a/commands.beta/eval-on-empty-stdin b/commands.beta/eval-on-empty-stdin deleted file mode 100755 index c2499c037..000000000 --- a/commands.beta/eval-on-empty-stdin +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -function eval_on_empty_stdin() ( - source "$DOROTHY/sources/bash.bash" - - setup-util-moreutils --quiet # ifne - - ifne -n "$@" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - eval_on_empty_stdin "$@" -fi diff --git a/commands.beta/eval-on-not-empty-stdin b/commands.beta/eval-on-not-empty-stdin deleted file mode 100755 index 063101bac..000000000 --- a/commands.beta/eval-on-not-empty-stdin +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -function eval_on_not_empty_stdin() ( - source "$DOROTHY/sources/bash.bash" - - setup-util-moreutils --quiet # ifne - - ifne "$@" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - eval_on_not_empty_stdin "$@" -fi diff --git a/commands.beta/example-generic-command b/commands.beta/example-generic-command index 79972b633..f64a831fa 100755 --- a/commands.beta/example-generic-command +++ b/commands.beta/example-generic-command @@ -30,7 +30,7 @@ function example_generic_command() ( -- ... Adds each argument to an array. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -38,7 +38,7 @@ function example_generic_command() ( # process local item option_boolean='' option_string='' option_multistring=() option_arg='' option_multiargs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -55,7 +55,7 @@ function example_generic_command() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_arg"; then + if [[ -z $option_arg ]]; then option_arg="$item" else help "An unrecognised argument was provided: $item" @@ -87,6 +87,6 @@ function example_generic_command() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then example_generic_command "$@" fi diff --git a/commands.beta/find-files b/commands.beta/find-files index dc42ec6c9..66e042ca6 100755 --- a/commands.beta/find-files +++ b/commands.beta/find-files @@ -25,7 +25,7 @@ function find_files() ( --exec= Provide to specify a command to run on each matching file. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -33,7 +33,7 @@ function find_files() ( # options local item path='' extension='' exec='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,7 +43,7 @@ function find_files() ( '--exec='*) exec="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$path"; then + if [[ -z $path ]]; then path="$item" else help "An unrecognised argument was provided: $item" @@ -53,7 +53,7 @@ function find_files() ( done # adjust path - if test -z "$path"; then + if [[ -z $path ]]; then path="$(pwd)" fi @@ -62,14 +62,14 @@ function find_files() ( # args local args=("$path") - if test -n "$extension"; then + if [[ -n $extension ]]; then # args+=('-path' "**/*.$extension") args+=('-name' "*.$extension") # ^^ glob is handled by find, not bash fi # act - if test -n "$exec"; then + if [[ -n $exec ]]; then find "${args[@]}" -exec "$exec" {} \; else find "${args[@]}" @@ -77,6 +77,6 @@ function find_files() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then find_files "$@" fi diff --git a/commands.beta/find-symlinks b/commands.beta/find-symlinks index 590409935..e21ef8986 100755 --- a/commands.beta/find-symlinks +++ b/commands.beta/find-symlinks @@ -19,7 +19,7 @@ function find_symlinks() ( Provide to specify which directory should be searched for files. If was not provided then the current working directory will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function find_symlinks() ( # options local item path='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -35,7 +35,7 @@ function find_symlinks() ( '--path='*) path="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$path"; then + if [[ -z $path ]]; then path="$item" else help "An unrecognised argument was provided: $item" @@ -45,7 +45,7 @@ function find_symlinks() ( done # adjust path - if test -z "$path"; then + if [[ -z $path ]]; then path="$(pwd)" fi @@ -57,6 +57,6 @@ function find_symlinks() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then find_symlinks "$@" fi diff --git a/commands.beta/font-search b/commands.beta/font-search index 4131f8a5d..19c17dc84 100755 --- a/commands.beta/font-search +++ b/commands.beta/font-search @@ -14,7 +14,7 @@ function font_search() ( USAGE: font-search EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,14 +22,14 @@ function font_search() ( # process local item query='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$query"; then + if [[ -z $query ]]; then query="$item" else help "An unrecognised argument was provided: $item" @@ -39,7 +39,7 @@ function font_search() ( done # check - if test -z "$query"; then + if [[ -z $query ]]; then help "No was provided." fi @@ -50,6 +50,6 @@ function font_search() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then font_search "$@" fi diff --git a/commands.beta/geocode b/commands.beta/geocode index 120009f4a..088fe1fad 100755 --- a/commands.beta/geocode +++ b/commands.beta/geocode @@ -15,7 +15,7 @@ function geocode_() ( USAGE: geocode [--token=...] [--location=...] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function geocode_() ( # process local item token='' location='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -54,6 +54,6 @@ function geocode_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then geocode_ "$@" fi diff --git a/commands.beta/get-codec b/commands.beta/get-codec index 4c443996b..c03bd0c52 100755 --- a/commands.beta/get-codec +++ b/commands.beta/get-codec @@ -12,7 +12,7 @@ function get_codec_test() ( eval-tester --name='ogg vorbis file is ogg vorbis' --stdout='vorbis' \ -- get-codec --path="$ogg_media_file" else - eval-tester --name='ogg vorbis file is ogg vorbis' --status=6 \ + eval-tester --name='ogg vorbis file is ogg vorbis' --status=6 --ignore-stderr \ -- get-codec --path="$ogg_media_file" fi @@ -33,7 +33,7 @@ function get_codec() ( USAGE: get-codec [--verbose] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -41,7 +41,7 @@ function get_codec() ( # process local item option_path='' option_verbose='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -52,7 +52,7 @@ function get_codec() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_path"; then + if [[ -z $option_path ]]; then option_path="$item" else help "An unrecognised argument was provided: $item" @@ -62,7 +62,7 @@ function get_codec() ( done # check - if test -z "$option_path"; then + if [[ -z $option_path ]]; then help "No path was provided" fi @@ -71,10 +71,10 @@ function get_codec() ( if __command_missing -- ffprobe; then echo-error 'ffprobe is required for this command.' - return 6 # ENXIO 6 No such device or address + return 6 # ENXIO 6 Device not configured fi - if test "$option_verbose" = 'no'; then + if [[ $option_verbose == 'no' ]]; then ffprobe -i "$option_path" 2>&1 | echo-regexp -fon --regexp='Audio: ([\w\d-]+)' --replace='$1' else ffprobe -i "$option_path" @@ -82,8 +82,8 @@ function get_codec() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_codec_test else get_codec "$@" diff --git a/commands.beta/get-git-active-branch b/commands.beta/get-git-active-branch index 60f0e3b1b..653f0622f 100755 --- a/commands.beta/get-git-active-branch +++ b/commands.beta/get-git-active-branch @@ -16,7 +16,7 @@ function get_git_active_branch() ( USAGE: get-git-active-branch EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -24,7 +24,7 @@ function get_git_active_branch() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -41,6 +41,6 @@ function get_git_active_branch() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_git_active_branch "$@" fi diff --git a/commands.beta/get-git-default-branch b/commands.beta/get-git-default-branch index 76270a81b..66a793536 100755 --- a/commands.beta/get-git-default-branch +++ b/commands.beta/get-git-default-branch @@ -16,7 +16,7 @@ function get_git_default_branch() ( USAGE: get-git-default-branch EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -24,7 +24,7 @@ function get_git_default_branch() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -46,9 +46,9 @@ function get_git_default_branch() ( default_local="$(git config init.defaultBranch 2>/dev/null || :)" default_global="$(git config --global init.defaultBranch 2>/dev/null || :)" - if test -n "$default_local" && __branch_exists "$default_local"; then + if [[ -n $default_local ]] && __branch_exists "$default_local"; then __print_lines "$default_local" - elif test -n "$default_global" && __branch_exists "$default_global"; then + elif [[ -n $default_global ]] && __branch_exists "$default_global"; then __print_lines "$default_global" elif __branch_exists 'main'; then __print_lines 'main' @@ -61,6 +61,6 @@ function get_git_default_branch() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_git_default_branch "$@" fi diff --git a/commands.beta/icloud-helper b/commands.beta/icloud-helper index 3b1431867..b0835a937 100755 --- a/commands.beta/icloud-helper +++ b/commands.beta/icloud-helper @@ -30,7 +30,7 @@ function icloud_helper() ( size get the size of the local iCloud Drive. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -38,7 +38,7 @@ function icloud_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -50,7 +50,7 @@ function icloud_helper() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -60,7 +60,7 @@ function icloud_helper() ( done # check - if test -z "$action"; then + if [[ -z $action ]]; then help "No was provided." fi @@ -76,8 +76,8 @@ function icloud_helper() ( for path in "$@"; do # .DS_Store files are not added to iCloud Drive # .icloud files are placeholders for non-local files - # .screenflow does not play niceley with iCloud Drive, as it is a secret directory - if test ! -d "$path" && [[ $path != *".DS_Store" && $path != *".icloud" ]]; then + # .screenflow does not play nicely with iCloud Drive, as it is a secret directory + if [[ ! -d $path && $path != *".DS_Store" && $path != *".icloud" ]]; then if [[ $path == *".screenflow"* || $path == *".sketch"* ]]; then __print_lines "skipped $path, as it is a directory in disguise as a file, and can only evict files" else @@ -100,7 +100,7 @@ function icloud_helper() ( # ===================================== # Act - if test "$(type -t "$action")" = 'function'; then + if [[ "$(type -t "$action")" == 'function' ]]; then "$action" "${option_args[@]}" else __print_lines "Action [$action] not yet implemented." >/dev/stderr @@ -109,6 +109,6 @@ function icloud_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then icloud_helper "$@" fi diff --git a/commands.beta/is-audio-mono b/commands.beta/is-audio-mono index 3b30154b0..f9e20de61 100755 --- a/commands.beta/is-audio-mono +++ b/commands.beta/is-audio-mono @@ -7,6 +7,6 @@ function is_audio_mono() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_audio_mono "$@" fi diff --git a/commands.beta/is-audio-stereo b/commands.beta/is-audio-stereo index 39e77ae97..8f1c71f2e 100755 --- a/commands.beta/is-audio-stereo +++ b/commands.beta/is-audio-stereo @@ -7,6 +7,6 @@ function is_audio_stereo() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_audio_stereo "$@" fi diff --git a/commands.beta/is-dir b/commands.beta/is-dir deleted file mode 100755 index 19b12633a..000000000 --- a/commands.beta/is-dir +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -function is_dir() ( - source "$DOROTHY/sources/bash.bash" - - local dir="$1" - test -d "$dir" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_dir "$@" -fi diff --git a/commands.beta/is-either b/commands.beta/is-either deleted file mode 100755 index d5d370a8d..000000000 --- a/commands.beta/is-either +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -function is_either() ( - source "$DOROTHY/sources/bash.bash" - - local arg - for arg in "${@:2}"; do - if test "$1" = "$arg"; then - return 0 - fi - done - return 1 -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_either "$@" -fi diff --git a/commands.beta/is-empty-directory b/commands.beta/is-empty-directory deleted file mode 100755 index a732a9e48..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 test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_paths=() - while test "$#" -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 test "${#option_paths[@]}" -eq 0; then - help "No s provided." - fi - - # ===================================== - # Action - - # action - local path result - for path in "${option_paths[@]}"; do - if test ! -d "$path"; then - return 1 - fi - result="$(ls -A "$path")" - if test -z "$result"; then - continue - else - return 1 - fi - done -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_empty_directory "$@" -fi diff --git a/commands.beta/is-empty-size b/commands.beta/is-empty-size index fb61a1b62..576f1f783 100755 --- a/commands.beta/is-empty-size +++ b/commands.beta/is-empty-size @@ -24,7 +24,7 @@ function is_empty_size() ( [1] if any s were not empty. [2] if any were not a file. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function is_empty_size() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -48,7 +48,7 @@ function is_empty_size() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help "No s provided." fi @@ -61,16 +61,16 @@ function is_empty_size() ( # process local path for path in "${option_paths[@]}"; do - if test ! -f "$path"; then + if [[ ! -f $path ]]; then echo-error 'A path was was not a file: ' --code="$path" return 2 fi - test "$(du -s "$path")" = $'0\t'"$path" + [[ "$(du -s "$path")" == $'0\t'"$path" ]] || return # explicit return with [[ required for bash v3 done return 0 ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_empty_size "$@" fi diff --git a/commands.beta/is-equal b/commands.beta/is-equal deleted file mode 100755 index 25792cbae..000000000 --- a/commands.beta/is-equal +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -function is_equal() ( - source "$DOROTHY/sources/bash.bash" - - test "$1" = "$2" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_equal "$@" -fi diff --git a/commands.beta/is-even b/commands.beta/is-even deleted file mode 100755 index 18b2cffba..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 test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item inputs=() - while test "$#" -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 test "${#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 - test "$((input % 2))" -eq 0 - done - return 0 -) - -# fire if invoked standalone -if test "$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 87c696901..000000000 --- a/commands.beta/is-exec +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -function is_exec() ( - source "$DOROTHY/sources/bash.bash" - - test -x "$1" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_exec "$@" -fi diff --git a/commands.beta/is-fedora b/commands.beta/is-fedora index e32df9e9b..19fbc738f 100755 --- a/commands.beta/is-fedora +++ b/commands.beta/is-fedora @@ -3,10 +3,11 @@ function is_fedora() ( source "$DOROTHY/sources/bash.bash" - test "$(uname -n)" = 'fedora' + [[ "$(uname -n)" == 'fedora' ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_fedora "$@" fi diff --git a/commands.beta/is-file b/commands.beta/is-file deleted file mode 100755 index 79dfeca21..000000000 --- a/commands.beta/is-file +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -function is_file() ( - source "$DOROTHY/sources/bash.bash" - - test -f "$1" -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_file "$@" -fi diff --git a/commands.beta/is-float b/commands.beta/is-float index bade8a176..eeed8d64f 100755 --- a/commands.beta/is-float +++ b/commands.beta/is-float @@ -4,10 +4,11 @@ function is_float() ( source "$DOROTHY/sources/bash.bash" # https://stackoverflow.com/a/29234612/130638 - [[ $1 == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]] || return # explicit return with [[ required for bash v3 + [[ $1 == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_float "$@" fi diff --git a/commands.beta/is-neither b/commands.beta/is-neither deleted file mode 100755 index 2d0271b37..000000000 --- a/commands.beta/is-neither +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -function is_neither() ( - source "$DOROTHY/sources/bash.bash" - - local arg - for arg in "${@:2}"; do - if test "$1" = "$arg"; then - return 1 - fi - done - - return 0 -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_neither "$@" -fi diff --git a/commands.beta/is-nonempty-file b/commands.beta/is-nonempty-file deleted file mode 100755 index a7b1314b4..000000000 --- a/commands.beta/is-nonempty-file +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -function is_nonempty_file() ( - source "$DOROTHY/sources/bash.bash" - source "$(type -P sudo-helper)" - - # ===================================== - # Arguments - - function help { - cat <<-EOF >/dev/stderr - ABOUT: - Checks if is a non-empty file. - - USAGE: - is-nonempty-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 were non-empty files. - [1] if any s were not a non-empty file. - EOF - if test "$#" -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 test "$#" -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 test "${#option_paths[@]}" -eq 0; then - help "No s provided." - fi - - # ===================================== - # Action - - function __are_nonempty_files { - local path - for path in "$@"; do - test -s "$path" || return - done - return 0 - } - - # if need sudo, use visa sudo - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -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[@]}" - return -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_nonempty_file "$@" -fi diff --git a/commands.beta/is-password-usable b/commands.beta/is-password-usable index 317519850..2b72e40ea 100755 --- a/commands.beta/is-password-usable +++ b/commands.beta/is-password-usable @@ -22,9 +22,9 @@ function is_password_usable() ( Defaults to the current user. QUIRKS: - Note that $(echo-style --code='usermod -L [user]') which is used to make the user only a share-user (not login), will cause the passsword status to become locked L, and thus return failure. + Note that $(echo-style --code='usermod -L [user]') which is used to make the user only a share-user (not login), will cause the password status to become locked L, and thus return failure. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,14 +32,14 @@ function is_password_usable() ( # process local item option_user='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_user"; then + if [[ -z $option_user ]]; then option_user="$item" else help "An unrecognised argument was provided: $item" @@ -49,7 +49,7 @@ function is_password_usable() ( done # ensure url - if test -z "$option_user"; then + if [[ -z $option_user ]]; then option_user="$(whoami)" fi @@ -60,6 +60,6 @@ function is_password_usable() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_password_usable "$@" fi diff --git a/commands.beta/is-prefix b/commands.beta/is-prefix index 530d5eb5d..9bd38877a 100755 --- a/commands.beta/is-prefix +++ b/commands.beta/is-prefix @@ -12,10 +12,11 @@ function is_prefix() ( # echo-regexp -q "^$needle" -- "$haystack" # better way: - test "$needle" = "${haystack:0:${#needle}}" + [[ $needle == "${haystack:0:${#needle}}" ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_prefix "$@" fi diff --git a/commands.beta/is-root b/commands.beta/is-root deleted file mode 100755 index 5809106b9..000000000 --- a/commands.beta/is-root +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -function is_root() ( - source "$DOROTHY/sources/bash.bash" - - # "$(id -u)" -eq 0 - test "$(whoami)" = 'root' -) - -# fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_root "$@" -fi diff --git a/commands.beta/is-shapeshifter b/commands.beta/is-shapeshifter index c6c551140..14937fead 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 @@ -162,7 +162,7 @@ function is_shapeshifter() ( OPTIONS: $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -170,7 +170,7 @@ function is_shapeshifter() ( # process our own arguments, delegate everything else to stdinargs local item option_quiet='yes' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -181,7 +181,7 @@ function is_shapeshifter() ( '--no-quiet'* | '--quiet'*) option_quiet="$(get-flag-value --affirmative --fallback="$option_quiet" -- "$item")" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -195,7 +195,7 @@ function is_shapeshifter() ( # Action local found='no' - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then function on_input { local input="$1" if __is_shapeshifter -- "$input"; then @@ -215,15 +215,16 @@ function is_shapeshifter() ( } fi function on_finish { - test "$found" = 'yes' + [[ $found == 'yes' ]] + return } stdinargs "${option_args[@]}" ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_shapeshifter_test else is_shapeshifter "$@" diff --git a/commands.beta/is-streaming b/commands.beta/is-streaming index 15a1049a1..5d02c18de 100755 --- a/commands.beta/is-streaming +++ b/commands.beta/is-streaming @@ -5,10 +5,11 @@ function is_streaming() ( source "$DOROTHY/sources/bash.bash" - test "$(osascript -e 'tell application "System Events" to (name of processes) contains "Twitch Studio"')" = 'true' + [[ "$(osascript -e 'tell application "System Events" to (name of processes) contains "Twitch Studio"')" == 'true' ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_streaming "$@" fi diff --git a/commands.beta/is-suffix b/commands.beta/is-suffix index 74f518626..a14cec98a 100755 --- a/commands.beta/is-suffix +++ b/commands.beta/is-suffix @@ -9,13 +9,18 @@ function is_suffix() ( haystack="${2:?"USAGE: is-suffix "}" # oldschool way: - # echo-regexp -q "$needle\$" -- "$haystack" + # echo-regexp -q "$needle\$" -- "$haystack" <-- doesn't work, as $needle is not escaped + # [[ $haystack =~ ^.*$needle$ ]] <-- doesn't work, as $needle is not escaped # better way: - test "$needle" = "${haystack:${#needle}*-1}" + local suffix + local -i length=${#needle} + suffix="$(__substr "$haystack" $((length * -1)))" + [[ $needle == "$suffix" ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_suffix "$@" fi diff --git a/commands.beta/is-yum b/commands.beta/is-yum index 0074dcec7..74cccf90c 100755 --- a/commands.beta/is-yum +++ b/commands.beta/is-yum @@ -14,7 +14,7 @@ function is_yum() ( USAGE: is-yum EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function is_yum() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,6 +39,6 @@ function is_yum() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_yum "$@" fi diff --git a/commands.beta/itunes-owners b/commands.beta/itunes-owners index 97a5e86f8..ffd98fdf3 100755 --- a/commands.beta/itunes-owners +++ b/commands.beta/itunes-owners @@ -15,7 +15,7 @@ function itunes_owners() ( USAGE: itunes-owners EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function itunes_owners() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -66,7 +66,7 @@ function itunes_owners() ( echo-style --header2="Fetching owners to $owner_list" while read -r song; do # check - if test ! -f "$song"; then + if [[ ! -f $song ]]; then echo-style "$song" ' ' --notice='is missing' __print_lines "$song" >>"$missing_list" continue @@ -76,7 +76,7 @@ function itunes_owners() ( owner="$(ffprobe -i "$song" 2>&1 | echo-regexp -o --regexp='account_id\s*:\s*(.+)' --replace='$1')" # write - if test -n "$owner"; then + if [[ -n $owner ]]; then echo-style "$song" ' ' --green='owner is' ' ' --bold="$owner" printf '%s\t%s' "$owner" "$song" >>"$owner_list" # else @@ -105,6 +105,6 @@ function itunes_owners() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then itunes_owners "$@" fi diff --git a/commands.beta/mac-address-helper b/commands.beta/mac-address-helper index d6e02212f..7f23e4f62 100755 --- a/commands.beta/mac-address-helper +++ b/commands.beta/mac-address-helper @@ -25,7 +25,7 @@ function mac_address_helper() ( Get the MAC address of the specified network interface . EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -33,7 +33,7 @@ function mac_address_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,10 +57,10 @@ function mac_address_helper() ( } function act_apply { local interface="${1-}" mac="${2-}" - if test -z "$interface"; then + if [[ -z $interface ]]; then help ' was missing for: ' --code='mac-address-helper apply ' $'\n' 'Typically ' --code='en0' ' or ' --code='p2p0' ' is used.' fi - if test -z "$mac"; then + if [[ -z $mac ]]; then help ' was missing for: ' --code='mac-address-helper apply ' $'\n' 'You can generate one using: ' --code='mac-address-helper new' fi sudo-helper -- /system/Library/PrivateFrameworks/Apple80211.framework/Resources/airport --disassociate @@ -71,7 +71,7 @@ function mac_address_helper() ( } function act_get { local interface="${1-}" - if test -z "$interface"; then + if [[ -z $interface ]]; then help ' was missing for: ' --code='mac-address-helper get ' $'\n' 'Typically ' --code='en0' ' or ' --code='p2p0' ' is used.' fi sudo-helper -- ifconfig "$interface" ether | echo-regexp -fon --regexp='ether ([\w\d:]+)' --replace='$1' @@ -80,7 +80,7 @@ function mac_address_helper() ( # ===================================== # Act - if test "$(type -t "act_$action")" = 'function'; then + if [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" "${option_args[@]}" else echo-style --stderr --error1="Action not yet implemented: " --code-error1="$action" @@ -89,6 +89,6 @@ function mac_address_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then mac_address_helper "$@" fi diff --git a/commands.beta/macos-settings b/commands.beta/macos-settings index 6e0d74bfe..d3da09604 100755 --- a/commands.beta/macos-settings +++ b/commands.beta/macos-settings @@ -23,7 +23,7 @@ function macos_settings() ( USAGE: macos-settings EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function macos_settings() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -81,7 +81,7 @@ function macos_settings() ( updatedaily "Check for software updates daily" \ devcrashes "Show debug information when an application crashes" ) - if test "${#choices[@]}" -eq 0; then + if [[ ${#choices[@]} -eq 0 ]]; then return 0 fi @@ -90,7 +90,7 @@ function macos_settings() ( defaults delete com.apple.dock autohide-time-modifier &>/dev/null || : - if is-needle dockside -- "${choices[@]}"; then + if is-needle --needle='dockside' -- "${choices[@]}"; then local dock_side dock_side="$( choose \ @@ -101,9 +101,9 @@ function macos_settings() ( __print_line fi - if is-needle dockhide -- "${choices[@]}"; then + if is-needle --needle='dockhide' -- "${choices[@]}"; then defaults write com.apple.dock autohide -bool true - if is-needle dockinstant -- "${choices[@]}"; then + if is-needle --needle='dockinstant' -- "${choices[@]}"; then defaults write com.apple.dock autohide-delay -float 0 else defaults delete com.apple.dock autohide-delay &>/dev/null || : @@ -112,23 +112,23 @@ function macos_settings() ( defaults delete com.apple.dock autohide &>/dev/null || : fi - if is-needle dockmin -- "${choices[@]}"; then + if is-needle --needle='dockmin' -- "${choices[@]}"; then defaults write com.apple.dock minimize-to-application -bool true else defaults delete com.apple.dock minimize-to-application &>/dev/null || : fi - if is-needle docksmall -- "${choices[@]}"; then + if is-needle --needle='docksmall' -- "${choices[@]}"; then defaults write com.apple.dock tilesize -int 32 fi - if is-needle groupwin -- "${choices[@]}"; then + if is-needle --needle='groupwin' -- "${choices[@]}"; then defaults write com.apple.dock expose-group-apps -bool true else defaults delete com.apple.dock expose-group-apps &>/dev/null || : fi - if is-needle manualspaces -- "${choices[@]}"; then + if is-needle --needle='manualspaces' -- "${choices[@]}"; then defaults write com.apple.dock mru-spaces -bool false else defaults delete com.apple.dock mru-spaces &>/dev/null || : @@ -137,29 +137,29 @@ function macos_settings() ( # ------------------------------------- # Safari - if is-needle safarinopass -- "${choices[@]}"; then + if is-needle --needle='safarinopass' -- "${choices[@]}"; then defaults write com.apple.Safari AutoFillPasswords -bool false else defaults delete com.apple.Safari AutoFillPasswords &>/dev/null || : fi - if is-needle safarinohome -- "${choices[@]}"; then + if is-needle --needle='safarinohome' -- "${choices[@]}"; then defaults write com.apple.Safari HomePage -string 'about:blank' fi - if is-needle safarifullurls -- "${choices[@]}"; then + if is-needle --needle='safarifullurls' -- "${choices[@]}"; then defaults write com.apple.Safari ShowFullURLInSmartSearchField -bool true else defaults delete com.apple.Safari ShowFullURLInSmartSearchField &>/dev/null || : fi - if is-needle safarinoopen -- "${choices[@]}"; then + if is-needle --needle='safarinoopen' -- "${choices[@]}"; then defaults write com.apple.Safari AutoOpenSafeDownloads -bool false else defaults delete com.apple.Safari AutoOpenSafeDownloads &>/dev/null || : fi - if is-needle safarinobar -- "${choices[@]}"; then + if is-needle --needle='safarinobar' -- "${choices[@]}"; then defaults write com.apple.Safari ShowFavoritesBar-v2 -bool false else defaults delete write com.apple.Safari ShowFavoritesBar-v2 &>/dev/null || : @@ -168,45 +168,45 @@ function macos_settings() ( # ------------------------------------- # Finder - if is-needle finderhome -- "${choices[@]}"; then + if is-needle --needle='finderhome' -- "${choices[@]}"; then defaults write com.apple.finder NewWindowTargetPath "file://$HOME" fi # https://software.com/mac/tweaks/show-file-extensions-in-finder - if is-needle allextensions -- "${choices[@]}"; then + if is-needle --needle='allextensions' -- "${choices[@]}"; then defaults write NSGlobalDomain AppleShowAllExtensions -bool true else defaults delete NSGlobalDomain AppleShowAllExtensions &>/dev/null || : fi # https://software.com/mac/tweaks/show-all-files-in-finder - if is-needle showhidden -- "${choices[@]}"; then + if is-needle --needle='showhidden' -- "${choices[@]}"; then defaults write com.apple.finder AppleShowAllFiles -bool true else defaults delete com.apple.finder AppleShowAllFiles &>/dev/null || : fi # http://osxdaily.com/2012/04/11/disable-the-file-extension-change-warning-in-mac-os-x/ - if is-needle hideextwarn -- "${choices[@]}"; then + if is-needle --needle='hideextwarn' -- "${choices[@]}"; then defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false else defaults delete com.apple.finder FXEnableExtensionChangeWarning &>/dev/null || : fi - if is-needle showstatusbar -- "${choices[@]}"; then + if is-needle --needle='showstatusbar' -- "${choices[@]}"; then defaults write com.apple.finder ShowStatusBar -bool false else defaults delete com.apple.finder ShowStatusBar &>/dev/null || : fi - if is-needle hidetrashwarn -- "${choices[@]}"; then + if is-needle --needle='hidetrashwarn' -- "${choices[@]}"; then defaults write com.apple.finder WarnOnEmptyTrash -bool false else defaults delete write com.apple.finder WarnOnEmptyTrash &>/dev/null || : fi # https://software.com/mac/tweaks/hide-desktop-icons - if is-needle hidedesktop -- "${choices[@]}"; then + if is-needle --needle='hidedesktop' -- "${choices[@]}"; then defaults write com.apple.finder CreateDesktop -bool false defaults write com.apple.finder ShowRemovableMediaOnDesktop -bool false defaults write com.apple.finder ShowHardDrivesOnDesktop -bool false @@ -224,27 +224,27 @@ function macos_settings() ( # ------------------------------------- # Global - if is-needle screenshotdir -- "${choices[@]}"; then + if is-needle --needle='screenshotdir' -- "${choices[@]}"; then __mkdirp "$HOME/Desktop/Screenshots" defaults write com.apple.screencapture location "$HOME/Desktop/Screenshots" else defaults delete com.apple.screencapture location &>/dev/null || : fi - if is-needle disableautocorrect -- "${choices[@]}"; then + if is-needle --needle='disableautocorrect' -- "${choices[@]}"; then defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false else defaults delete NSGlobalDomain NSAutomaticSpellingCorrectionEnabled &>/dev/null || : fi - if is-needle updatedaily -- "${choices[@]}"; then + if is-needle --needle='updatedaily' -- "${choices[@]}"; then defaults write com.apple.SoftwareUpdate ScheduleFrequency -int 1 else defaults delete com.apple.SoftwareUpdate ScheduleFrequency &>/dev/null || : fi # https://en.wikipedia.org/wiki/Apple_Developer_Tools#CrashReporterPrefs - if is-needle devcrashes -- "${choices[@]}"; then + if is-needle --needle='devcrashes' -- "${choices[@]}"; then defaults write com.apple.CrashReporter DialogType -string 'developer' else defaults delete com.apple.CrashReporter DialogType &>/dev/null || : @@ -259,6 +259,6 @@ function macos_settings() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then macos_settings "$@" fi diff --git a/commands.beta/macos-state b/commands.beta/macos-state index d759ed31d..974bb3265 100755 --- a/commands.beta/macos-state +++ b/commands.beta/macos-state @@ -28,7 +28,7 @@ function macos_state() ( # To restore configuration from a remote machine: macos-state restore --root='username@hostname:/Volumes/System' EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -36,7 +36,7 @@ function macos_state() ( # process local item action='' local_root='' backup_root='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,7 +45,7 @@ function macos_state() ( '--backups='*) backup_root="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -80,7 +80,7 @@ function macos_state() ( # Use time machine? local backup_type='fs' # tm, fs, remote local time_backup_volume='' time_backup_machine='' - if test "$action" = 'restore' -a -z "$backup_root"; then + if [[ $action == 'restore' && -z $backup_root ]]; then __print_line if confirm --linger --bool --ppid=$$ -- 'Do you want to restore from Time Machine?' \ @@ -89,7 +89,7 @@ function macos_state() ( "- The Time Machine Backup Volume is already mounted" then backup_root="$(tmutil latestbackup || :)" - if test ! -d "$backup_root"; then + if [[ ! -d $backup_root ]]; then __print_lines '' 'Unable to find the Time Machine backup automatically, attempting manual resolution...' time_backup_volume="$( choose --linger --required \ @@ -103,7 +103,7 @@ function macos_state() ( )" backup_root="$time_backup_machine/Latest" fi - if test -d "$backup_root"; then + if [[ -d $backup_root ]]; then __print_lines "Time Machine Backup: $backup_root" backup_type='tm' else @@ -115,9 +115,9 @@ function macos_state() ( # Use cloud location if it exists local cloud_root='' - if test "$action" = 'restore' -a -z "$backup_root"; then + if [[ $action == 'restore' && -z $backup_root ]]; then cloud_root="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Apps/macos-state" - if test -d "$cloud_root"; then + if [[ -d $cloud_root ]]; then backup_root="$(fs-join -- "$cloud_root" "$local_root")" fi fi @@ -126,7 +126,7 @@ function macos_state() ( __print_line local question='' cloud_root="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Apps/macos-state" - if test "$action" = 'backup'; then + if [[ $action == 'backup' ]]; then question="Where will the backups be stored?" else question="Where are the backups stored?" @@ -138,7 +138,7 @@ function macos_state() ( )" # Detection - if [[ "Time Machine Backups" == *"$backup_root"* ]] || [[ "Backups.backupdb" == *"$backup_root"* ]]; then + if [[ "Time Machine Backups" == *"$backup_root"* || "Backups.backupdb" == *"$backup_root"* ]]; then # tm backup_type='tm' elif [[ $backup_root == *"@"* ]]; then @@ -156,11 +156,11 @@ function macos_state() ( local potential_root='' if [[ $backup_root != *"Volumes"* ]]; then # if it is backup, use our current location - if test "$action" = 'backup'; then + if [[ $action == 'backup' ]]; then backup_root="$(fs-join -- "$backup_root" "$local_root")" else # if it is restore, then ask the user, if supported - if test "$backup_type" = 'remote'; then + if [[ $backup_type == 'remote' ]]; then # fail on remote potential_root="$(fs-join -- "$backup_root" "$local_root")" echo-style \ @@ -226,7 +226,7 @@ function macos_state() ( EOF if is-present -- "$from"; then - if test "$backup_type" = 'remote'; then + if [[ $backup_type == 'remote' ]]; then cpr -- "$from" "$to" else do_replace "$to" @@ -250,10 +250,10 @@ function macos_state() ( $to EOF - if test "$backup_type" = 'tm'; then + if [[ $backup_type == 'tm' ]]; then do_replace "$to" tmutil restore -v "$from" "$to" - elif test "$backup_type" = 'remote'; then + elif [[ $backup_type == 'remote' ]]; then do_replace "$to" cpr -- "$from" "$to" elif is-present -- "$from"; then @@ -271,9 +271,9 @@ function macos_state() ( echo-style --h1 "$title" if confirm --linger --bool --ppid=$$ -- "Do you want to $action $title"; then for path in "${paths[@]}"; do - if test "$action" = 'backup'; then + if [[ $action == 'backup' ]]; then do_backup "$path" - elif test "$action" = 'restore'; then + elif [[ $action == 'restore' ]]; then do_restore "$path" fi done @@ -567,7 +567,7 @@ function macos_state() ( setup-mac clean # Finish - if test "${#warnings[@]}" -eq 0; then + if [[ ${#warnings[@]} -eq 0 ]]; then echo-style --success="Completed successfully" else { @@ -578,6 +578,6 @@ function macos_state() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then macos_state "$@" fi diff --git a/commands.beta/macos-theme b/commands.beta/macos-theme index 3266ba17f..5ddde24ff 100755 --- a/commands.beta/macos-theme +++ b/commands.beta/macos-theme @@ -37,7 +37,7 @@ function macos_theme() ( VSCODE_THEME_LIGHT = $VSCODE_THEME_LIGHT VSCODE_THEME_DARK = $VSCODE_THEME_DARK EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -45,14 +45,14 @@ function macos_theme() ( # process local item theme='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$theme"; then + if [[ -z $theme ]]; then theme="$item" else help "An unrecognised argument was provided: $item" @@ -67,19 +67,19 @@ function macos_theme() ( fi # check - if test -z "$WALLPAPER_DIRECTORY_LIGHT" -o ! -d "$WALLPAPER_DIRECTORY_LIGHT"; then + if [[ -z $WALLPAPER_DIRECTORY_LIGHT || ! -d $WALLPAPER_DIRECTORY_LIGHT ]]; then help "Missing directory [WALLPAPER_DIRECTORY_LIGHT] = [$WALLPAPER_DIRECTORY_LIGHT]" fi - if test -z "$WALLPAPER_DIRECTORY_DARK" -o ! -d "$WALLPAPER_DIRECTORY_DARK"; then + if [[ -z $WALLPAPER_DIRECTORY_DARK || ! -d $WALLPAPER_DIRECTORY_DARK ]]; then help "Missing directory [WALLPAPER_DIRECTORY_DARK] = [$WALLPAPER_DIRECTORY_DARK]" fi - if test -z "$VSCODE_SETTINGS_FILE" -o ! -f "$VSCODE_SETTINGS_FILE"; then + if [[ -z $VSCODE_SETTINGS_FILE || ! -f $VSCODE_SETTINGS_FILE ]]; then help "Missing file [VSCODE_SETTINGS_FILE] = [$VSCODE_SETTINGS_FILE]" fi - if test -z "$VSCODE_THEME_LIGHT"; then + if [[ -z $VSCODE_THEME_LIGHT ]]; then help "Missing value [VSCODE_THEME_LIGHT] = [$VSCODE_THEME_LIGHT]" fi - if test -z "$VSCODE_THEME_DARK"; then + if [[ -z $VSCODE_THEME_DARK ]]; then help "Missing value [VSCODE_THEME_DARK] = [$VSCODE_THEME_DARK]" fi @@ -124,7 +124,7 @@ function macos_theme() ( osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to $dark_mode" } - if test "$theme" = 'dark'; then + if [[ $theme == 'dark' ]]; then # enable dark mode macos_dark_mode_enable 'yes' vscode_set_theme "$VSCODE_THEME_DARK" @@ -138,6 +138,6 @@ function macos_theme() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then macos_theme "$@" fi diff --git a/commands.beta/mail-sync b/commands.beta/mail-sync index 12f55ea38..24059ec17 100755 --- a/commands.beta/mail-sync +++ b/commands.beta/mail-sync @@ -39,7 +39,7 @@ function mail_sync() ( --type2=<$(echo-join '|' -- "${types[@]}")> If not supplied, you will be prompted. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -47,7 +47,7 @@ function mail_sync() ( # process, @todo rewrite with option_ prefix local item args=() user1='' user2='' password1='' password2='' type1='' type2='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -79,13 +79,13 @@ function mail_sync() ( --question='Who is the host of the origin IMAP server?' \ --default="$type1" -- "${types[@]}" )" - if test "$type1" = 'gmail'; then + if [[ $type1 == 'gmail' ]]; then args+=( --host1 imap.gmail.com --gmail1 --compress1 ) - elif test "$type1" = 'icloud'; then + elif [[ $type1 == 'icloud' ]]; then args+=( --host1 imap.mail.me.com --ssl1 --port1 993 @@ -128,13 +128,13 @@ function mail_sync() ( --question='Who is the host of the target IMAP server?' \ --default="$type2" -- "${types[@]}" )" - if test "$type2" = 'gmail'; then + if [[ $type2 == 'gmail' ]]; then args+=( --host2 imap.gmail.com --gmail2 --compress2 ) - elif test "$type2" = 'icloud'; then + elif [[ $type2 == 'icloud' ]]; then args+=( --host2 imap.mail.me.com --ssl2 --port1 993 @@ -172,7 +172,7 @@ function mail_sync() ( ) # adjustments - if test "$type1" = 'gmail' -a "$type2" = 'icloud'; then + if [[ $type1 == 'gmail' && $type2 == 'icloud' ]]; then args+=( # Business Gmail --folderlast '[Gmail]/All Mail' --f1f2 '[Gmail]/All Mail=Archive' @@ -190,7 +190,7 @@ function mail_sync() ( # --usecache # --useuid local __imapsync_status - while true; do + while :; do __imapsync_status=0 && imapsync \ --automap \ --skipcrossduplicates \ @@ -198,10 +198,10 @@ function mail_sync() ( --syncinternaldates \ --nofoldersizes --nofoldersizesatend --no-modulesversion --nolog \ "${args[@]}" || __imapsync_status=$? - if test "$__imapsync_status" -eq 0; then + if [[ $__imapsync_status -eq 0 ]]; then echo-style --success='Success!' break - elif test "$__imapsync_status" -eq 143; then + elif [[ $__imapsync_status -eq 143 ]]; then echo-style --error="Manually terminated [$__imapsync_status]..." >/dev/stderr break return "$__imapsync_status" @@ -212,6 +212,6 @@ function mail_sync() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then mail_sync "$@" fi diff --git a/commands.beta/pdf-decrypt b/commands.beta/pdf-decrypt index 681936575..55819da1d 100755 --- a/commands.beta/pdf-decrypt +++ b/commands.beta/pdf-decrypt @@ -9,63 +9,78 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item files=() password='' - while test "$#" -ne 0; do + 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 ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then pdf_decrypt "$@" fi diff --git a/commands.beta/researchgate-rename b/commands.beta/researchgate-rename index b12826b1f..472347bdf 100755 --- a/commands.beta/researchgate-rename +++ b/commands.beta/researchgate-rename @@ -14,7 +14,7 @@ function researchgate_rename() ( USAGE: researchgate-rename EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function researchgate_rename() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -52,13 +52,13 @@ function researchgate_rename() ( pdftotext -layout -eol unix "$file" "$f" while read -r line; do # echo $line - if test -z "$id"; then + if [[ -z $id ]]; then # find id id="$(__print_lines "$line" | python-regex "$regex")" - elif test -z "$name"; then + elif [[ -z $name ]]; then # trim possibly invalid chars from name name="$(__print_lines "$line" | echo-trim-special --stdin)" - elif test -z "$year"; then + elif [[ -z $year ]]; then year="$(__print_lines "$line" | python-regex '[^\d](\d\d\d\d)[^\d]')" else break @@ -69,8 +69,8 @@ function researchgate_rename() ( #id="$(python-regex "$f" "$regex")" #url="https://www.researchgate.net/publication/$id" - if test -n "$id" -a -n "$name"; then - if test -n "$year"; then + if [[ -n $id && -n $name ]]; then + if [[ -n $year ]]; then nf="$year - $name [$id].pdf" else nf="$name [$id].pdf" @@ -78,14 +78,14 @@ function researchgate_rename() ( dir="$(dirname "$file")" filename="$(basename "$file")" np="$dir/$nf" - if test "$file" = "$np"; then + if [[ $file == "$np" ]]; then cat <<-EOF $filename => already named correctly EOF else dup=1 - while test -f "$np"; do + while [[ -f $np ]]; do if is-same summary --algorithm=sha256sum -- "$file" "$np"; then rm "$file" cat <<-EOF @@ -99,7 +99,7 @@ function researchgate_rename() ( nf="${nf%.pdf*} [$dup].pdf" np="$dir/$nf" done - if test -n "$np"; then + if [[ -n $np ]]; then mv "$file" "$np" cat <<-EOF $filename @@ -112,6 +112,6 @@ function researchgate_rename() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then researchgate_rename "$@" fi diff --git a/commands.beta/rm-deno-cache b/commands.beta/rm-deno-cache index d84e4c3f4..da7c1ebb1 100755 --- a/commands.beta/rm-deno-cache +++ b/commands.beta/rm-deno-cache @@ -14,7 +14,7 @@ function rm_deno_cache() ( USAGE: rm-deno-cache EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function rm_deno_cache() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -36,7 +36,7 @@ function rm_deno_cache() ( # Action # ensure DENO_DIR exists - if test -z "${DENO_DIR-}"; then + if [[ -z ${DENO_DIR-} ]]; then DENO_DIR="$HOME/Library/Caches/deno" fi @@ -45,6 +45,6 @@ function rm_deno_cache() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then rm_deno_cache "$@" fi diff --git a/commands.beta/rm-vmware b/commands.beta/rm-vmware-fusion similarity index 91% rename from commands.beta/rm-vmware rename to commands.beta/rm-vmware-fusion index 6ffcb2dfe..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=( @@ -35,7 +35,7 @@ function rm_vmware() ( FILES: $(echo-lines -- "${paths[@]}") EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -43,7 +43,7 @@ function rm_vmware() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -60,6 +60,6 @@ function rm_vmware() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - rm_vmware "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + setup_util_vmware_fusion "$@" fi diff --git a/commands.beta/setup-server b/commands.beta/setup-server index eadbaa5f9..230fedbdf 100755 --- a/commands.beta/setup-server +++ b/commands.beta/setup-server @@ -121,7 +121,7 @@ function setup_server() ( function setup_owner { # prepare local item option_reload='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -135,13 +135,13 @@ function setup_server() ( done # check configuration - if test -z "$SHARE_USER" -o -z "$SHARE_GROUP"; then + if [[ -z $SHARE_USER || -z $SHARE_GROUP ]]; then echo-error 'SHARE_USER and SHARE_GROUP must be defined' return 1 fi # handle share user if desired - if test -n "$SHARE_USER"; then + if [[ -n $SHARE_USER ]]; then # create share user if necessary if ! is-user -- "$SHARE_USER"; then # create user @@ -149,7 +149,7 @@ function setup_server() ( fi # ensure correct share user password - if test -n "$SHARE_PASSWORD"; then + if [[ -n $SHARE_PASSWORD ]]; then printf '%s:%s\n' "$SHARE_USER" "$SHARE_PASSWORD" | sudo-helper -- chpasswd if __command_exists -- smbpasswd; then printf '%s\n%s\n' "$SHARE_PASSWORD" "$SHARE_PASSWORD" | sudo-helper -- smbpasswd -sa "$SHARE_USER" @@ -162,7 +162,7 @@ function setup_server() ( fi # create share group if desired and necessary - if test -n "$SHARE_GROUP" && ! is-group -- "$SHARE_GROUP"; then + if [[ -n $SHARE_GROUP ]] && ! is-group -- "$SHARE_GROUP"; then sudo-helper -- groupadd "$SHARE_GROUP" || : fi @@ -186,14 +186,14 @@ function setup_server() ( # add the user to the group sudo-helper -- gpasswd -a "$user" "$group" # if it affected us, we have to reload - if test "$user" = "$me"; then + if [[ $user == "$me" ]]; then option_reload='yes' fi done done # check if reload is necessary - if test "$option_reload" = 'yes'; then + if [[ $option_reload == 'yes' ]]; then echo-style \ --success="The current user [$me] has been added to new groups." --newline \ --notice='You must logout or reboot for the change to apply.' @@ -202,7 +202,7 @@ function setup_server() ( # assert owner local OWNER_MOUNT=() - if test -n "$SHARE_USER" -a -n "$SHARE_GROUP"; then + if [[ -n $SHARE_USER && -n $SHARE_GROUP ]]; then assert_linux if is-user -- "$SHARE_USER" && is-group -- "$SHARE_GROUP" && is-user-in-group --user="$me" --group="$SHARE_GROUP"; then owner="$(get-user-id "$SHARE_USER"):$(get-group-id "$SHARE_GROUP")" @@ -227,63 +227,63 @@ function setup_server() ( # https://support.plex.tv/articles/201105343-advanced-hidden-server-settings/ # https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/ temp='/var/lib/plexmediaserver' - if is-present -- "$temp" && test -n "$PLEX_HOME"; then - if is-missing -- "$PLEX_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp" && [[ -n $PLEX_HOME ]]; then + if is-missing --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$PLEX_HOME"; then sudo-helper -- mv -v "$temp" "$PLEX_HOME" else echo-style --warning="Both [$temp] and [$PLEX_HOME] exist" fi fi - if is-present -- "$temp"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then paths+=("$temp") fi - if is-present -- "$PLEX_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$PLEX_HOME"; then paths+=("$PLEX_HOME") fi # syncthing # https://docs.syncthing.net/users/config.html temp="$XDG_CONFIG_HOME/syncthing" - if is-present -- "$temp" && test -n "$SYNCTHING_HOME"; then - if is-missing -- "$SYNCTHING_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp" && [[ -n $SYNCTHING_HOME ]]; then + if is-missing --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$SYNCTHING_HOME"; then sudo-helper -- mv -v "$temp" "$SYNCTHING_HOME" else echo-style --warning="Both [$temp] and [$SYNCTHING_HOME] exist" fi fi - if is-present -- "$temp"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then paths+=("$temp") fi - if is-present -- "$SYNCTHING_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$SYNCTHING_HOME"; then paths+=("$SYNCTHING_HOME") fi temp="$HOME/Sync" - if is-present -- "$temp"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then paths+=("$temp") fi # transmission temp='/var/lib/transmission-daemon' - if is-present -- "$temp" && test -n "$TRANSMISSION_HOME"; then - if is-missing -- "$TRANSMISSION_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp" && [[ -n $TRANSMISSION_HOME ]]; then + if is-missing --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$TRANSMISSION_HOME"; then sudo-helper -- mv -v "$temp" "$TRANSMISSION_HOME" else echo-style --warning="Both [$temp] and [$TRANSMISSION_HOME] exist" fi fi - if is-present -- "$temp"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then paths+=("$temp") fi - if is-present -- "$TRANSMISSION_HOME"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$TRANSMISSION_HOME"; then paths+=("$TRANSMISSION_HOME") # clear the log as it goes on forever - if is-present -- "$TRANSMISSION_HOME/transmission.log"; then + if is-present --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$TRANSMISSION_HOME/transmission.log"; then sudo-helper -- truncate -s0 "$TRANSMISSION_HOME/transmission.log" fi fi # apply - if test "${#paths[@]}" -ne 0; then + if [[ ${#paths[@]} -ne 0 ]]; then eval-helper --no-quiet --wrap -- \ fs-own --sudo --optional --permissions="$perms" --user="$SHARE_USER" --group="$SHARE_GROUP" \ -- "${paths[@]}" @@ -307,25 +307,25 @@ function setup_server() ( fi # plex - if test -n "$PLEX_HOME"; then + if [[ -n $PLEX_HOME ]]; then temp="$PLEX_HOME/Preferences.xml" - if test -f "$temp"; then + if is-file --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then edit --wait --sudo -- "$temp" fi fi # syncthing - if test -n "$SYNCTHING_HOME"; then + if [[ -n $SYNCTHING_HOME ]]; then temp="$SYNCTHING_HOME/config.xml" - if test -f "$temp"; then + if is-file --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then edit --wait --sudo -- "$temp" fi fi # transmission - if test -n "$TRANSMISSION_HOME"; then + if [[ -n $TRANSMISSION_HOME ]]; then temp="$TRANSMISSION_HOME/settings.json" - if test -f "$temp"; then + if is-file --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp"; then edit --wait --sudo -- "$temp" fi fi @@ -371,7 +371,7 @@ function setup_server() ( # gocryptfs local temp temp="$(type -P gocryptfs 2>/dev/null || :)" - if test -n "$temp"; then + if [[ -n $temp ]]; then # ensure that it is accessible if ! sudo-helper --inherit --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$temp" --version; then setup-util-gocryptfs --upgrade @@ -386,7 +386,7 @@ function setup_server() ( # VPN function verify_nordvpn_killswitch { - # verify firewall, this is curcial for killswitch to work + # verify firewall, this is crucial for killswitch to work nordvpn set firewall on || : # or to prevent already enabled errors waiter 5 # this can take a while for changes to be reflected if ! nordvpn settings | echo-wait | grep --quiet --fixed-strings --regexp='Firewall: enabled'; then @@ -413,7 +413,7 @@ function setup_server() ( vpn_prefix="$(__print_lines "$vpn_address" | echo-regexp -o --regexp='^[0-9]+[.][0-9]+[.][0-9]+[.]')" clear_address="$(what-is-my-ip remote)" clear_prefix="$(__print_lines "$clear_address" | echo-regexp -o --regexp='^[0-9]+[.][0-9]+[.][0-9]+[.]')" - if test "$vpn_prefix" = "$clear_prefix"; then + if [[ $vpn_prefix == "$clear_prefix" ]]; then echo-style --success="Successfully connected [$clear_address] to NordVPN [$vpn_address]." else echo-style --error="Not connected [$clear_address] to NordVPN [$vpn_address]." @@ -525,7 +525,7 @@ function setup_server() ( fi # nordvpn dns - if test -n "$NORDVPN_DNS"; then + if [[ -n $NORDVPN_DNS ]]; then nordvpn set dns "$NORDVPN_DNS" else nordvpn set threatprotectionlite on @@ -561,7 +561,7 @@ function setup_server() ( # nordvpn whitelist add port "$nordvpn_port" # done - # verify that killswitch i spossible + # verify that killswitch is possible verify_nordvpn_killswitch # attempt nordvpn connect @@ -576,7 +576,7 @@ function setup_server() ( } waiter 60 - # verify that connetion and killswitch work + # verify that connection and killswitch work verify_nordvpn_connection fi @@ -598,21 +598,21 @@ function setup_server() ( # verify vpn eval_capture --statusvar=ec -- verify_nordvpn_connection - if test "$ec" -eq 2; then + if [[ $ec -eq 2 ]]; then # killswitch failed, we must reboot act_stop echo-style --warning='Failed to activate killswitch. Stopped all services. Ready for reboot:' echo-style --code='sudo reboot' return 1 - elif test "$ec" -ne 0; then + elif [[ $ec -ne 0 ]]; then # failed to connect, try again service-helper --stop --ignore -- "${services_vpn_dependent[@]}" eval_capture --statusvar=ec -- act_connect - if test "$ec" -eq 0; then + if [[ $ec -eq 0 ]]; then echo-style --success='Reconnected to NordVPN.' echo-style --notice='Resuming dependent services:' - # the [|| :] is to that we resume, rather than start, as the service may be dependent on another service such as samba or a mount, this functinality is more just for temporary vpn disconnections/reconnections + # the [|| :] is to that we resume, rather than start, as the service may be dependent on another service such as samba or a mount, this functionality is more just for temporary vpn disconnections/reconnections service-helper --start --ignore -- "${services_vpn_dependent[@]}" || : echo-style --success='Resumed dependent services.' else @@ -657,22 +657,22 @@ function setup_server() ( function act_mount { # volumes - if ! test -d '/Volumes'; then + if is-not-directory --user="$SHARE_USER" --group="$SHARE_GROUP" -- '/Volumes'; then sudo-helper -- mkdir -p '/Volumes' fs-own --sudo --user="$SHARE_USER" --group="$SHARE_GROUP" -- '/Volumes' fi # drive - if test "${#DRIVE_MOUNT[@]}" -ne 0; then + if [[ ${#DRIVE_MOUNT[@]} -ne 0 ]]; then eval-helper --no-quiet --wrap --shapeshifter \ -- mount-helper "${DRIVE_MOUNT[@]}" "${OWNER_MOUNT[@]}" --mount fi # vault - if test "${#VAULT_MOUNT[@]}" -ne 0; then + if [[ ${#VAULT_MOUNT[@]} -ne 0 ]]; then local vault_share vault_share="$(get-flag-value share -- "${VAULT_MOUNT[@]}")" - if test -n "$vault_share" -a ! -e "$vault_share"; then + if [[ -n $vault_share ]] && is-missing --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$vault_share"; then gocryptfs-helper new -- "$vault_share" fs-own --sudo --user="$SHARE_USER" --group="$SHARE_GROUP" -- "$vault_share" fi @@ -681,27 +681,27 @@ function setup_server() ( fi # samba - if test "${#SAMBA_MOUNT[@]}" -ne 0; then + if [[ ${#SAMBA_MOUNT[@]} -ne 0 ]]; then mount-helper "${SAMBA_MOUNT[@]}" "${OWNER_MOUNT[@]}" --mount fi } function act_unmount { # samba mounts service-helper --ignore --stop --status --logs -- "${services_samba_dependent[@]}" - if test "${#SAMBA_MOUNT[@]}" -ne 0; then + if [[ ${#SAMBA_MOUNT[@]} -ne 0 ]]; then mount-helper "${SAMBA_MOUNT[@]}" "${OWNER_MOUNT[@]}" --unmount --no-automount fi # vault service-helper --ignore --stop --status --logs -- "${services_vault_dependent[@]}" - if test "${#VAULT_MOUNT[@]}" -ne 0; then + if [[ ${#VAULT_MOUNT[@]} -ne 0 ]]; then eval-helper --no-quiet --wrap --shapeshifter \ -- mount-helper "${VAULT_MOUNT[@]}" "${OWNER_MOUNT[@]}" --unmount --no-automount fi # drive service-helper --ignore --stop --status --logs -- "${services_drive_dependent[@]}" - if test "${#DRIVE_MOUNT[@]}" -ne 0; then + if [[ ${#DRIVE_MOUNT[@]} -ne 0 ]]; then eval-helper --no-quiet --wrap --shapeshifter \ -- mount-helper "${DRIVE_MOUNT[@]}" "${OWNER_MOUNT[@]}" --unmount --no-automount fi @@ -787,7 +787,7 @@ function setup_server() ( check Check the internet (and optional VPN) connection is working. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -795,7 +795,7 @@ function setup_server() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -807,7 +807,7 @@ function setup_server() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -820,9 +820,9 @@ function setup_server() ( assert_linux # act - if test -z "$action"; then + if [[ -z $action ]]; then help 'No provided.' - elif test "$(type -t "act_$action")" = 'function'; then + elif [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" "${option_args[@]}" else echo-error "$0: Action not yet implemented: $action" @@ -831,7 +831,7 @@ function setup_server() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then setup_server "$@" fi diff --git a/commands.beta/svg-export b/commands.beta/svg-export index 718962f40..ee1fdf067 100755 --- a/commands.beta/svg-export +++ b/commands.beta/svg-export @@ -41,7 +41,7 @@ function svg_export() ( EXAMPLE: svg-export -- *.svg EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -49,7 +49,7 @@ function svg_export() ( # process local item files=() dpi='' scale='' background='white' format='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -69,7 +69,7 @@ function svg_export() ( done # if no files, fail - if test "${#files[@]}" -eq 0; then + if [[ ${#files[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -96,15 +96,15 @@ function svg_export() ( local suffix='' args=( --format "$format" ) - if test -n "$scale"; then + if [[ -n $scale ]]; then suffix+=" [scale=$scale]" args+=(--scale "$scale") fi - if test -n "$dpi"; then + if [[ -n $dpi ]]; then suffix+=" [dpi=$dpi]" args+=(--dpi "$dpi") fi - if test -n "$background"; then + if [[ -n $background ]]; then suffix+=" [bg=$background]" args+=(--background "$background") fi @@ -129,6 +129,6 @@ function svg_export() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then svg_export "$@" fi diff --git a/commands.beta/tmutil-helper b/commands.beta/tmutil-helper index 6c1c7df7f..01b940e44 100755 --- a/commands.beta/tmutil-helper +++ b/commands.beta/tmutil-helper @@ -21,7 +21,7 @@ function tmutil_helper() ( - system has run out of storage - iCloud syncs are failing EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -29,14 +29,14 @@ function tmutil_helper() ( # process local item action='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -78,6 +78,6 @@ function tmutil_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then tmutil_helper "$@" fi diff --git a/commands.beta/twitter-helper b/commands.beta/twitter-helper index 2c23a2599..9bb0cf745 100755 --- a/commands.beta/twitter-helper +++ b/commands.beta/twitter-helper @@ -16,7 +16,7 @@ function twitter_helper() ( ACTIONS: advertisers [twitter_advertiser_list.pdf] - Extract sthe usernames from the list of advertisers. + Extracts the usernames from the list of advertisers. Fetch the list from: https://twitter.com/settings/your_twitter_data/audiences block ... @@ -38,7 +38,7 @@ function twitter_helper() ( # Delete recent tweets twitter-helper tweets | twitter-helper delete EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -46,7 +46,7 @@ function twitter_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -62,7 +62,7 @@ function twitter_helper() ( done # check - if test -z "$action"; then + if [[ -z $action ]]; then help "No action was provided" fi @@ -100,7 +100,7 @@ function twitter_helper() ( function advertisers { local advertiser_list="${1:-'twitter_advertiser_list.pdf'}" - if test ! -f "$advertiser_list"; then + if [[ ! -f $advertiser_list ]]; then cat <<-EOF >/dev/stderr You need to download your advertiser list PDF file first. Get it from: https://twitter.com/settings/your_twitter_data/audiences @@ -171,6 +171,6 @@ function twitter_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then twitter_helper "$@" fi diff --git a/commands.beta/video-merge b/commands.beta/video-merge index 034cd169a..10a6dbe31 100755 --- a/commands.beta/video-merge +++ b/commands.beta/video-merge @@ -24,7 +24,7 @@ function video_merge() ( QUIRKS: If is not provided, the basename of the current directory will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function video_merge() ( # process local item inputs=() output='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,7 +45,7 @@ function video_merge() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$output"; then + if [[ -z $output ]]; then output="$item" else help "An unrecognised argument was provided: $item" @@ -59,12 +59,12 @@ function video_merge() ( for index in "${!inputs[@]}"; do # check existence input="${inputs[index]}" - if test ! -f "$input"; then + if [[ ! -f $input ]]; then # fail help "The input file does not exist: $input" else # extract extension - if test -z "$extension"; then + if [[ -z $extension ]]; then extension="$(fs-extension -- "$input")" fi # convert to absolute path @@ -73,7 +73,7 @@ function video_merge() ( done # ensure output - if test -z "$output"; then + if [[ -z $output ]]; then output="$(basename "$(pwd)").$extension" fi @@ -91,7 +91,7 @@ function video_merge() ( # confirm the output is not an input for index in "${!inputs[@]}"; do - if test "$output" = "${inputs[index]}"; then + if [[ $output == "${inputs[index]}" ]]; then echo-style --notice="The output file has been automatically trimmed from the input files." inputs[index]='' fi @@ -108,7 +108,7 @@ function video_merge() ( --file --extension='txt' )" for input in "${inputs[@]}"; do - if test -n "$input"; then + if [[ -n $input ]]; then __print_lines "file: $(echo-escape-command -- "$input")" >>"$temp_list" fi done @@ -128,6 +128,6 @@ function video_merge() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then video_merge "$@" fi diff --git a/commands.beta/wallhaven-helper b/commands.beta/wallhaven-helper index afdf8ec34..55b74e7c0 100755 --- a/commands.beta/wallhaven-helper +++ b/commands.beta/wallhaven-helper @@ -15,7 +15,7 @@ function wallhaven_helper() ( USAGE: wallhaven-helper [--user=...] [--key=...] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function wallhaven_helper() ( # process local item username='' apikey='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -36,13 +36,13 @@ function wallhaven_helper() ( done # essentials - if test -z "$username"; then + if [[ -z $username ]]; then username="$( ask --required \ --question="Enter the wallhaven username." )" fi - if test -z "$apikey"; then + if [[ -z $apikey ]]; then apikey="$( ask --required --password \ --question="Enter the wallhaven API Key for $username. You can get this from: https://wallhaven.cc/settings/account" @@ -81,6 +81,6 @@ function wallhaven_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then wallhaven_helper "$@" fi diff --git a/commands.beta/xps2pdf b/commands.beta/xps2pdf index e0107531d..e6d729ee3 100755 --- a/commands.beta/xps2pdf +++ b/commands.beta/xps2pdf @@ -30,7 +30,7 @@ function xps2pdf_() ( QUIRKS: If supported, the PDF files will be created with the same creation time as the original XPS files. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -38,7 +38,7 @@ function xps2pdf_() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -54,7 +54,7 @@ function xps2pdf_() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -85,6 +85,6 @@ function xps2pdf_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then xps2pdf_ "$@" fi diff --git a/commands.beta/ytd-helper b/commands.beta/ytd-helper index d657c3d3b..a0a3e6305 100755 --- a/commands.beta/ytd-helper +++ b/commands.beta/ytd-helper @@ -62,7 +62,7 @@ function ytd_helper() ( The URL to download the video from. --tool= - The tool to actually invoke, defaults to [ytd-dl] or [youtube-dl] based on avaliability. + The tool to actually invoke, defaults to [ytd-dl] or [youtube-dl] based on availability. -- [... tool options] Forward to the invoked . Refer to [youtube-dl --help]. @@ -103,7 +103,7 @@ function ytd_helper() ( --playlist Download with each playlist in its own folder, and each video in its own folder. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -119,7 +119,7 @@ function ytd_helper() ( # arguments local item tool='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -219,7 +219,7 @@ function ytd_helper() ( done # handle tool - if test -z "$tool"; then + if [[ -z $tool ]]; then if __command_exists -- 'yt-dlp'; then tool='yt-dlp' elif __command_exists -- 'youtube-dl'; then @@ -233,7 +233,7 @@ function ytd_helper() ( fi fi fi - if test -z "$tool" || __command_missing -- "$tool"; then + if [[ -z $tool ]] || __command_missing -- "$tool"; then echo-error "tool [$tool] is not available" return 3 # ESRCH 3 No such process fi @@ -246,6 +246,6 @@ function ytd_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then ytd_helper "$@" fi diff --git a/commands.deprecated/choose-menu b/commands.deprecated/choose-menu index e573e0275..5ce86bfc9 100755 --- a/commands.deprecated/choose-menu +++ b/commands.deprecated/choose-menu @@ -11,6 +11,6 @@ function choose_menu() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then choose_menu "$@" fi diff --git a/commands.deprecated/choose-option b/commands.deprecated/choose-option index 18a2dd0ac..c8803ffdf 100755 --- a/commands.deprecated/choose-option +++ b/commands.deprecated/choose-option @@ -11,6 +11,6 @@ function choose_option() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then choose_option "$@" fi diff --git a/commands.deprecated/echo-clear-line b/commands.deprecated/echo-clear-line index ceeb03e73..0c463a1be 100755 --- a/commands.deprecated/echo-clear-line +++ b/commands.deprecated/echo-clear-line @@ -11,6 +11,6 @@ function echo_clear_line() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_clear_line "$@" fi diff --git a/commands.deprecated/echo-color-enabled b/commands.deprecated/echo-color-enabled index 1e25f0ce8..2d7758cf6 100755 --- a/commands.deprecated/echo-color-enabled +++ b/commands.deprecated/echo-color-enabled @@ -11,6 +11,6 @@ function echo_color_enabled() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_color_enabled "$@" fi diff --git a/commands.deprecated/echo-element b/commands.deprecated/echo-element index 7a4ea48bf..48a1bd1b6 100755 --- a/commands.deprecated/echo-element +++ b/commands.deprecated/echo-element @@ -20,7 +20,7 @@ function echo_element() ( # process local item option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -52,6 +52,6 @@ function echo_element() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_element "$@" 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/echo-if-path b/commands.deprecated/echo-if-path new file mode 100755 index 000000000..eb2c2e580 --- /dev/null +++ b/commands.deprecated/echo-if-path @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +function echo_if_path() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='echo-if-path' --bold=' has been deprecated in favor of ' --code='echo-if-present' + + # ===================================== + # Action + + echo-if-present "$@" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + echo_if_path "$@" +fi diff --git a/commands.deprecated/echo-quiet-enabled b/commands.deprecated/echo-quiet-enabled index 882db3668..f2d6135d6 100755 --- a/commands.deprecated/echo-quiet-enabled +++ b/commands.deprecated/echo-quiet-enabled @@ -1,16 +1,16 @@ #!/usr/bin/env bash -function echo_quier_enabled() ( +function echo_quiet_enabled() ( source "$DOROTHY/sources/bash.bash" - dorothy-warnings add --code='echo-quier-enabled' --bold=' has been deprecated in favor of ' --code='get-terminal-quier-support' + dorothy-warnings add --code='echo-quiet-enabled' --bold=' has been deprecated in favor of ' --code='get-terminal-quiet-support' # ===================================== # Action - get-terminal-quier-support + get-terminal-quiet-support ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_quier_enabled "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + echo_quiet_enabled "$@" fi diff --git a/commands.deprecated/echo-title b/commands.deprecated/echo-title index 5156c2c72..7f653e663 100755 --- a/commands.deprecated/echo-title +++ b/commands.deprecated/echo-title @@ -11,6 +11,6 @@ function echo_title() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_title "$@" 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 80% rename from commands/get-array-count rename to commands.deprecated/get-array-count index d4cda57b3..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 @@ -17,7 +18,7 @@ function get_array_count() ( USAGE: get-array-count [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -25,7 +26,7 @@ function get_array_count() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -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 @@ -58,6 +59,6 @@ function get_array_count() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_array_count "$@" fi diff --git a/commands.beta/get-line-count b/commands.deprecated/get-line-count similarity index 78% rename from commands.beta/get-line-count rename to commands.deprecated/get-line-count index 0a985e656..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 @@ -16,7 +17,7 @@ function get_line_count() ( USAGE: echo-lines -- 1 2 | get-line-count EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -24,7 +25,7 @@ function get_line_count() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -41,6 +42,6 @@ function get_line_count() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_line_count "$@" fi diff --git a/commands/is-array-count b/commands.deprecated/is-array-count similarity index 77% rename from commands/is-array-count rename to commands.deprecated/is-array-count index 816d821b2..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 @@ -22,7 +23,7 @@ function is_array_count() ( An element of the array. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +31,7 @@ function is_array_count() ( # process local item option_size='' option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,7 +44,7 @@ function is_array_count() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_size"; then + if [[ -z $option_size ]]; then option_size="$item" else option_inputs+=("$item" "$@") @@ -55,7 +56,7 @@ function is_array_count() ( done # check - if test -z "$option_size"; then + if [[ -z $option_size ]]; then help ' is required' fi if ! is-integer -- "$option_size"; then @@ -65,14 +66,11 @@ function is_array_count() ( # ===================================== # Action - if test "$(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 -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_count "$@" fi diff --git a/commands/is-array-count-ge b/commands.deprecated/is-array-count-ge similarity index 75% rename from commands/is-array-count-ge rename to commands.deprecated/is-array-count-ge index c75056cbe..b88142a79 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 @@ -17,12 +18,12 @@ function is_array_count_ge() ( OPTIONS: | --size= - How many elements to expect at aminimum. + How many elements to expect at a minimum. An element of the array. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +31,7 @@ function is_array_count_ge() ( # process local item option_size='' option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,7 +44,7 @@ function is_array_count_ge() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_size"; then + if [[ -z $option_size ]]; then option_size="$item" else option_inputs+=("$item" "$@") @@ -55,7 +56,7 @@ function is_array_count_ge() ( done # check - if test -z "$option_size"; then + if [[ -z $option_size ]]; then help ' is required' fi if ! is-integer -- "$option_size"; then @@ -65,7 +66,7 @@ function is_array_count_ge() ( # ===================================== # Action - if test "$(get-array-count -- "${option_inputs[@]}")" -ge "$option_size"; then + if [[ "$(get-array-count -- "${option_inputs[@]}")" -ge $option_size ]]; then return 0 else return 1 @@ -73,6 +74,6 @@ function is_array_count_ge() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_count_ge "$@" fi diff --git a/commands/is-array-empty b/commands.deprecated/is-array-empty similarity index 77% rename from commands/is-array-empty rename to commands.deprecated/is-array-empty index 46b8870b8..ad926f063 100755 --- a/commands/is-array-empty +++ b/commands.deprecated/is-array-empty @@ -5,12 +5,13 @@ # failure if any value is truthy # pass if all values are empty -# if you are using this in conjuction with a is-array-partial, -# or your own test -n or test -z calls, +# if you are using this in conjunction with a is-array-partial, +# or your own [[ -n ... ]] or [[ -z ... ]] calls, # then there is no need for this 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 @@ -31,7 +32,7 @@ function is_array_empty() ( [0] if the array is entirely empty with zero non-empty elements. [1] if the array is not entirely empty, having at least one non-empty element. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -39,7 +40,7 @@ function is_array_empty() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -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 @@ -71,6 +72,6 @@ function is_array_empty() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_empty "$@" fi diff --git a/commands/is-array-empty-or-partial b/commands.deprecated/is-array-empty-or-partial similarity index 78% rename from commands/is-array-empty-or-partial rename to commands.deprecated/is-array-empty-or-partial index d77b3c389..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 @@ -27,7 +28,7 @@ function is_array_empty_or_partial() ( [0] if the array is empty, or has an empty element. [1] if the array is not empty and has zero empty elements. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -35,7 +36,7 @@ function is_array_empty_or_partial() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,13 +58,13 @@ function is_array_empty_or_partial() ( # ===================================== # Action - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then return 0 fi local input for input in "${option_inputs[@]}"; do - if is-empty-string -- "$input"; then + if is-whitespace -- "$input"; then return 0 fi done @@ -71,6 +72,6 @@ function is_array_empty_or_partial() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_empty_or_partial "$@" fi diff --git a/commands/is-array-full b/commands.deprecated/is-array-full similarity index 80% rename from commands/is-array-full rename to commands.deprecated/is-array-full index 3f7caf8fc..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 @@ -27,7 +28,7 @@ function is_array_full() ( [0] if the array is not empty and has zero empty elements. [1] if the array is empty or has an empty element. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -35,7 +36,7 @@ function is_array_full() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,13 +58,13 @@ function is_array_full() ( # ===================================== # Action - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then return 1 fi local input for input in "${option_inputs[@]}"; do - if is-empty-string -- "$input"; then + if is-whitespace -- "$input"; then return 1 fi done @@ -71,6 +72,6 @@ function is_array_full() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_full "$@" fi diff --git a/commands/is-array-full-or-partial b/commands.deprecated/is-array-full-or-partial similarity index 79% rename from commands/is-array-full-or-partial rename to commands.deprecated/is-array-full-or-partial index 2ef4a32c6..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 @@ -27,7 +28,7 @@ function is_array_full_or_partial() ( [0] if the array is has at least one non-empty element. [1] if the array is entirely empty with zero non-empty elements. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -35,7 +36,7 @@ function is_array_full_or_partial() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -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 @@ -67,6 +68,6 @@ function is_array_full_or_partial() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_full_or_partial "$@" fi diff --git a/commands/is-array-partial b/commands.deprecated/is-array-partial similarity index 64% rename from commands/is-array-partial rename to commands.deprecated/is-array-partial index 1b3853833..876d52f4f 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 @@ -15,7 +16,7 @@ function is_array_partial() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Check if an array has both empty and non-empty elemnets. + Check if an array has both empty and non-empty elements. USAGE: is-array-full [--] ... @@ -28,7 +29,7 @@ function is_array_partial() ( [0] if the array has both empty and non-empty elements. [1] if the array is entirely empty or entirely full. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -36,7 +37,7 @@ function is_array_partial() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -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 test "$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 test "$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 test "$has_empty" = 'yes' && test "$has_nonempty" = 'yes'; then + if [[ $had_empty_string == 'yes' && $had_nonempty_string == 'yes' ]]; then return 0 fi done @@ -74,6 +75,6 @@ function is_array_partial() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_array_partial "$@" fi diff --git a/commands.deprecated/is-color-enabled b/commands.deprecated/is-color-enabled index 1d78eced1..8e4d9c45d 100755 --- a/commands.deprecated/is-color-enabled +++ b/commands.deprecated/is-color-enabled @@ -11,6 +11,6 @@ function is_color_enabled() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_color_enabled "$@" fi diff --git a/commands.deprecated/is-dir b/commands.deprecated/is-dir new file mode 100755 index 000000000..30b2193dc --- /dev/null +++ b/commands.deprecated/is-dir @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +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 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_dir "$@" +fi diff --git a/commands.deprecated/is-either b/commands.deprecated/is-either new file mode 100755 index 000000000..6bd9acf08 --- /dev/null +++ b/commands.deprecated/is-either @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +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 + if [[ $1 == "$arg" ]]; then + return 0 + fi + done + return 1 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_either "$@" +fi 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..992eafa80 --- /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.deprecated/is-equal b/commands.deprecated/is-equal new file mode 100755 index 000000000..708110adb --- /dev/null +++ b/commands.deprecated/is-equal @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +function is_equal() ( + source "$DOROTHY/sources/bash.bash" + dorothy-warnings add --code='is-equal' --bold=' has been deprecated in favor of ' --code='test "$a" = "$b"' --bold=' or ' --code='is-needle --needle="$a" -- "$b"' + + [[ $1 == "$2" ]] + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_equal "$@" +fi diff --git a/commands.deprecated/is-exec b/commands.deprecated/is-exec new file mode 100755 index 000000000..fe6755c77 --- /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 in favor of ' --code='is-executable' + + # ===================================== + # Action + + is-executable "$@" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_exec "$@" +fi diff --git a/commands.deprecated/is-interactive b/commands.deprecated/is-interactive index 4244d9983..6c2e10fc0 100755 --- a/commands.deprecated/is-interactive +++ b/commands.deprecated/is-interactive @@ -15,7 +15,7 @@ function is_interactive() ( USAGE: is-interactive EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function is_interactive() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -41,6 +41,6 @@ function is_interactive() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_interactive "$@" fi diff --git a/commands.deprecated/is-match b/commands.deprecated/is-match index faf8da3ba..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 \ @@ -42,7 +42,7 @@ function is_match() ( -i | --ignore-case Ignore case when matching. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -50,7 +50,7 @@ function is_match() ( # process local item option_pattern='' option_input='' option_ignore_case='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -63,9 +63,9 @@ function is_match() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_pattern"; then + if [[ -z $option_pattern ]]; then option_pattern="$item" - elif test -z "$option_input"; then + elif [[ -z $option_input ]]; then option_input="$item" else help "An unrecognised argument was provided: $item" @@ -80,15 +80,15 @@ function is_match() ( local cmd=( echo-regexp -q --regexp="$option_pattern" ) - if test "$option_ignore_case" = 'yes'; then + if [[ $option_ignore_case == 'yes' ]]; then cmd+=('--ignore-case') fi "${cmd[@]}" -- "$option_input" ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_match_test else is_match "$@" diff --git a/commands.deprecated/is-neither b/commands.deprecated/is-neither new file mode 100755 index 000000000..1a79b2999 --- /dev/null +++ b/commands.deprecated/is-neither @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +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 + if [[ $1 == "$arg" ]]; then + return 1 + fi + done + + return 0 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + is_neither "$@" +fi 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.deprecated/is-quiet-enabled b/commands.deprecated/is-quiet-enabled index 694a48d4a..b7ef13ab3 100755 --- a/commands.deprecated/is-quiet-enabled +++ b/commands.deprecated/is-quiet-enabled @@ -11,6 +11,6 @@ function is_quiet_enabled() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_quiet_enabled "$@" fi diff --git a/commands.deprecated/is-tty b/commands.deprecated/is-tty index 188efc58f..31cc4d4d1 100755 --- a/commands.deprecated/is-tty +++ b/commands.deprecated/is-tty @@ -20,7 +20,7 @@ function is_tty() ( --fallback Output /dev/tty if available, otherwise output /dev/stderr. Always return success status. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -28,7 +28,7 @@ function is_tty() ( # process local item option_fallback='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -46,7 +46,7 @@ function is_tty() ( # correctly, safely, and without side-effects, determine if the TTY is attached, readable, and writable # note that &>/dev/null is only possible for checking TTY (checking stdout/stderr that way will affect stdout/stderr, it is an observer effect) - if test "$option_fallback" = 'yes'; then + if [[ $option_fallback == 'yes' ]]; then get-terminal-device-file else get-terminal-tty-support --quiet @@ -54,6 +54,6 @@ function is_tty() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_tty "$@" fi diff --git a/commands/rm-junk b/commands.deprecated/rm-junk similarity index 63% rename from commands/rm-junk rename to commands.deprecated/rm-junk index 38baab7a4..f86711f8e 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 --cache' # ===================================== # Arguments @@ -22,7 +23,7 @@ function rm_junk() ( --empty If provided, remove empty directories as well. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +31,7 @@ function rm_junk() ( # options local item option_paths=() option_empty='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -50,43 +51,18 @@ function rm_junk() ( done # adjust path - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then option_paths+=("$(pwd)") fi # ===================================== # 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 test "$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 --cache --empty-directories="$option_empty" -- "$path" + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then rm_junk "$@" fi diff --git a/commands/rm-modules b/commands.deprecated/rm-modules similarity index 77% rename from commands/rm-modules rename to commands.deprecated/rm-modules index ffe5f031e..71e91173e 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 --module' # ===================================== # Arguments @@ -20,7 +21,7 @@ function rm_modules() ( Provide to specify which paths should have their junk files removed. If was not provided then the current working directory will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -28,7 +29,7 @@ function rm_modules() ( # options local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,20 +46,18 @@ function rm_modules() ( done # adjust path - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then option_paths+=("$(pwd)") fi # ===================================== # 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 --module --empty-directories="$option_empty" -- "$path" + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then rm_modules "$@" fi diff --git a/commands.beta/rm-svn b/commands.deprecated/rm-svn similarity index 73% rename from commands.beta/rm-svn rename to commands.deprecated/rm-svn index d72a27ab2..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 @@ -14,7 +15,7 @@ function rm_svn() ( USAGE: rm-svn [.] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,14 +23,14 @@ function rm_svn() ( # process local item path='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$path"; then + if [[ -z $path ]]; then path="$item" else help "An unrecognised argument was provided: $item" @@ -39,7 +40,7 @@ function rm_svn() ( done # ensure - if test -z "$path"; then + if [[ -z $path ]]; then path="$(pwd)" fi @@ -50,6 +51,6 @@ function rm_svn() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then rm_svn "$@" fi diff --git a/commands.beta/rm-sync b/commands.deprecated/rm-sync similarity index 73% rename from commands.beta/rm-sync rename to commands.deprecated/rm-sync index e64e2c025..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 @@ -14,7 +15,7 @@ function rm_sync() ( USAGE: rm-sync [.] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,14 +23,14 @@ function rm_sync() ( # process local item path='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$path"; then + if [[ -z $path ]]; then path="$item" else help "An unrecognised argument was provided: $item" @@ -39,7 +40,7 @@ function rm_sync() ( done # ensure - if test -z "$path"; then + if [[ -z $path ]]; then path="$(pwd)" fi @@ -50,6 +51,6 @@ function rm_sync() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then rm_sync "$@" fi diff --git a/commands/alias-helper b/commands/alias-helper index fa97da7e9..0dc176998 100755 --- a/commands/alias-helper +++ b/commands/alias-helper @@ -30,7 +30,7 @@ function alias_helper() ( info -- Human friendly details about the macOS alias file. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -38,7 +38,7 @@ function alias_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -50,7 +50,7 @@ function alias_helper() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -88,12 +88,12 @@ function alias_helper() ( function do_verify { local path="$1" alias - if test -L "$path"; then + if [[ -L $path ]]; then echo-style --bold+red="$path" ' ' --error='<- origin is a symlink not an alias' >/dev/stderr return 22 # EINVAL 22 Invalid argument - elif test -e "$path"; then + elif [[ -e $path ]]; then eval_capture --stdoutvar=alias -- get_alias_origin "$path" - if test -n "$alias"; then + if [[ -n $alias ]]; then # don't output anything, we are just verifying return 0 else @@ -109,9 +109,9 @@ function alias_helper() ( function do_target { local path="$1" target verify_status eval_capture --statusvar=verify_status -- do_verify "$path" - if test "$verify_status" -eq 0; then + if [[ $verify_status -eq 0 ]]; then eval_capture --stdoutvar=target -- get_alias_target "$path" - if test -n "$target"; then + if [[ -n $target ]]; then __print_lines "$target" # output the target from the origin return 0 else @@ -126,7 +126,7 @@ function alias_helper() ( # verify eval_capture --stdoutvar=origin -- get_alias_origin "$path" - if test -z "$origin"; then + if [[ -z $origin ]]; then echo-style \ --bold+red="$path" \ $'\t' \ @@ -136,14 +136,14 @@ function alias_helper() ( # target eval_capture --stdoutvar=target -- get_alias_target "$path" - if test -z "$target"; then + if [[ -z $target ]]; then echo-style \ --bold="$origin" \ $'\t' \ $'\t' --error='← target broken' >/dev/stderr return 9 # EBADF 9 Bad file descriptor fi - if test ! -e "$target"; then + if [[ ! -e $target ]]; then echo-style \ --bold="$origin" \ --nocolor=$'\t' --color+dim=$'\tβ†’\t' --bold+red="$target" \ @@ -165,9 +165,9 @@ function alias_helper() ( path_filename="$(basename "$path_absolute")" # act - if test -d "$target_absolute"; then + if [[ -d $target_absolute ]]; then type='folder' - elif test -f "$target_absolute"; then + elif [[ -f $target_absolute ]]; then type='file' else echo-error 'Invalid path or unsupported type:' ' ' --code="$path" @@ -194,27 +194,27 @@ function alias_helper() ( # verify alias eval_capture --stdoutvar=origin -- get_alias_origin "$path" - if test -z "$origin"; then + if [[ -z $origin ]]; then echo-style --bold+red="$path" ' ' --error='<- not an alias' >/dev/stderr return 22 # EINVAL 22 Invalid argument fi # verify target eval_capture --stdoutvar=target -- get_alias_target "$path" - if test -z "$target"; then + if [[ -z $target ]]; then echo-style --bold="$origin" --dim=' β†’ ' --bold+red="$target" ' ' --error='← target broken' >/dev/stderr return 9 # EBADF 9 Bad file descriptor fi - if test ! -e "$target"; then + if [[ ! -e $target ]]; then echo-style --bold="$origin" --dim=' β†’ ' --bold+red="$target" ' ' --error='← target missing' >/dev/stderr return 2 # ENOENT 2 No such file or directory fi # convert - if test -f "$target"; then + if [[ -f $target ]]; then ln -nfs "$target" "$origin" __print_lines "converted $path -> $target" - elif test -d "$target"; then + elif [[ -d $target ]]; then ln -nfs "$target" "$origin" __print_lines "converted $path -> $target" fi @@ -223,7 +223,7 @@ function alias_helper() ( # ===================================== # Act - if test "$(type -t "do_$action")" = 'function'; then + if [[ "$(type -t "do_$action")" == 'function' ]]; then "do_$action" "${option_args[@]}" else help " unsupported or yet implemented: $action" || : @@ -232,6 +232,6 @@ function alias_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then alias_helper "$@" fi diff --git a/commands/ask b/commands/ask index 122c94a8b..ae1d60eea 100755 --- a/commands/ask +++ b/commands/ask @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# read can set the default with [-i ] however that requries [-e] which uses readline, which requires stdin to not be programmatic +# read can set the default with [-i ] however that requires [-e] which uses readline, which requires stdin to not be programmatic # # if stdin is programmatic (ssh -T, CI, piped) then read behaves differently: # [-p ] is discarded, no prompt is shown @@ -82,10 +82,10 @@ function ask_test() ( # confirm the provided value sleep 3 __print_line - } | eval-tester --name='receuve a custom response, by sending the custon response to overwrite the default, and then confirming the custom response' --stdout='a custom response' \ + } | eval-tester --name='receive a custom response, by sending the custom response to overwrite the default, and then confirming the custom response' --stdout='a custom response' \ -- ask --question='What is your response?' --default='a default response' --confirm - # @todo add tests for timout + # @todo add tests for timeout echo-style --g1="TEST: $0" return 0 @@ -133,7 +133,7 @@ function ask_() ( Custom timeout value in seconds. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -144,7 +144,7 @@ function ask_() ( local option_default='' option_confirm_default='yes' option_confirm_input='no' local option_required='no' option_password='no' local option_linger='no' option_timeout='' option_inline='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -189,8 +189,8 @@ function ask_() ( # question local question_title question_body - if test "${#option_question[@]}" -ne 0; then # bash v3 compat - if test -n "${option_question[0]}"; then + if [[ ${#option_question[@]} -ne 0 ]]; then # bash v3 compat + if [[ -n ${option_question[0]} ]]; then question_title="${option_question[0]}" question_body="$(__print_lines "${option_question[@]:1}")" else @@ -216,8 +216,8 @@ function ask_() ( # style the question local question_title_and_body_and_newline='' question_title_result='' - if test -n "$question_title"; then - if test -n "$question_body"; then + if [[ -n $question_title ]]; then + if [[ -n $question_body ]]; then question_title_and_body_and_newline="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}"$'\n'"${style__question_body}${question_body}${style__end__question_body}"$'\n' else question_title_and_body_and_newline="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}"$'\n' @@ -243,19 +243,19 @@ function ask_() ( # adjust prompt local input_prompt_and_newline='' - if test "$terminal_reactive" = 'no' -a -n "$option_default"; then + if [[ $terminal_reactive == 'no' && -n $option_default ]]; then input_prompt_and_newline="Press ${style__key}ENTER${style__end__key} to use the default value of ${style__code}${option_default}${style__end__code}. Press ${style__key}ESC${style__end__key} then ${style__key}ENTER${style__end__key} to use no value."$'\n' fi # adjust timeout to one minute if we have a default value, or if optional - if test -z "$option_timeout" && (is-value -- "$RESULT" || test "$option_required" = 'no'); then + if [[ -z $option_timeout ]] && (is-value -- "$RESULT" || [[ $option_required == 'no' ]]); then option_timeout=60 fi # adjust read args based on timeout local READ_RESULT READ_PROMPT="${question_title_and_body_and_newline}${input_prompt_and_newline}${style__icon_prompt}" READ_PROMPT_LINES CLEAR - if test "$inline" = 'no'; then - if test -n "$option_timeout"; then + if [[ $inline == 'no' ]]; then + if [[ -n $option_timeout ]]; then function __read { READ_RESULT='' __print_string "${CLEAR}${READ_PROMPT}" >"$terminal_device_file" @@ -272,7 +272,7 @@ function ask_() ( fi else READ_PROMPT_LINES="$(echo-clear-lines --count-only --here-string <<<"$READ_PROMPT")" - if test -n "$option_timeout"; then + if [[ -n $option_timeout ]]; then function __read { READ_RESULT='' IFS= read -rt "$option_timeout" -ei "$RESULT" -p "${CLEAR}${READ_PROMPT}" READ_RESULT @@ -294,7 +294,7 @@ function ask_() ( # default commentary="${style__result_commentary_spacer}${style__commentary_timeout_default}" return 0 - elif test "$option_required" = 'no'; then + elif [[ $option_required == 'no' ]]; then # optional commentary="${style__result_commentary_spacer}${style__commentary_timeout_optional}" return 0 @@ -304,28 +304,28 @@ function ask_() ( return 60 # ETIMEDOUT 60 Operation timed out fi } - function do_prompt { # has sideffects: RESULT, ASKED + function do_prompt { # has side-effects: RESULT, ASKED local __read_status result_prompt result_prompt_lines # reset clear, in case a choose/confirm failed and we are re-prompting - if test "$inline" = 'no'; then + if [[ $inline == 'no' ]]; then CLEAR="$style__alternative_screen_buffer" else CLEAR='' fi ASKED='yes' # not local - while true; do + while :; do # reset __read_status=0 READ_RESULT='' # adapt according to read mode - if test "$inline" = 'no'; then + if [[ $inline == 'no' ]]; then __read || __read_status=$? - if test "$__read_status" -eq 0; then + if [[ $__read_status -eq 0 ]]; then # process the input result to the actual RESULT - if test -z "$READ_RESULT" -o "$READ_RESULT" = $'\n'; then + if [[ -z $READ_RESULT || $READ_RESULT == $'\n' ]]; then # treat empty string and newline as default : elif [[ $READ_RESULT =~ ^[[:cntrl:][:space:]]*$ ]]; then @@ -343,7 +343,7 @@ function ask_() ( # update the value on successful read, and prepare the clear # note if there was a default value, pressing enter will set [READ_RESULT] to it - if test "$__read_status" -eq 0; then + if [[ $__read_status -eq 0 ]]; then # only update RESULT on successful read status, as [READ_RESULT] will be empty on timeout RESULT="$READ_RESULT" # the user has pressed enter. which will be added to the TTY, so trim it and trim a possibly very long input @@ -359,14 +359,14 @@ function ask_() ( fi # handle the result - if test "$__read_status" -eq 142; then + if [[ $__read_status -eq 142 ]]; then __read_status=60 # ETIMEDOUT 60 Operation timed out break fi if is-value -- "$RESULT"; then # we have a value, proceed break - elif test "$option_required" = 'yes'; then + elif [[ $option_required == 'yes' ]]; then # reset and ask again RESULT="$option_default" continue @@ -382,7 +382,7 @@ function ask_() ( __print_string "$CLEAR" >"$terminal_device_file" fi # done - if test "$__read_status" -ne 0; then + if [[ $__read_status -ne 0 ]]; then return "$__read_status" fi do_validate @@ -391,33 +391,33 @@ function ask_() ( local choose_args=() choose_status choice choices=() # have we prompted? - if test "$ASKED" = 'no'; then + if [[ $ASKED == 'no' ]]; then # do we want to confirm the default value - # adding a [&& test -t 0] will cause stdin input to always be respected, which is inconsistent with expecations, e.g. + # adding a [&& -t 0] will cause stdin input to always be respected, which is inconsistent with expectations, e.g. # echo 'overwrite' | { ask --linger --default='d1' --skip-default --question='q1'; ask --linger --default='d2' --skip-default --question='q2' ) # should output 'd1' and 'overwrite', not 'overwrite' and 'd2', as the goal of skip-default is to use the default preference for performance and intuition reasons - if is-value -- "$RESULT" && test "$option_confirm_default" = 'no'; then + if is-value -- "$RESULT" && [[ $option_confirm_default == 'no' ]]; then return 0 fi else # we have asked, do we want to confirm the input value - if test "$option_confirm_input" = 'no'; then + if [[ $option_confirm_input == 'no' ]]; then return 0 fi # redo choices, has to be redone each time due to result - if test "$option_password" = 'yes'; then + if [[ $option_password == 'yes' ]]; then choices+=('existing' 'use the entered password') else choices+=('existing' "use the entered value: [$RESULT]") fi choices+=('custom' 'redo the entered value') - if test "$option_required" = 'no'; then + if [[ $option_required == 'no' ]]; then choices+=('none' 'use no value') fi # we want to confirm - if test "${#option_question[@]}" -ne 0; then + if [[ ${#option_question[@]} -ne 0 ]]; then choose_args+=( "${option_question[@]}" ) @@ -431,22 +431,22 @@ function ask_() ( local choose_status=0 eval_capture --statusvar=choose_status --stdoutvar=choice -- \ choose "${choose_args[@]}" - if test "$choose_status" -eq 60; then + if [[ $choose_status -eq 60 ]]; then on_timeout return - elif test "$choose_status" -ne 0; then + elif [[ $choose_status -ne 0 ]]; then commentary="${style__result_commentary_spacer}$(printf "$style__commentary_input_failure" "choose failure: $choose_status")" return "$choose_status" fi - # proceess the confirmation - if test "$choice" = 'existing'; then - # done, sucess + # process the confirmation + if [[ $choice == 'existing' ]]; then + # done, success return 0 - elif test "$choice" = 'custom'; then + elif [[ $choice == 'custom' ]]; then : # proceed with prompt - elif test "$choice" = 'none'; then - # done, sucess + elif [[ $choice == 'none' ]]; then + # done, success RESULT='' return 0 else @@ -459,7 +459,7 @@ function ask_() ( # prompt and check for failure local prompt_status=0 eval_capture --statusvar=prompt_status -- do_prompt - if test "$prompt_status" -ne 0; then + if [[ $prompt_status -ne 0 ]]; then # timeout probably on_timeout return @@ -473,14 +473,14 @@ function ask_() ( local validate_status=0 eval_capture --statusvar=validate_status -- do_validate local render="$question_title_result$commentary" - if test "$validate_status" -eq 0; then + if [[ $validate_status -eq 0 ]]; then # success response # inform if requested - if test "$option_linger" = 'yes'; then + if [[ $option_linger == 'yes' ]]; then local result - if test -z "$RESULT"; then + if [[ -z $RESULT ]]; then result="${style__commentary_nothing_provided}" - elif test "$option_password" = 'yes'; then + elif [[ $option_password == 'yes' ]]; then result="${style__commentary_using_password}" else result="${RESULT//$'\n'/$'\n'"${style__indent_blockquote}"}" # add the necessary indentation @@ -490,13 +490,13 @@ function ask_() ( __print_string "$render" >"$terminal_device_file" fi # stdout - if test -n "$RESULT"; then + if [[ -n $RESULT ]]; then __print_lines "$RESULT" # stdout, so use __print_lines fi else # failure response # always inform - if test "$option_linger" = 'yes'; then + if [[ $option_linger == 'yes' ]]; then __print_string "$render" >"$terminal_device_file" else __print_string "$render" >/dev/stderr @@ -506,8 +506,8 @@ function ask_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then ask_test else ask_ "$@" diff --git a/commands/brew b/commands/brew index 3cd074d5a..e7886b997 100755 --- a/commands/brew +++ b/commands/brew @@ -16,9 +16,9 @@ function brew_() ( # get the homebrew binary local bin='' - if test -n "${HOMEBREW_PREFIX-}"; then + if [[ -n ${HOMEBREW_PREFIX-} ]]; then bin="$HOMEBREW_PREFIX/bin/brew" - if test ! -x "$bin"; then + if [[ ! -x $bin ]]; then bin='' fi fi @@ -27,7 +27,7 @@ function brew_() ( # Arguments function help { - if test -n "$bin"; then + if [[ -n $bin ]]; then "$bin" --help || : __print_line fi @@ -42,7 +42,7 @@ function brew_() ( - disable auto updates on macOS, as Dorothy handles that better via [setup-mac-brew] - disable brew environment hints, as Dorothy handles that better via [setup-environment-commands] EOF - if test -z "$bin"; then + if [[ -z $bin ]]; then if is-mac; then echo-style --error1='Homebrew is not installed.' --notice1=' Install it with: ' --code-notice1='setup-mac-brew' else @@ -53,7 +53,7 @@ function brew_() ( fi return 22 # EINVAL 22 Invalid argument } - if test -z "$bin" -o "$*" = '--help'; then + if [[ -z $bin || $* == '--help' ]]; then help >/dev/stderr fi @@ -61,7 +61,7 @@ function brew_() ( # Action # run brew - if test -n "${HOMEBREW_ARCH-}"; then + if [[ -n ${HOMEBREW_ARCH-} ]]; then arch -"$HOMEBREW_ARCH" \ "$bin" "$@" else @@ -70,6 +70,6 @@ function brew_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then brew_ "$@" fi diff --git a/commands/brew-installed b/commands/brew-installed index 9c547404b..4d1c01c88 100755 --- a/commands/brew-installed +++ b/commands/brew-installed @@ -37,15 +37,9 @@ function brew_installed() ( QUIRKS: If packages are provided, failure exit status will be returned if any are missing. - To check if any are present, use: - - $(echo-style --code="test -n \"\$(brew-installed -- bash something-missing || :)\"") - - If you just want the exit status, use: - - $(echo-style --code="brew-installed --quiet -- bash something-missing") + Existing packages will still be output to stdout, unless --quiet is provided. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -66,7 +60,7 @@ function brew_installed() ( 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 + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -93,14 +87,14 @@ function brew_installed() ( done # add the filter - if test -n "$option_type"; then + if [[ -n $option_type ]]; then brew_list_cmd+=("--$option_type") brew_info_cmd+=("--$option_type") deno_script_args+=("--$option_type") fi # add the packages - if test "${#option_packages[@]}" -ne 0; then + if [[ ${#option_packages[@]} -ne 0 ]]; then brew_list_cmd+=("${option_packages[@]}") brew_info_cmd+=( '--json=v2' @@ -118,7 +112,7 @@ function brew_installed() ( function do_brew_simple { # handles only --cask and --formula, but not --requested - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then "${brew_list_cmd[@]}" &>/dev/null else "${brew_list_cmd[@]}" | cut -d' ' -f1 | sort | uniq @@ -134,7 +128,7 @@ function brew_installed() ( # run local deno_script deno_script="$(type -P 'brew-installed.ts')" - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then "${brew_info_cmd[@]}" | "$deno_script" "${deno_script_args[@]}" &>/dev/null else "${brew_info_cmd[@]}" | "$deno_script" "${deno_script_args[@]}" @@ -142,16 +136,16 @@ function brew_installed() ( } # get names of requested packages - if test "$option_requested" = 'no'; then + if [[ $option_requested == 'no' ]]; then do_brew_simple - elif test "${#option_packages[@]}" -eq 0; then + elif [[ ${#option_packages[@]} -eq 0 ]]; then do_brew_advanced - elif test "$(do_brew_advanced | echo-count-lines --no-inline --stdin)" -ne "${#option_packages[@]}"; then + elif [[ "$(do_brew_advanced | echo-count-lines --no-inline --stdin)" -ne ${#option_packages[@]} ]]; then return 1 fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then brew_installed "$@" fi diff --git a/commands/btrfs-helper b/commands/btrfs-helper index 41e750503..01587ea3b 100755 --- a/commands/btrfs-helper +++ b/commands/btrfs-helper @@ -42,7 +42,7 @@ function btrfs_helper() ( balance Runs a btrfs raid1 balance on a selected btrfs filesystem. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -50,14 +50,14 @@ function btrfs_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; 'devices') action="$item" - if test "${1-}" = '--'; then + if [[ ${1-} == '--' ]]; then shift fi option_args+=("$@") @@ -105,7 +105,7 @@ function btrfs_helper() ( sudo-helper --wrap \ -- btrfs filesystem show "$path" - if test -n "$existing_mount"; then + if [[ -n $existing_mount ]]; then # Show detailed information about internal allocations in devices. # usage: btrfs device usage [options] [..] sudo-helper --wrap \ @@ -137,7 +137,7 @@ function btrfs_helper() ( -- btrfs device stats "$path" # Get all devices of the mount - if test -n "$existing_uuid" && test "$existing_count" -gt 1; then + if [[ -n $existing_uuid && $existing_count -gt 1 ]]; then get-devices --uuid="$existing_uuid" --filesystem=btrfs fi @@ -239,11 +239,11 @@ function btrfs_helper() ( IFS=$'\t' read -rd $'\n' extra_node extra_uuid <<<"$temp" # validate - if test "$existing_node" = "$extra_node"; then + if [[ $existing_node == "$extra_node" ]]; then echo-style --error='You cannot add the same device to itself.' >/dev/stderr return 22 # EINVAL 22 Invalid argument fi - if test "$existing_uuid" = "$extra_uuid"; then + if [[ $existing_uuid == "$extra_uuid" ]]; then echo-style --error='The device is already part of the intended filesystem.' >/dev/stderr return 22 # EINVAL 22 Invalid argument fi @@ -279,7 +279,7 @@ function btrfs_helper() ( )" # check count - if test "$existing_count" -eq 2; then + if [[ $existing_count -eq 2 ]]; then if confirm --positive --ppid=$$ -- "$(echo-style --notice="Removing a device from a btrfs filesystem with only 2 devices, requires the balance to be set to single rather than raid1. Do you wish to continue?")"; then act_balance "$existing_mount" 'single' else @@ -305,7 +305,7 @@ function btrfs_helper() ( function act_balance { # Resume a balance local temp existing_mount="${1-}" existing_count='' strategy="${2:-"raid1"}" action='start' args=() - if test -z "$existing_mount"; then + if [[ -z $existing_mount ]]; then temp="$( get-devices --result=mount,count --mount --count --filesystem=btrfs \ --select="$(echo-style --notice="Which btrfs filesystem to balance as $strategy?")" \ @@ -315,7 +315,7 @@ function btrfs_helper() ( fi # validate - if test -n "$existing_count" && test "$existing_count" -eq 1 -a "$strategy" != 'single'; then + if [[ -n $existing_count && $existing_count -eq 1 && $strategy != 'single' ]]; then echo-error 'btrfs filesystems with a single device can only be balanced as [single]' return 22 # EINVAL 22 Invalid argument fi @@ -327,7 +327,7 @@ function btrfs_helper() ( -- btrfs balance status --verbose "$existing_mount"; then # balance is running, ask what to do - if test "$(choose --linger --required --label --question="Do you wish to?" -- 'resume' "Resume the existing balance, and abort a new $strategy balance." 'start' "Cancel the existing balance and resume, and start a new $strategy balance.")" = 'resume'; then + if [[ "$(choose --linger --required --label --question="Do you wish to?" -- 'resume' "Resume the existing balance, and abort a new $strategy balance." 'start' "Cancel the existing balance and resume, and start a new $strategy balance.")" == 'resume' ]]; then # usage: btrfs balance resume # Resume interrupted balance sudo-helper --wrap --confirm \ @@ -347,9 +347,9 @@ function btrfs_helper() ( # --background|--bg run the balance as a background process # --enqueue wait if there's another exclusive operation running, otherwise continue # -d[filters] act on data chunks with optional filters (no space in between) - # -m[filters] act on metadata chunks with optinal filters (no space in between) + # -m[filters] act on metadata chunks with optional filters (no space in between) # -s[filters] act on system chunks (only under -f) with optional filters (no space in between) - if test "$strategy" = 'single'; then + if [[ $strategy == 'single' ]]; then args+=('--force') fi sudo-helper --wrap --confirm \ @@ -357,9 +357,9 @@ function btrfs_helper() ( } # act - if test -z "$action"; then + if [[ -z $action ]]; then help 'No specified.' - elif test "$(type -t "act_$action")" = 'function'; then + elif [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" "${option_args[@]}" else help " unsupported or yet implemented: $action" || : @@ -368,6 +368,6 @@ function btrfs_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then btrfs_helper "$@" fi diff --git a/commands/checksum b/commands/checksum index d43ab86f5..cbadd2836 100755 --- a/commands/checksum +++ b/commands/checksum @@ -46,7 +46,7 @@ function checksum_() ( QUIRKS: [--relative] does not respect pwd, as such [--relative] is only useful when in [--no-summary] mode. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -54,7 +54,7 @@ function checksum_() ( # process local item option_paths=() option_relative='no' option_untitled='no' option_summary='no' option_algorithm='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -87,7 +87,7 @@ function checksum_() ( )" # ensure paths - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then option_paths+=("$(pwd)") fi @@ -104,14 +104,14 @@ function checksum_() ( local path="$1" use_relative="$2" hash_dot # relative - if test "$use_relative" = 'yes'; then + if [[ $use_relative == 'yes' ]]; then path="$(basename "$path")" fi # algorithm "$option_algorithm" | { IFS=' ' read -ra hash_dot - if test "$option_untitled" = 'yes'; then + if [[ $option_untitled == 'yes' ]]; then __print_lines "${hash_dot[0]}" else __print_lines "${hash_dot[0]} $path" @@ -130,7 +130,7 @@ function checksum_() ( function do_checksum_of_paths { local path temp_file use_relative="${2:-"$option_relative"}" for path in "$@"; do - if test -f "$path" -o "$option_summary" != 'yes'; then + if [[ -f $path || $option_summary != 'yes' ]]; then # file, or itemise do_checksum_of_file_or_directory "$path" "$use_relative" else @@ -148,6 +148,6 @@ function checksum_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then checksum_ "$@" fi diff --git a/commands/choose b/commands/choose index 896fda431..b39aaaa4a 100755 --- a/commands/choose +++ b/commands/choose @@ -41,13 +41,13 @@ function choose_test() ( -- env COLOR=no choose 'q' 'd' --index --timeout=5 -- a b c # timeout required - eval-tester --name='receive timeout response by timeout with no input and reqyured' --status='60' --stderr="q $timeout_required" \ + eval-tester --name='receive timeout response by timeout with no input and required' --status='60' --stderr="q $timeout_required" \ -- env COLOR=no choose 'q' 'd' --required --timeout=5 -- a b c - eval-tester --name='receive timeout --index response by timeout with no input and reqyured' --status='60' --stderr="q $timeout_required" \ + eval-tester --name='receive timeout --index response by timeout with no input and required' --status='60' --stderr="q $timeout_required" \ -- env COLOR=no choose 'q' 'd' --index --required --timeout=5 -- a b c - # single mode, single defalut + # single mode, single default eval-tester --name='receive default response by timeout with no input and optional' --stdout=b --stderr="q $timeout_defaults" \ -- env COLOR=no choose 'q' 'd' --timeout=5 --default=b -- a b c @@ -276,7 +276,7 @@ function choose_test() ( sleep "$delay" __print_string ' ' sleep "$delay" - } | eval-tester --name='test multi modifiers are noops in single mode' --stdout='b' \ + } | eval-tester --name='test multi modifiers are no-ops in single mode' --stdout='b' \ -- env COLOR=no choose 'q' 'd' -- a b c { @@ -507,6 +507,9 @@ function choose_() ( --defaults-fuzzy= Pre-select s by fuzzy matching. + --[no-]default-all=[yes|NO] + If provided, select everything as a default. + --[no-]skip-solo=[YES|no] | --[no-]confirm-solo=[NO|yes] If a selection is required, and there is only a single item, the solo item will be selected. This option skips the prompt for the required solo item, sending it without doing the prompt. Defaults to disabled. @@ -516,8 +519,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. @@ -531,13 +537,16 @@ function choose_() ( --[no-]linger=[yes|NO] Whether the prompt should persist afterwards. + --[no-]truncate-body=[yes|NO] + Truncate the body if it is too long and would prevent the menu from being entirely viewable without a font size change. + --timeout= 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -547,10 +556,10 @@ 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_default_all='' 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 test "$#" -ne 0; do + local option_linger='no' option_timeout='' option_truncate_body='no' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -573,6 +582,9 @@ function choose_() ( mapfile -t tmp <<<"${item#*=}" defaults_fuzzy+=("${tmp[@]}") ;; + '--no-default-all'* | '--default-all'*) + option_default_all="$(get-flag-value --affirmative --fallback="$option_default_all" -- "$item")" + ;; '--no-skip-solo'* | '--skip-solo'*) option_confirm_solo="$(get-flag-value --non-affirmative --fallback="$option_confirm_solo" -- "$item")" ;; @@ -588,10 +600,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")" @@ -602,6 +618,9 @@ function choose_() ( '--no-linger'* | '--linger'*) option_linger="$(get-flag-value --affirmative --fallback="$option_linger" -- "$item")" ;; + '--no-truncate-body'* | '--truncate-body'*) + option_truncate_body="$(get-flag-value --affirmative --fallback="$option_truncate_body" -- "$item")" + ;; '--timeout='*) option_timeout="${item#*=}" ;; '--') inputs+=("$@") @@ -614,14 +633,14 @@ function choose_() ( done # ensure items were provided - if test "${#inputs[@]}" -eq 0; then + if [[ ${#inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # question local question_title question_body - if test "${#option_question[@]}" -ne 0; then # bash v3 compat - if test -n "${option_question[0]}"; then + if [[ ${#option_question[@]} -ne 0 ]]; then # bash v3 compat + if [[ -n ${option_question[0]} ]]; then question_title="${option_question[0]}" question_body="$(__print_lines "${option_question[@]:1}")" else @@ -646,7 +665,7 @@ function choose_() ( refresh_style_cache -- 'question_title_prompt' 'question_title_result' 'question_body' 'input_warning' 'input_error' 'icon_prompt' 'error' 'notice' 'count_spacer' 'result_line' 'active_line' 'selected_line' 'default_line' 'empty_line' 'inactive_line' 'legend' 'key' 'count_more' 'count_selected' 'count_defaults' 'count_empty' 'bar_top' 'bar_middle' 'bar_bottom' 'icon_multi_selected' 'icon_multi_default' 'icon_multi_active' 'icon_multi_standard' 'icon_single_selected' 'icon_single_default' 'icon_single_active_required' 'icon_single_active_optional' 'icon_single_standard' 'icon_nothing_provided' 'icon_no_selection' 'commentary_nothing_selected' 'legend_legend_spacer' 'legend_key_spacer' 'key_key_spacer' 'indent_bar' 'indent_active' 'indent_inactive' 'indent_blockquote' 'bar_line' 'alternative_screen_buffer' 'default_screen_buffer' 'commentary_timeout_default' 'commentary_timeout_optional' 'commentary_timeout_required' 'commentary_input_failure' 'result_commentary_spacer' # select icons - if test "$option_multi" = 'yes'; then + if [[ $option_multi == 'yes' ]]; then style__icon_selected="$style__icon_multi_selected" style__icon_default="$style__icon_multi_default" style__icon_active="$style__icon_multi_active" @@ -654,7 +673,7 @@ function choose_() ( else style__icon_selected="$style__icon_single_selected" style__icon_default="$style__icon_single_default" - if test "$option_required" = 'yes'; then + if [[ $option_required == 'yes' ]]; then style__icon_active="$style__icon_single_active_required" else style__icon_active="$style__icon_single_active_optional" @@ -664,11 +683,11 @@ function choose_() ( # style the question local question_title_prompt='' question_title_result='' question_body_prompt='' - if test -n "$question_title"; then + if [[ -n $question_title ]]; then question_title_prompt="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}" question_title_result="${style__question_title_result}${question_title}${style__end__question_title_result}" fi - if test -n "$question_body"; then + if [[ -n $question_body ]]; then question_body_prompt="${style__question_body}${question_body}${style__end__question_body}" fi @@ -676,7 +695,7 @@ function choose_() ( # Menu # enforce question if lingering - if test "$option_linger" = 'yes' -a -z "$question_title"; then + if [[ $option_linger == 'yes' && -z $question_title ]]; then help 'A is required when using --linger' fi @@ -689,7 +708,7 @@ function choose_() ( # prepare label handling local inputs_step=1 - if test "$option_label" != 'no'; then + if [[ $option_label != 'no' ]]; then # because of labels, we will iterate items two at a time inputs_step=2 @@ -713,62 +732,66 @@ function choose_() ( INDEX=$((INDEX + 1)) # order of - if test "$option_label" = 'no'; then + if [[ $option_label == 'no' ]]; then VALUE="${inputs[index]}" LABEL="$VALUE" - if test -z "$VALUE"; then + if [[ -z $VALUE ]]; then __print_lines "${style__error}The $INDEX item at index $index was empty, all items must be non-empty.${style__end__error}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi else - if test "$option_label" = 'yes'; then + if [[ $option_label == 'yes' ]]; then VALUE="${inputs[index]}" LABEL="${inputs[index + 1]}" - elif test "$option_label" = 'first'; then + elif [[ $option_label == 'first' ]]; then VALUE="${inputs[index + 1]}" LABEL="${inputs[index]}" else __print_lines "${style__error}Invalid label option [$option_label], it must be no, yes, or first.${style__end__error}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi - if test -z "$LABEL" -o -z "$VALUE"; then + if [[ -z $LABEL || -z $VALUE ]]; then __print_lines "${style__error}Invalid label=[$LABEL] value=[$VALUE] combination, both must be non-empty.${style__end__error}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi fi # enable if default - if test "${#defaults_exact[@]}" -ne 0; then # bash v3 compat - for item in "${defaults_exact[@]}"; do - if test "$VALUE" = "$item"; then - defaults[INDEX]='yes' - fi - done - fi - if test "${#defaults_fuzzy[@]}" -ne 0; then # bash v3 compat - for item in "${defaults_fuzzy[@]}"; do - if __string_has_case_insensitive_substring "$VALUE" "$item"; then - defaults[INDEX]='yes' - fi - done + if [[ $option_default_all == 'yes' ]]; then + defaults[INDEX]='yes' + else + if [[ ${#defaults_exact[@]} -ne 0 ]]; then # bash v3 compat + for item in "${defaults_exact[@]}"; do + if [[ $VALUE == "$item" ]]; then + defaults[INDEX]='yes' + fi + done + fi + if [[ ${#defaults_fuzzy[@]} -ne 0 ]]; then # bash v3 compat + for item in "${defaults_fuzzy[@]}"; do + if __string_has_case_insensitive_substring "$VALUE" "$item"; then + defaults[INDEX]='yes' + fi + done + fi fi # generate what is used - if test -n "$option_visual"; then + if [[ -n $option_visual ]]; then eval "VISUAL=\"$option_visual\"" else VISUAL="$LABEL" fi - if test -z "$VISUAL"; then + if [[ -z $VISUAL ]]; then __print_lines "${style__error}Invalid visual=[$VISUAL] for label=[$LABEL] value=[$VALUE], all must be non-empty.${style__end__error}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi - if test -n "$option_return"; then + if [[ -n $option_return ]]; then eval "RETURN=\"$option_return\"" else RETURN="$VALUE" fi - if test -z "$RETURN"; then + if [[ -z $RETURN ]]; then __print_lines "${style__error}Invalid return=[$RETURN] for label=[$LABEL] value=[$VALUE], all must be non-empty.${style__end__error}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi @@ -776,9 +799,9 @@ function choose_() ( returns+=("$RETURN") done items_count="${#items[@]}" - if test "$items_count" -eq 1 -a "$option_required" = 'yes'; then + if [[ $items_count -eq 1 && $option_required == 'yes' ]]; then defaults[0]='yes' - if test "$option_confirm_solo" = 'no'; then + if [[ $option_confirm_solo == 'no' ]]; then can_skip_prompt='yes' fi fi @@ -787,34 +810,35 @@ function choose_() ( # handle default options local can_revert_to_defaults can_cancel - if test "$defaults_count" -ne 0; then + if [[ $defaults_count -ne 0 ]]; then defaults_last="$((defaults_count - 1))" can_revert_to_defaults='yes' can_cancel='yes' - if test "$option_confirm_default" = 'no'; then + 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 test "$option_required" = 'no'; then + if [[ $option_required == 'no' ]]; then can_cancel='yes' else can_cancel='no' fi fi - # adjust fallbacks for single vs multi mode - # fallbacks=(...) not actually needed, as never used - if test "$option_multi" = 'no' -a "$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='' \ @@ -862,11 +886,11 @@ function choose_() ( text__pick_nothing_cancel='cancel with nothing' text__pick_nothing_choose='choose from nothing' text__pick_nothing_confirm='confirm with nothing' - if test "$items_count" -eq 1 -a "$option_required" = 'yes'; then + if [[ $items_count -eq 1 && $option_required == 'yes' ]]; then text__pick_cancel='cancel to the' text__pick_choose='choose the' text__pick_confirm='confirm the' - elif test "$option_multi" = 'yes'; then + elif [[ $option_multi == 'yes' ]]; then text__pick_cancel='cancel to the' text__pick_choose='choose any of' text__pick_confirm='confirm the' @@ -883,15 +907,15 @@ function choose_() ( paging_supported='no' fi if ! get-terminal-title-support --quiet; then - # [ssh -T ...] passes [test ! -t 0] [test "$terminal_device_file" = '/dev/stderr'] [__command_exists tput] + # [ssh -T ...] passes [! -t 0] ["$terminal_device_file" = '/dev/stderr'] [__command_exists tput] title_supported='no' fi # prepare hints - if test -n "$question_title_prompt"; then + if [[ -n $question_title_prompt ]]; then menu_header+="$question_title_prompt"$'\n' fi - if test -n "$question_body_prompt"; then + if [[ -n $question_body_prompt ]]; then menu_header+="$question_body_prompt"$'\n' fi function add_legend_keys { @@ -910,13 +934,13 @@ function choose_() ( for ((index = 0; index < total; index++)); do key="${keys[index]}" inject+="${style__key}${key}${style__end__key}" - if test "$index" -ne "$last"; then + if [[ $index -ne $last ]]; then inject+="${style__key_key_spacer}" fi done - # apply the change performantly - if test -n "$content" && [[ $content != *$'\n' ]]; then # the trailing newline check is for [SEND LINE BUFFER] edge case + # apply the change performant + if [[ -n $content && $content != *$'\n' ]]; then # the trailing newline check is for [SEND LINE BUFFER] edge case content+="${style__legend_legend_spacer}${inject}" else content+="$inject" @@ -924,16 +948,16 @@ function choose_() ( eval "${name}_content=\"\$content\"" # append the content, not splitting legend keys across lines - this is disabled because it is not performant and in practice is not ideal - # if test "$rows" -eq 0; then + # if [[ "$rows" -eq 0 ]]; then # content+="$inject" # eval "${name}_content=\"\$content\"" # lines="$(echo-count-lines -- "$content")" # eval "${name}_rows=\"\$lines\"" - # elif test "$paging_supported" = 'yes'; then + # elif [[ "$paging_supported" = 'yes' ]]; then # shrunk="$(echo-wrap --width="$content_columns" -- "${content}${style__legend_legend_spacer}${inject}")" # lines="$(echo-count-lines -- "$shrunk")" # # if same rows as before, then append on same line, otherwise, append on new line - # if test "$rows" -eq "$lines"; then + # if [[ "$rows" -eq "$lines" ]]; then # content+="${style__legend_legend_spacer}${inject}" # else # content+=$'\n'"$inject" @@ -952,7 +976,7 @@ function choose_() ( rows="$(echo-wrap --width="$content_columns" -- "$content" | echo-count-lines --stdin)" eval "${name}_rows=\"$rows\"" } - function render_lengends { + function render_legends { # reset legends legend_cancel_content='' legend_cancel_rows=0 @@ -962,37 +986,37 @@ function choose_() ( legend_confirm_rows=0 # prefer key names if possible, as people don't know the symbols - if test "$option_multi" = 'yes'; then - add_legend_keys 'legend_cancel' 'CONFIRM CANCELATION' 'ENTER' 'E' + if [[ $option_multi == 'yes' ]]; then + add_legend_keys 'legend_cancel' 'CONFIRM CANCELLATION' 'ENTER' 'E' add_legend_keys 'legend_choose' 'SELECT' 'SPACE' add_legend_keys 'legend_choose' 'CONFIRM' 'ENTER' 'E' add_legend_keys 'legend_confirm' 'CONFIRM SELECTION' 'ENTER' 'E' else - add_legend_keys 'legend_cancel' 'CONFIRM CANCELATION' 'SPACE' 'ENTER' 'E' + add_legend_keys 'legend_cancel' 'CONFIRM CANCELLATION' 'SPACE' 'ENTER' 'E' add_legend_keys 'legend_choose' 'SELECT' 'SPACE' 'ENTER' 'E' add_legend_keys 'legend_confirm' 'CONFIRM SELECTION' 'SPACE' 'ENTER' 'E' fi - add_legend_keys 'legend_cancel' 'ABORT CANCELATION' 'ESC' 'Q' + add_legend_keys 'legend_cancel' 'ABORT CANCELLATION' 'ESC' 'Q' add_legend_keys 'legend_confirm' 'CHANGE SELECTION' 'ESC' 'Q' add_legend_keys 'legend_cancel' 'ABORT' 'CTRL C' add_legend_keys 'legend_confirm' 'ABORT' 'CTRL C' - if test "$can_cancel" = 'yes'; then + if [[ $can_cancel == 'yes' ]]; then # cancel restores defaults (if multi) or selects none (if non-multi) and leaves add_legend_keys 'legend_choose' 'CANCEL' 'ESC' 'Q' fi - if test "$items_count" -ne 1; then + if [[ $items_count -ne 1 ]]; then # [⬆⬇⇧] have alignment issues, use [↑↓] add_legend_keys 'legend_choose' 'UP' '↑' 'W' 'K' add_legend_keys 'legend_choose' 'DOWN' '↓' 'S' 'J' - if test "$can_revert_to_defaults" = 'yes'; then + 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 - if test "$option_multi" = 'yes'; then + add_legend_keys 'legend_choose' 'RESET' 'Z' 'R' # reset preferences + if [[ $option_multi == 'yes' ]]; then add_legend_keys 'legend_choose' 'ALL/NONE' 'T' fi fi - if test "$paging_supported" = 'no'; then + if [[ $paging_supported == 'no' ]]; then add_legend_keys 'legend_choose' 'FIRST' '←' 'A' 'H' 'HOME' 'fn ⇧ ←' add_legend_keys 'legend_choose' 'LAST' 'β†’' 'D' 'L' 'END' 'fn ⇧ β†’' else @@ -1001,14 +1025,14 @@ function choose_() ( add_legend_keys 'legend_choose' 'FIRST' 'HOME' 'fn ⇧ ←' add_legend_keys 'legend_choose' 'LAST' 'END' 'fn ⇧ β†’' fi - if test "$option_multi" = 'yes'; then + if [[ $option_multi == 'yes' ]]; then add_legend_keys 'legend_choose' 'SELECT & NEXT' '+' 'INSERT' add_legend_keys 'legend_choose' 'SELECT & PREV' '-' add_legend_keys 'legend_choose' 'UNSELECT & NEXT' 'DELETE' add_legend_keys 'legend_choose' 'UNSELECT & PREV' 'BACKSPACE' fi fi - if test "$paging_supported" = 'yes'; then + if [[ $paging_supported == 'yes' ]]; then legends_rows 'legend_cancel' legends_rows 'legend_choose' legends_rows 'legend_confirm' @@ -1024,7 +1048,7 @@ function choose_() ( legend_confirm_content+=': ' fi } - if test "$paging_supported" = 'no'; then + if [[ $paging_supported == 'no' ]]; then # no need for paging paging_used='no' # no need for row and column sizes @@ -1034,7 +1058,7 @@ function choose_() ( menu_header_rows=0 terminal_too_short='no' # set the legends. and disable cursors on line-buffering no-TTY mode as it is useful to see where the keys are being buffered - render_lengends + render_legends style__hide_cursor='' style__show_cursor='' # set rendering @@ -1067,14 +1091,14 @@ function choose_() ( terminal_columns="${terminal_size[1]}" # only recalculate everything if the size actually changed from last time - if test "$terminal_lines" -ne "$terminal_lines_prior" -o "$terminal_columns" -ne "$terminal_columns_prior"; then + if [[ $terminal_lines -ne $terminal_lines_prior || $terminal_columns -ne $terminal_columns_prior ]]; then content_columns="$((terminal_columns - terminal_margin))" # refresh header to column size menu_header_rows="$(echo-wrap --width="$content_columns" -- "$menu_header" | echo-count-lines --stdin)" # refresh the legends - render_lengends + render_legends # move start index to current item, as otherwise it could be out of range - if test "$terminal_lines" -lt "$terminal_lines_prior" -o "$terminal_columns" -lt "$terminal_columns_prior"; then + if [[ $terminal_lines -lt $terminal_lines_prior || $terminal_columns -lt $terminal_columns_prior ]]; then # @todo we should detect if showing everything is possible before resorting to this menu_top_index_choose="$menu_cursor_choose" menu_bottom_index_choose="$menu_cursor_choose" @@ -1089,7 +1113,7 @@ function choose_() ( terminal_lines_prior="$terminal_lines" terminal_columns_prior="$terminal_columns" fi - if test -n "$menu_resized_to" && test "$menu_resized_to" -gt "$terminal_lines"; then + if [[ -n $menu_resized_to && $menu_resized_to -gt $terminal_lines ]]; then terminal_too_short='yes' else terminal_too_short='no' @@ -1105,11 +1129,11 @@ function choose_() ( selected_count=0 selected_last=-1 } - if test "$option_multi" = 'yes'; then + if [[ $option_multi == 'yes' ]]; then function unselect_index { local index for index in "$@"; do - if test "${selected[index]-}" = 'yes'; then + if [[ ${selected[index]-} == 'yes' ]]; then unset 'selected[index]' fi done @@ -1120,7 +1144,7 @@ function choose_() ( function select_index { local index for index in "$@"; do - if test "${selected[index]-}" != 'yes'; then + if [[ ${selected[index]-} != 'yes' ]]; then selected[index]='yes' fi done @@ -1131,7 +1155,7 @@ function choose_() ( function toggle_index { local index for index in "$@"; do - if test "${selected[index]-}" = 'yes'; then + if [[ ${selected[index]-} == 'yes' ]]; then unset 'selected[index]' else selected[index]='yes' @@ -1145,27 +1169,27 @@ function choose_() ( select_index "${!items[@]}" } function select_defaults { - if test "$defaults_count" -ne 0; then # bash v3 compat + if [[ $defaults_count -ne 0 ]]; then # bash v3 compat select_index "${defaults_indexes[@]}" fi } function action_toggle_cursor { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then toggle_index "$menu_cursor_choose" - if test "$menu_cursor_choose" -eq "$menu_bottom_index_choose"; then + if [[ $menu_cursor_choose -eq $menu_bottom_index_choose ]]; then # don't cause a new page, instead keep rendering it as the bottom item menu_direction=-1 fi else toggle_index "$menu_cursor_confirm_and_cancel" - if test "$menu_cursor_confirm_and_cancel" -eq "$menu_bottom_index_confirm"; then + if [[ $menu_cursor_confirm_and_cancel -eq $menu_bottom_index_confirm ]]; then # don't cause a new page, instead keep rendering it as the bottom item menu_direction=-1 fi fi } function action_select_and_next { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then select_index "$menu_cursor_choose" else select_index "$menu_cursor_confirm_and_cancel" @@ -1173,7 +1197,7 @@ function choose_() ( action_down } function action_unselect_and_next { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then unselect_index "$menu_cursor_choose" else unselect_index "$menu_cursor_confirm_and_cancel" @@ -1181,7 +1205,7 @@ function choose_() ( action_down } function action_select_and_prior { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then select_index "$menu_cursor_choose" else select_index "$menu_cursor_confirm_and_cancel" @@ -1189,7 +1213,7 @@ function choose_() ( action_up } function action_unselect_and_prior { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then unselect_index "$menu_cursor_choose" else unselect_index "$menu_cursor_confirm_and_cancel" @@ -1201,7 +1225,7 @@ function choose_() ( } else function select_only_index { - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then selected=() selected["$1"]='yes' selected_indexes=("${!selected[@]}") @@ -1211,14 +1235,17 @@ function choose_() ( menu_cursor_confirm_and_cancel=0 fi } + function select_index { + select_only_index "$1" + } function select_defaults { # select only first preference - if test "$defaults_count" -ne 0; then + if [[ $defaults_count -ne 0 ]]; then select_only_index "${defaults_indexes[0]}" fi } function action_select_cursor { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then select_only_index "$menu_cursor_choose" else select_only_index "$menu_cursor_confirm_and_cancel" @@ -1237,21 +1264,29 @@ 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 - if test "$menu_mode" = 'choose'; then - if test "$index" -le 1; then + if [[ $menu_mode == 'choose' ]]; then + if [[ $index -le 1 ]]; then menu_cursor_choose=0 - elif test "$index" -ge "$items_count"; then + elif [[ $index -ge $items_count ]]; then menu_cursor_choose="$items_last" else menu_cursor_choose="$((index - 1))" fi else - if test "$index" -le 1; then + if [[ $index -le 1 ]]; then menu_cursor_confirm_and_cancel=0 - elif test "$index" -ge "$selected_count"; then + elif [[ $index -ge $selected_count ]]; then menu_cursor_confirm_and_cancel="$selected_last" else menu_cursor_confirm_and_cancel="$((index - 1))" @@ -1260,14 +1295,14 @@ function choose_() ( } function action_up { - if test "$menu_mode" = 'choose'; then - if test "$menu_cursor_choose" -le 0; then + if [[ $menu_mode == 'choose' ]]; then + if [[ $menu_cursor_choose -le 0 ]]; then menu_cursor_choose="$items_last" else menu_cursor_choose="$((menu_cursor_choose - 1))" fi else - if test "$menu_cursor_confirm_and_cancel" -le 0; then + if [[ $menu_cursor_confirm_and_cancel -le 0 ]]; then menu_cursor_confirm_and_cancel="$selected_last" else menu_cursor_confirm_and_cancel="$((menu_cursor_confirm_and_cancel - 1))" @@ -1275,38 +1310,38 @@ function choose_() ( fi } function action_down { - if test "$menu_mode" = 'choose'; then - if test "$menu_cursor_choose" -ge "$items_last"; then + if [[ $menu_mode == 'choose' ]]; then + if [[ $menu_cursor_choose -ge $items_last ]]; then menu_cursor_choose=0 else menu_cursor_choose="$((menu_cursor_choose + 1))" - if test "$menu_cursor_choose" -ge "$menu_bottom_index_choose"; then + if [[ $menu_cursor_choose -ge $menu_bottom_index_choose ]]; then # scroll down by one, rather than render whole new page menu_direction=-1 fi fi else - if test "$menu_cursor_confirm_and_cancel" -ge "$selected_last"; then + if [[ $menu_cursor_confirm_and_cancel -ge $selected_last ]]; then menu_cursor_confirm_and_cancel=0 else menu_cursor_confirm_and_cancel="$((menu_cursor_confirm_and_cancel + 1))" - if test "$menu_cursor_confirm_and_cancel" -ge "$menu_bottom_index_confirm"; then + if [[ $menu_cursor_confirm_and_cancel -ge $menu_bottom_index_confirm ]]; then # scroll down by one, rather than render whole new page menu_direction=-1 fi fi fi } - if test "$paging_supported" = 'yes'; then + if [[ $paging_supported == 'yes' ]]; then function action_page_up { - if test "$menu_mode" = 'choose'; then - if test "$menu_cursor_choose" -le "$menu_top_index_choose"; then + if [[ $menu_mode == 'choose' ]]; then + if [[ $menu_cursor_choose -le $menu_top_index_choose ]]; then menu_direction=-1 else menu_cursor_choose="$menu_top_index_choose" fi else - if test "$menu_cursor_confirm_and_cancel" -le "$menu_top_index_confirm"; then + if [[ $menu_cursor_confirm_and_cancel -le $menu_top_index_confirm ]]; then menu_direction=-1 else menu_cursor_confirm_and_cancel="$menu_top_index_confirm" @@ -1314,15 +1349,15 @@ function choose_() ( fi } function action_page_down { - if test "$menu_mode" = 'choose'; then - if test "$menu_cursor_choose" -ge "$menu_bottom_index_choose"; then + if [[ $menu_mode == 'choose' ]]; then + if [[ $menu_cursor_choose -ge $menu_bottom_index_choose ]]; then menu_direction=1 else menu_cursor_choose="$menu_bottom_index_choose" menu_direction=-1 fi else - if test "$menu_cursor_confirm_and_cancel" -ge "$menu_bottom_index_confirm"; then + if [[ $menu_cursor_confirm_and_cancel -ge $menu_bottom_index_confirm ]]; then menu_direction=1 else menu_cursor_confirm_and_cancel="$menu_bottom_index_confirm" @@ -1332,14 +1367,14 @@ function choose_() ( } fi function action_first { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then menu_cursor_choose=0 else menu_cursor_confirm_and_cancel=0 fi } function action_last { - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then menu_cursor_choose="$items_last" else menu_cursor_confirm_and_cancel="$selected_last" @@ -1347,10 +1382,11 @@ function choose_() ( } function __is_preference { local index="$1" - test "${selected[index]-}" = 'yes' -o "${defaults[index]-}" = 'yes' + [[ ${selected[index]-} == 'yes' || ${defaults[index]-} == 'yes' ]] + return } function action_next_preference { - if test "$menu_mode" != 'choose'; then + if [[ $menu_mode != 'choose' ]]; then action_down return fi @@ -1373,7 +1409,7 @@ function choose_() ( return 0 } function action_prior_preference { - if test "$menu_mode" != 'choose'; then + if [[ $menu_mode != 'choose' ]]; then action_up return fi @@ -1398,11 +1434,10 @@ function choose_() ( function set_menu_mode { local new="$1" # sanity check if new menu is appropriate - if test "$option_required" = 'yes'; then + if [[ $option_required == 'yes' ]]; then # if required, and trying to do proceed without items, then go back to choose and send a bell if - test "$selected_count" -eq 0 && test "$new" = 'confirm' -o "$new" = 'confirmed' || - test "$fallbacks_count" -eq 0 -a "$new" = 'cancel' + [[ ($selected_count -eq 0 && $new =~ ^(confirm|confirmed)$) || ($fallbacks_count -eq 0 && $new == 'cancel') ]] then menu_skip_render='yes' menu_mode='choose' @@ -1411,12 +1446,12 @@ function choose_() ( fi fi # check if we need to recalculate paging and update the mode - if test "$new" != "$menu_mode"; then - if test "$paging_supported" = 'yes'; then + if [[ $new != "$menu_mode" ]]; then + if [[ $paging_supported == 'yes' ]]; then paging_used='maybe' fi menu_mode="$new" - if test "$new" != 'choose'; then + if [[ $new != 'choose' ]]; then # reset cursor menu_cursor_confirm_and_cancel=0 fi @@ -1463,7 +1498,7 @@ function choose_() ( # refresh the terminal size refresh_terminal_size # fetch the appropriate size, must be done after refresh_terminal_size - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then verb='Selected' menu_bottom_index="$menu_bottom_index_choose" menu_cursor="$menu_cursor_choose" @@ -1473,7 +1508,7 @@ function choose_() ( menu_last="$items_last" menu_top_index="$menu_top_index_choose" else - if test "$menu_mode" = 'cancel'; then + if [[ $menu_mode == 'cancel' ]]; then verb='Cancel to' menu_total="$fallbacks_count" menu_last="$fallbacks_last" @@ -1492,20 +1527,20 @@ function choose_() ( menu_top_index="$menu_top_index_confirm" fi # adjust page direction - if test -z "$menu_index"; then - if test "$menu_cursor" -le 0; then + if [[ -z $menu_index ]]; then + if [[ $menu_cursor -le 0 ]]; then menu_cursor=0 menu_index="$menu_cursor" menu_direction=1 - elif test "$menu_cursor" -ge "$menu_last"; then + elif [[ $menu_cursor -ge $menu_last ]]; then menu_cursor="$menu_last" menu_index="$menu_cursor" menu_direction=-1 - elif test "$menu_cursor" -le "$menu_top_index"; then + elif [[ $menu_cursor -le $menu_top_index ]]; then menu_index="$menu_cursor" - elif test "$menu_cursor" -ge "$menu_bottom_index"; then + elif [[ $menu_cursor -ge $menu_bottom_index ]]; then menu_index="$menu_cursor" - elif test "$menu_direction" -eq 1; then + elif [[ $menu_direction -eq 1 ]]; then # maintain the same page, as the cursor is within it menu_index="$menu_top_index" else @@ -1514,7 +1549,7 @@ function choose_() ( fi fi # @TODO HANDLE NO SELECTED OPTIONS BETTER, PERHAPS AN IF HERE - if test "$paging_supported" = 'no'; then + if [[ $paging_supported == 'no' ]]; then menu_index=0 menu_direction=1 fi @@ -1528,15 +1563,15 @@ function choose_() ( } # e.g. go to second page, navigate half way, go page up for (( ; menu_index >= 0 && menu_index <= menu_last; menu_index += menu_direction)); do - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then item_index="$menu_index" - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then item_index="${fallbacks_indexes[menu_index]}" else item_index="${selected_indexes[menu_index]}" fi # determine prefix - if test "$menu_index" -eq "$menu_cursor"; then + if [[ $menu_index -eq $menu_cursor ]]; then line_begin="$style__active_line" line_prefix="$style__indent_active" line_end="$style__end__active_line" @@ -1545,17 +1580,17 @@ function choose_() ( line_prefix="$style__indent_inactive" line_end="$style__end__inactive_line" fi - if test "${selected[item_index]-}" = 'yes' -a "$menu_mode" != 'cancel'; then + if [[ ${selected[item_index]-} == 'yes' && $menu_mode != 'cancel' ]]; then line_begin+="$style__selected_line" line_prefix+="${style__icon_selected}" line_end+="$style__end__selected_line" bar_middle_selected_count="$((bar_middle_selected_count + 1))" - elif test "${defaults[item_index]-}" = 'yes'; then + elif [[ ${defaults[item_index]-} == 'yes' ]]; then line_begin+="$style__default_line" line_prefix+="${style__icon_default}" line_end+="$style__end__default_line" bar_middle_unselected_defaults_count="$((bar_middle_unselected_defaults_count + 1))" - elif test "$menu_index" -eq "$menu_cursor"; then + elif [[ $menu_index -eq $menu_cursor ]]; then line_prefix+="${style__icon_active}" else line_prefix+="${style__icon_standard}" @@ -1563,9 +1598,9 @@ function choose_() ( line_prefix="${line_begin}${line_prefix}" # determine paging - if test "$paging_used" = 'no'; then + if [[ $paging_used == 'no' ]]; then # paging is no longer needed, now row calculations needed, only need to blockquote the multiline item - if test -n "${items_renders[item_index]}"; then + if [[ -n ${items_renders[item_index]} ]]; then item_rendered="${items_renders[item_index]}" else item_original="${items[item_index]}" @@ -1577,12 +1612,12 @@ function choose_() ( item_line="${line_prefix}${item_rendered}${line_end}"$'\n' else # paging is needed, so we must recalculate bundled size - if test -n "${items_rows[item_index]}"; then + if [[ -n ${items_rows[item_index]} ]]; then item_rendered="${items_renders[item_index]}" item_rows="${items_rows[item_index]}" else item_original="${items[item_index]}" - if test "${#item_original}" -lt "$content_columns" && [[ $item_original != *$'\n'* && $item_original != *$'\t'* ]]; then + if [[ ${#item_original} -lt $content_columns && $item_original != *$'\n'* && $item_original != *$'\t'* ]]; then # no need to format item, as it is small enough item_rendered="$item_original" item_rows=1 @@ -1602,9 +1637,9 @@ function choose_() ( menu_rows="$((menu_header_rows + items_bundled_rows + item_rows + menu_legend_rows + 3))" item_line="${line_prefix}${item_rendered}${line_end}"$'\n' items_bundled_rows="$((items_bundled_rows + item_rows))" - if test "$menu_rows" -gt "$terminal_lines"; then - if test -z "$items_bundled"; then - # we need to reszie the terminal + if [[ $menu_rows -gt $terminal_lines ]]; then + if [[ -z $items_bundled ]]; then + # we need to resize the terminal menu_resized_to="$menu_rows" else # we already have content rendered, so exit with what we have @@ -1613,21 +1648,21 @@ function choose_() ( fi fi # append or prepend the line to the bundle - # item line will always be defined, no need for test -n - if test "$menu_direction" -eq 1; then + # item line will always be defined, no need for [[ -n ... ]] + if [[ $menu_direction -eq 1 ]]; then items_bundled+="$item_line" menu_bottom_index="$menu_index" else items_bundled="$item_line$items_bundled" menu_top_index="$menu_index" fi - if test -n "$menu_resized_to"; then + if [[ -n $menu_resized_to ]]; then break fi done # save paging changes - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then menu_top_index_choose="$menu_top_index" menu_bottom_index_choose="$menu_bottom_index" else @@ -1636,8 +1671,8 @@ function choose_() ( fi # calculate paging needed - if test "$paging_supported" = 'yes'; then - if test "$menu_top_index" -eq 0 -a "$menu_bottom_index" -eq "$menu_last"; then + if [[ $paging_supported == 'yes' ]]; then + if [[ $menu_top_index -eq 0 && $menu_bottom_index -eq $menu_last ]]; then paging_used='no' else paging_used='yes' @@ -1646,12 +1681,12 @@ function choose_() ( # if paging was used, and we could support more rows, then re-render in the other direction # @todo update menu_index instead of cursor, so that cursor remains sensible (optional, may not be a good idea) - if test "$paging_used" = 'yes' -a "$menu_rows" -lt "$terminal_lines"; then - if test "$menu_direction" -eq -1 -a "$menu_top_index" -eq 0; then + if [[ $paging_used == 'yes' && $menu_rows -lt $terminal_lines ]]; then + if [[ $menu_direction -eq -1 && $menu_top_index -eq 0 ]]; then menu_direction=1 render_menu 0 return - elif test "$menu_direction" -eq 1 -a "$menu_bottom_index" -eq "$menu_last"; then + elif [[ $menu_direction -eq 1 && $menu_bottom_index -eq $menu_last ]]; then menu_direction=-1 render_menu "$menu_last" return @@ -1659,24 +1694,24 @@ function choose_() ( fi # reset direction - if test "$menu_direction" -eq -1; then + if [[ $menu_direction -eq -1 ]]; then menu_direction=1 fi # resize the terminal if it is too short? - if test -n "$menu_resized_to"; then + if [[ -n $menu_resized_to ]]; then menu_title+=$'\e[8;'"$menu_resized_to"';t' fi # calculate page top, middle, bottom - if test -z "$items_bundled"; then + if [[ -z $items_bundled ]]; then # no items, no need to count page_items_count=0 - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_empty}${text__pick_nothing_choose} below${style__end__count_empty}" items_bundled="${style__active_line}${style__empty_line}${style__indent_active}${style__icon_nothing_provided}${style__end__empty_line}${style__end__active_line}"$'\n' bar_bottom_content+="${style__indent_bar}${style__bar_bottom}${style__count_empty}${text__pick_nothing_choose} above${style__end__count_empty}" - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_defaults}${text__pick_nothing_cancel} selected${style__end__count_empty}" items_bundled="${style__active_line}${style__empty_line}${style__indent_active}${style__icon_no_selection}${style__end__empty_line}${style__end__active_line}"$'\n' bar_bottom_content+="${style__indent_bar}${style__bar_bottom}${style__count_defaults}${text__pick_nothing_cancel} selected${style__end__count_empty}" @@ -1690,20 +1725,20 @@ function choose_() ( page_items_count="$((menu_bottom_index - menu_top_index + 1))" # calculate header counts - if test "$menu_top_index" -eq 0; then - if test "$menu_mode" = 'choose'; then + if [[ $menu_top_index -eq 0 ]]; then + if [[ $menu_mode == 'choose' ]]; then # choose bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_more}${text__pick_choose} ${menu_total} below${style__end__count_more}" bar_top_selected_count="$selected_count" for index in "${defaults_indexes[@]}"; do - if test "${selected[index]-}" = 'yes'; then + if [[ ${selected[index]-} == 'yes' ]]; then continue fi bar_top_unselected_defaults_count="$((bar_top_unselected_defaults_count + 1))" done - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # cancel - if test "$defaults_count" -ne "$fallbacks_count" -a "$fallbacks_count" -eq 1; then + if [[ $defaults_count -ne $fallbacks_count && $fallbacks_count -eq 1 ]]; then bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_defaults}${text__pick_cancel} first default below${style__end__count_defaults}" else bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_defaults}${text__pick_cancel} ${menu_total} defaults below${style__end__count_defaults}" @@ -1713,25 +1748,25 @@ function choose_() ( bar_top_content+="${style__indent_bar}${style__bar_top}${style__count_selected}${text__pick_confirm} ${menu_total} selected below${style__end__count_selected}" fi else - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then # choose bar_top_content+="${style__indent_bar}${style__bar_middle}${style__count_more}${menu_top_index} more above${style__end__count_more}" for index in "${selected_indexes[@]}"; do - if test "$index" -ge "$menu_top_index"; then + if [[ $index -ge $menu_top_index ]]; then break fi bar_top_selected_count="$((bar_top_selected_count + 1))" done for index in "${defaults_indexes[@]}"; do - if test "$index" -ge "$menu_top_index"; then + if [[ $index -ge $menu_top_index ]]; then break fi - if test "${selected[index]-}" = 'yes'; then + if [[ ${selected[index]-} == 'yes' ]]; then continue fi bar_top_unselected_defaults_count="$((bar_top_unselected_defaults_count + 1))" done - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # cancel bar_top_content+="${style__indent_bar}${style__bar_middle}${style__count_defaults}${menu_top_index} more defaults above${style__end__count_defaults}" else @@ -1741,20 +1776,20 @@ function choose_() ( fi # calculate bottom counts - if test "$menu_bottom_index" -eq "$menu_last"; then - if test "$menu_mode" = 'choose'; then + if [[ $menu_bottom_index -eq $menu_last ]]; then + if [[ $menu_mode == 'choose' ]]; then # choose bar_bottom_content+="${style__indent_bar}${style__bar_bottom}${style__count_more}${text__pick_choose} ${menu_total} above${style__end__count_more}" bar_bottom_selected_count="$selected_count" for index in "${defaults_indexes[@]}"; do - if test "${selected[index]-}" = 'yes'; then + if [[ ${selected[index]-} == 'yes' ]]; then continue fi bar_bottom_unselected_defaults_count="$((bar_bottom_unselected_defaults_count + 1))" done - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # cancel - if test "$defaults_count" -ne "$fallbacks_count" -a "$fallbacks_count" -eq 1; then + if [[ $defaults_count -ne $fallbacks_count && $fallbacks_count -eq 1 ]]; then bar_bottom_content+="${style__indent_bar}${style__bar_bottom}${style__count_defaults}${text__pick_cancel} first default above${style__end__count_defaults}" else bar_bottom_content+="${style__indent_bar}${style__bar_bottom}${style__count_defaults}${text__pick_cancel} ${menu_total} defaults above${style__end__count_defaults}" @@ -1765,22 +1800,22 @@ function choose_() ( fi else index="$((menu_last - menu_bottom_index))" - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then # choose bar_bottom_content+="${style__indent_bar}${style__bar_middle}${style__count_more}${index} more below" for index in "${selected_indexes[@]}"; do - if test "$index" -le "$menu_bottom_index"; then + if [[ $index -le $menu_bottom_index ]]; then continue fi bar_bottom_selected_count="$((bar_bottom_selected_count + 1))" done for index in "${defaults_indexes[@]}"; do - if test "$index" -le "$menu_bottom_index" -o "${selected[index]-}" = 'yes'; then + if [[ $index -le $menu_bottom_index || ${selected[index]-} == 'yes' ]]; then continue fi bar_bottom_unselected_defaults_count="$((bar_bottom_unselected_defaults_count + 1))" done - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # cancel bar_bottom_content+="${style__indent_bar}${style__bar_middle}${style__count_defaults}cancel to ${index} more defaults below${style__end__count_defaults}" else @@ -1789,33 +1824,33 @@ function choose_() ( fi fi - # add selected and unslected defaults counts - if test "$menu_mode" = 'choose'; then + # add selected and unselected defaults counts + if [[ $menu_mode == 'choose' ]]; then # choose: top bar - if test "$bar_top_selected_count" -ne 0; then + if [[ $bar_top_selected_count -ne 0 ]]; then bar_top_content+="${style__count_spacer}${style__count_selected}${bar_top_selected_count} selected${style__end__count_selected}" fi - if test "$bar_top_unselected_defaults_count" -ne 0; then + if [[ $bar_top_unselected_defaults_count -ne 0 ]]; then bar_top_content+="${style__count_spacer}${style__count_defaults}${bar_top_unselected_defaults_count} unselected defaults${style__end__count_defaults}" fi # choose: middle bar - if test "$paging_used" = 'yes'; then + if [[ $paging_used == 'yes' ]]; then bar_middle_content+="${style__indent_bar}${style__bar_middle}${style__count_more}${page_items_count} visible${style__end__count_more}" - if test "$bar_middle_selected_count" -ne 0; then + if [[ $bar_middle_selected_count -ne 0 ]]; then bar_middle_content+="${style__count_spacer}${style__count_selected}${bar_middle_selected_count} selected${style__end__count_selected}" fi - if test "$bar_middle_unselected_defaults_count" -ne 0; then + if [[ $bar_middle_unselected_defaults_count -ne 0 ]]; then bar_middle_content+="${style__count_spacer}${style__count_defaults}${bar_middle_unselected_defaults_count} unselected defaults${style__end__count_defaults}" fi fi # choose: bottom bar - if test "$bar_bottom_selected_count" -ne 0; then + if [[ $bar_bottom_selected_count -ne 0 ]]; then bar_bottom_content+="${style__count_spacer}${style__count_selected}${bar_bottom_selected_count} selected${style__end__count_selected}" fi - if test "$bar_bottom_unselected_defaults_count" -ne 0; then + if [[ $bar_bottom_unselected_defaults_count -ne 0 ]]; then bar_bottom_content+="${style__count_spacer}${style__count_defaults}${bar_bottom_unselected_defaults_count} unselected defaults${style__end__count_defaults}" fi - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # cancel bar_middle_content+="${style__indent_bar}${style__bar_middle}${style__count_defaults}${page_items_count} visible${style__end__count_defaults}" else @@ -1824,28 +1859,28 @@ function choose_() ( fi fi bar_top_content+="${style__end__bar_top}"$'\n' - if test -n "$bar_middle_content"; then + if [[ -n $bar_middle_content ]]; then bar_middle_content+="${style__end__bar_middle}"$'\n' fi bar_bottom_content+="${style__end__bar_bottom}"$'\n' # output menu - if test "$menu_mode" = 'choose'; then + if [[ $menu_mode == 'choose' ]]; then verb_total="$selected_count" else verb_total="$menu_total" fi - if test "$title_supported" = 'no'; then + if [[ $title_supported == 'no' ]]; then menu_title='' - elif test "$paging_used" = 'yes'; then + elif [[ $paging_used == 'yes' ]]; then menu_title+=$'\e]0;'"πŸ‘‰ ${verb} ${verb_total} of ${items_count} items πŸ’β€β™€οΈ Viewing ${page_items_count} of ${items_count} items [$((menu_top_index + 1))…$((menu_cursor + 1))…$((menu_bottom_index + 1))] πŸ‘ˆ"$'\a' else menu_title+=$'\e]0;'"πŸ‘‰ ${verb} ${verb_total} of ${items_count} items πŸ‘ˆ"$'\a' fi printf '%s' "${style__clear_screen}${style__hide_cursor}${menu_title}${menu_header}${bar_top_content}${bar_middle_content}${items_bundled}${bar_bottom_content}${menu_legend_content}" >"$terminal_device_file" - if test -n "$menu_resized_to"; then + if [[ -n $menu_resized_to ]]; then refresh_terminal_size - if test "$terminal_too_short" = 'yes'; then + if [[ $terminal_too_short == 'yes' ]]; then menu_title=$'\e]0;'"‼️ TERMINAL TOO SHORT ‼️"$'\a' local additional_rows="$((menu_resized_to - terminal_lines))" printf '%s' "${style__clear_screen}${style__hide_cursor}${menu_title}${style__error}You have been prompted to make a menu selection, however the terminal does not have enough vertical height to make that selection.${style__end__error}"$'\n'"${style__notice}Increase the terminal height or reduce font size to proceed, then press any key.${style__end__notice}"$'\n'"Terminal rows = ${terminal_lines}, Needed rows = ${menu_resized_to}, Additional rows = ${additional_rows}"$'\n' >"$terminal_device_file" @@ -1854,18 +1889,18 @@ function choose_() ( } function render_result { local render="${question_title_result}${commentary}" index item item_rendered - if test -n "$render"; then + if [[ -n $render ]]; then render+=$'\n' fi - if test "$menu_status" -eq 0; then + if [[ $menu_status -eq 0 ]]; then # render linger or temp - if test "$option_linger" = 'yes'; then + if [[ $option_linger == 'yes' ]]; then # add results only if lingering, as there may be more than terminal height, so clearing wouldn't support such - if test "$selected_count" -eq 0; then + if [[ $selected_count -eq 0 ]]; then render+="${style__commentary_nothing_selected}"$'\n' else for index in "${selected_indexes[@]}"; do - if test "${selected[index]}" = 'yes'; then + if [[ ${selected[index]} == 'yes' ]]; then item="${items[index]}" item_rendered="${item//$'\n'/$'\n'"$style__indent_blockquote"}" # re-add the necessary indentation render+="${style__result_line}${style__icon_selected}${item_rendered}${style__end__result_line}"$'\n' @@ -1874,20 +1909,20 @@ function choose_() ( fi # inform __print_string "$render" >"$terminal_device_file" - elif test -n "$commentary"; then + elif [[ -n $commentary ]]; then # inform to stderr, consistent with ask, choose, confirm __print_string "$render" >/dev/stderr fi # stdout for index in "${selected_indexes[@]}"; do - if test "${selected[index]}" = 'yes'; then + if [[ ${selected[index]} == 'yes' ]]; then __print_lines "${returns[index]}" fi done else # inform - if test -n "$commentary"; then - if test "$option_linger" = 'yes'; then + if [[ -n $commentary ]]; then + if [[ $option_linger == 'yes' ]]; then __print_string "$render" >"$terminal_device_file" else __print_string "$render" >/dev/stderr @@ -1901,23 +1936,23 @@ function choose_() ( # action show_cursor_on_exit __print_string "$style__alternative_screen_buffer" >"$terminal_device_file" - while true; do + while :; do # (re-)render the menu? - if test "$menu_skip_render" = 'no'; then + if [[ $menu_skip_render == 'no' ]]; then render_menu fi menu_skip_render='no' # handle the response eval_capture --statusvar=read_status --stdoutvar=input -- read-key --timeout="$option_timeout" - if test "$read_status" -eq 60; then - if test "$selected_count" -ne 0; then + if [[ $read_status -eq 60 ]]; then + if [[ $selected_count -ne 0 ]]; then # default commentary="${style__result_commentary_spacer}${style__commentary_timeout_default}" action_revert set_menu_mode 'exit' break - elif test "$option_required" = 'no'; then + elif [[ $option_required == 'no' ]]; then # optional commentary="${style__result_commentary_spacer}${style__commentary_timeout_optional}" action_none @@ -1930,20 +1965,20 @@ function choose_() ( set_menu_mode 'exit' break fi - elif test "$read_status" -eq 94; then + elif [[ $read_status -eq 94 ]]; then # unknown character, send bell and continue menu_skip_render='yes' __print_string "$style__bell" >"$terminal_device_file" continue - elif test "$read_status" -ne 0; then + elif [[ $read_status -ne 0 ]]; then # failure commentary="${style__result_commentary_spacer}$(printf "$style__commentary_input_failure" "read status: $read_status")" menu_status="$read_status" # error causes no selection set_menu_mode 'exit' break - elif test "$terminal_too_short" = 'yes'; then + elif [[ $terminal_too_short == 'yes' ]]; then continue - elif test -z "$input"; then + elif [[ -z $input ]]; then menu_skip_render='yes' continue fi @@ -1956,95 +1991,99 @@ function choose_() ( if is-digit -- "$key"; then action_jump "$key" # it may be tempting to make these (this action and below) non-multi selections, however that is not desired for timeouts, as timeouts should be empty if non-required, or the default if required, rather than whatever the user had their menu on, as timeout = escape/cancel - elif test "$key" = 'up' -o "$key" = 'w' -o "$key" = 'k'; then + elif [[ $key =~ ^(up|w|k)$ ]]; then action_up - elif test "$key" = 'down' -o "$key" = 's' -o "$key" = 'j'; then + elif [[ $key =~ ^(down|s|j)$ ]]; then action_down - elif test "$key" = 'left' -o "$key" = 'a' -o "$key" = 'h' -o "$key" = 'page-up'; then - if test "$paging_supported" = 'yes'; then + elif [[ $key =~ ^(left|a|h|page-up)$ ]]; then + if [[ $paging_supported == 'yes' ]]; then action_page_up else action_first fi - elif test "$key" = 'right' -o "$key" = 'd' -o "$key" = 'l' -o "$key" = 'page-down'; then - if test "$paging_supported" = 'yes'; then + elif [[ $key =~ ^(right|d|l|page-down)$ ]]; then + if [[ $paging_supported == 'yes' ]]; then action_page_down else action_last fi - elif test "$key" = 'home'; then + elif [[ $key == 'home' ]]; then action_first - elif test "$key" = 'end'; then + elif [[ $key == 'end' ]]; then action_last - elif test "$key" = 'tab'; then + elif [[ $key == 'tab' ]]; then action_next_preference - elif test "$key" = 'backtab'; then + elif [[ $key == 'backtab' ]]; then action_prior_preference - elif test "$menu_mode" = 'cancel'; then + elif [[ $menu_mode == 'cancel' ]]; then # CANCEL MENU - if test "$key" = 'enter' -o "$key" = 'e' || test "$key" = 'space' -a "$option_multi" = 'no'; then - action_revert + if [[ $key =~ ^(enter|e)$ || ($key == 'space' && $option_multi == 'no') ]]; then + action_fallbacks set_menu_mode 'confirmed' break - elif test "$key" = 'escape' -o "$key" = 'q'; then + elif [[ $key =~ ^(escape|q)$ ]]; then + if [[ $option_multi == 'no' ]]; then + action_revert + fi set_menu_mode 'choose' fi - elif test "$menu_mode" = 'confirm'; then + elif [[ $menu_mode == 'confirm' ]]; then # CONFIRM MENU - if test "$key" = 'enter' -o "$key" = 'e' || test "$key" = 'space' -a "$option_multi" = 'no'; then + if [[ $key =~ ^(enter|e)$ || ($key == 'space' && $option_multi == 'no') ]]; then set_menu_mode 'confirmed' break - elif test "$key" = 'escape' -o "$key" = 'q'; then + elif [[ $key =~ ^(escape|q)$ ]]; then set_menu_mode 'choose' fi - elif test "$key" = 'space'; then - if test "$option_multi" = 'yes'; then + elif [[ $key == 'space' ]]; then + if [[ $option_multi == 'yes' ]]; then action_toggle_cursor else action_select_cursor - if test "$option_confirm_input" = 'yes'; then + if [[ $option_confirm_input == 'yes' ]]; then set_menu_mode 'confirm' else set_menu_mode 'confirmed' break fi fi - elif test "$can_cancel" = 'yes' && test "$key" = 'escape' -o "$key" = 'q'; then - if test "$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 - elif test "$key" = 'enter' -o "$key" = 'e'; then - if test "$option_multi" = 'no'; then + elif [[ $key =~ ^(enter|e)$ ]]; then + if [[ $option_multi == 'no' ]]; then action_select_cursor fi - if test "$option_confirm_input" = 'yes'; then + if [[ $option_confirm_input == 'yes' ]]; then set_menu_mode 'confirm' else set_menu_mode 'confirmed' break fi - elif test "$key" = 'z'; then + elif [[ $key =~ ^(z|r)$ ]]; then action_revert - elif test "$option_multi" = 'yes'; then - if test "$key" = 't'; then - if test "$selected_count" -eq "$items_count"; then + elif [[ $option_multi == 'yes' ]]; then + if [[ $key == 't' ]]; then + if [[ $selected_count -eq $items_count ]]; then action_none else action_all fi - elif test "$key" = 'insert' -o "$key" = '+'; then + elif [[ $key =~ ^(insert|\+)$ ]]; then action_select_and_next - elif test "$key" = '-'; then + elif [[ $key == '-' ]]; then action_select_and_prior - elif test "$key" = 'backspace'; then + elif [[ $key == 'backspace' ]]; then action_unselect_and_prior - elif test "$key" = 'delete'; then + elif [[ $key == 'delete' ]]; then action_unselect_and_next - elif test "$key" = 'all'; then + elif [[ $key == 'all' ]]; then action_all else # nothing done, no need to repeat, just need to read again @@ -2057,7 +2096,7 @@ function choose_() ( continue fi done - if test "${#skips[@]}" -eq "${#keys[@]}"; then + if [[ ${#skips[@]} -eq ${#keys[@]} ]]; then # all were unknown characters, send bell and continue menu_skip_render='yes' __print_string "$style__bell" >"$terminal_device_file" @@ -2065,7 +2104,7 @@ function choose_() ( fi # finish if finished - if test "$menu_mode" = 'confirmed' -o "$menu_mode" = 'exit'; then + if [[ $menu_mode =~ ^(confirmed|exit)$ ]]; then break fi done @@ -2077,7 +2116,7 @@ function choose_() ( select_defaults # render if not skipping - if test "$can_skip_prompt" = 'yes'; then + if [[ $can_skip_prompt == 'yes' ]]; then : # we have defaults, and want to skip defaults else handle_menu @@ -2089,8 +2128,8 @@ function choose_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then choose_test else choose_ "$@" diff --git a/commands/choose-path b/commands/choose-path index 2e0e840a5..5a4aa8004 100755 --- a/commands/choose-path +++ b/commands/choose-path @@ -20,7 +20,7 @@ function choose_path() ( # process local item option_paths=() options=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -35,7 +35,7 @@ function choose_path() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -51,6 +51,6 @@ function choose_path() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then choose_path "$@" fi diff --git a/commands/command-exists b/commands/command-exists index b0105f4dc..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,12 +35,12 @@ 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. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,7 +48,7 @@ function command_exists() ( # process local item option_commands=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -64,7 +64,7 @@ function command_exists() ( done # check - if test "${#option_commands[@]}" -eq 0; then + if [[ ${#option_commands[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -77,8 +77,8 @@ function command_exists() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then command_exists_test else command_exists "$@" diff --git a/commands/command-missing b/commands/command-missing index e8dc51361..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,12 +35,12 @@ 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. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,7 +48,7 @@ function command_missing() ( # process local item option_commands=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -64,7 +64,7 @@ function command_missing() ( done # check - if test "${#option_commands[@]}" -eq 0; then + if [[ ${#option_commands[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -77,8 +77,8 @@ function command_missing() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then command_missing_test else command_missing "$@" diff --git a/commands/command-working b/commands/command-working index b9a77f4c5..0192acc4d 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" @@ -46,7 +46,7 @@ function command_working() ( QUIRKS: Returns on first failure. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -54,7 +54,7 @@ function command_working() ( # process local item option_commands=() option_sudo='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -73,7 +73,7 @@ function command_working() ( done # check - if test "${#option_commands[@]}" -eq 0; then + if [[ ${#option_commands[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -86,6 +86,7 @@ function command_working() ( local failures=() exceptions=( dash ksh + rmtrash rpi-update ssh-askpass sshd @@ -96,24 +97,24 @@ function command_working() ( function check_status { local cmd=() cmd_string cmd_exit_status cmd_output # ensure sbin commands work - if [[ $* == *sbin* ]] || test "$option_sudo" = 'yes'; then + if [[ $* == *sbin* || $option_sudo == 'yes' ]]; then cmd+=( 'sudo-helper' '--reason=Your sudo/root/login password is required to verify this command is available and working:' '--' ) fi - # continue with the comand + # continue with the command cmd+=("$@") # run the command hiding the output eval_capture --statusvar=cmd_exit_status --outputvar=cmd_output -- "${cmd[@]}" # check for correct failure codes - if test "$cmd_exit_status" -eq 22 -o "$cmd_exit_status" -eq 200; then + if [[ $cmd_exit_status -eq 22 || $cmd_exit_status -eq 200 ]]; then return 0 else # otherwise return success or failure code cmd_string="$(echo-escape-command -- "${cmd[@]}")" - if test -n "$cmd_output"; then + if [[ -n $cmd_output ]]; then cmd_output+=$'\n' fi failures+=("$( @@ -130,7 +131,7 @@ function command_working() ( local cmd cmd_string cmd_string="$(echo-escape-command -- "$1")" cmd="$(type -P "$1" 2>/dev/null || :)" - if test -z "$cmd"; then + if [[ -z $cmd ]]; then # we must at least find it echo-style --element/+red="$cmd_string" --status=3 ' ' --error='not working because it is missing' >/dev/stderr return 3 # ESRCH 3 No such process @@ -164,7 +165,7 @@ function command_working() ( local command action_exit_status for command in "${option_commands[@]}"; do eval_capture --statusvar=action_exit_status -- check_working "$command" - if test "$action_exit_status" -eq 0; then + if [[ $action_exit_status -eq 0 ]]; then continue else return "$action_exit_status" @@ -174,8 +175,8 @@ function command_working() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then command_working_test else command_working "$@" diff --git a/commands/config-edit b/commands/config-edit index 2adac014b..addfde181 100755 --- a/commands/config-edit +++ b/commands/config-edit @@ -42,7 +42,7 @@ function config_edit() ( --comparer= A command that is called that will compare the found lines (first argument) with the desired (second argument). - --editer= + --editor= A command that is called that will edit the configuration file. --applier= @@ -51,7 +51,7 @@ function config_edit() ( --fuse | --fstab | --sudoers | --hosts | --cron-system | --cron-user | --file= Each of these set appropriate defaults for those configurations. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -59,26 +59,27 @@ function config_edit() ( # helpers function default_comparer { - test "$1" = "$2" + [[ $1 == "$2" ]] + return } # process - local item option_name='' option_file='' option_action='' option_line='' option_needle='' option_searcher='' option_comparer='default_comparer' option_editer='' option_applier='' - while test "$#" -ne 0; do + local item option_name='' option_file='' option_action='' option_line='' option_needle='' option_searcher='' option_comparer='default_comparer' option_editor='' option_applier='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--action='*) option_action="${item#*=}" ;; '--no-add'* | '--add'*) - if test "$(get-flag-value --affirmative -- "$item")" = 'yes'; then + if [[ "$(get-flag-value --affirmative -- "$item")" == 'yes' ]]; then option_action='add' else option_action='remove' fi ;; '--no-remove'* | '--remove'*) - if test "$(get-flag-value --affirmative -- "$item")" = 'yes'; then + if [[ "$(get-flag-value --affirmative -- "$item")" == 'yes' ]]; then option_action='remove' else option_action='add' @@ -91,12 +92,12 @@ function config_edit() ( option_file="${item#*=}" option_name="$option_file" option_searcher='default_file_searcher' - option_editer='default_file_editer' + option_editor='default_file_editor' option_applier='default_file_applier' function default_file_searcher { rg --fixed-strings --regexp="$1" "$option_file" } - function default_file_editer { + function default_file_editor { edit --wait -- "$option_file" } function default_file_applier { @@ -109,17 +110,17 @@ function config_edit() ( '--needle='*) option_needle="${item#*=}" ;; '--searcher='*) option_searcher="${item#*=}" ;; '--comparer='*) option_comparer="${item#*=}" ;; - '--editer='*) option_editer="${item#*=}" ;; + '--editor='*) option_editor="${item#*=}" ;; '--applier='*) option_applier="${item#*=}" ;; '--fuse') option_name='fuse' option_searcher='default_fuse_searcher' - option_editer='default_fuse_editer' + option_editor='default_fuse_editor' option_applier='default_fuse_applier' function default_fuse_searcher { rg --fixed-strings --regexp="$1" --line-regexp '/etc/fuse.conf' } - function default_fuse_editer { + function default_fuse_editor { edit --wait --sudo -- '/etc/fuse.conf' } function default_fuse_applier { @@ -132,12 +133,12 @@ function config_edit() ( '--fstab') option_name='fstab' option_searcher='default_fstab_searcher' - option_editer='default_fstab_editer' + option_editor='default_fstab_editor' option_applier='default_fstab_applier' function default_fstab_searcher { rg --fixed-strings --regexp="$1" '/etc/fstab' } - function default_fstab_editer { + function default_fstab_editor { edit --wait --sudo -- '/etc/fstab' if is-linux; then sudo-helper -- systemctl daemon-reload @@ -156,11 +157,11 @@ function config_edit() ( '--sudoers') option_name='sudoers' option_searcher='default_sudoers_searcher' - option_editer='default_sudoers_editer' + option_editor='default_sudoers_editor' function default_sudoers_searcher { sudo-helper -- cat /etc/sudoers | rg --fixed-strings --regexp="$1" } - function default_sudoers_editer { + function default_sudoers_editor { # --inherit to maintain editor preferences sudo-helper --inherit -- visudo } @@ -168,12 +169,12 @@ function config_edit() ( '--hosts') option_name='fuse' option_searcher='default_hosts_searcher' - option_editer='default_hosts_editer' + option_editor='default_hosts_editor' option_applier='default_hosts_applier' function default_hosts_searcher { rg --fixed-strings --regexp="$1" --line-regexp '/etc/hosts.conf' } - function default_hosts_editer { + function default_hosts_editor { edit --wait --sudo -- '/etc/hosts' } function default_hosts_applier { @@ -187,14 +188,15 @@ function config_edit() ( option_name='cron[system]' option_comparer='default_cron_system_comparer' option_searcher='default_cron_system_searcher' - option_editer='default_cron_system_editer' + option_editor='default_cron_system_editor' function default_cron_system_comparer { - test "$1" = "$2" || [[ $1 == *cronitor*"$2"* ]] + [[ $1 == "$2" || $1 == *cronitor*"$2"* ]] + return } function default_cron_system_searcher { sudo-helper -- crontab -l | echo-wait | rg --fixed-strings --regexp="$1" } - function default_cron_system_editer { + function default_cron_system_editor { # --inherit to maintain editor preferences sudo-helper --inherit -- crontab -e || : if is-mac; then @@ -208,15 +210,16 @@ function config_edit() ( option_name="cron[$USER]" option_comparer='default_cron_user_comparer' option_searcher='default_cron_user_searcher' - option_editer='default_cron_user_editer' + option_editor='default_cron_user_editor' function default_cron_user_comparer { - test "$1" = "$2" || [[ $1 == *cronitor*"$2"* ]] + [[ $1 == "$2" || $1 == *cronitor*"$2"* ]] + return } function default_cron_user_searcher { crontab -l | echo-wait | rg --fixed-strings --regexp="$1" } - function default_cron_user_editer { - # user contrab already inherits editor preferences + function default_cron_user_editor { + # user crontab already inherits editor preferences crontab -e || : if is-mac; then service-helper --restart -- system/com.vix.cron @@ -237,14 +240,14 @@ function config_edit() ( fi fi - if test -z "$option_needle" -a -n "$option_line"; then + if [[ -z $option_needle && -n $option_line ]]; then option_needle="$option_line" fi - if test -z "$option_action"; then - if test -n "$option_line"; then + if [[ -z $option_action ]]; then + if [[ -n $option_line ]]; then option_action='add' - elif test -n "$option_needle"; then + elif [[ -n $option_needle ]]; then option_action='remove' else option_action='edit' @@ -252,40 +255,40 @@ function config_edit() ( fi # use [command -v] as we want builtins/functions as well - if test "$option_action" = 'edit'; then - if test -z "$option_editer" || ! command -v "$option_editer" >/dev/null; then - help 'The editer command ' --code="$option_editer" ' does not exist.' + if [[ $option_action == 'edit' ]]; then + if [[ -z $option_editor ]] || ! command -v "$option_editor" >/dev/null; then + help 'The editor command ' --code="$option_editor" ' does not exist.' else - "$option_editer" + "$option_editor" return fi fi - if test -z "$option_name"; then + if [[ -z $option_name ]]; then help 'You must provide a ' fi - if test -z "$option_searcher" || ! command -v "$option_searcher" >/dev/null; then + if [[ -z $option_searcher ]] || ! command -v "$option_searcher" >/dev/null; then help 'The provided searcher command ' --code="$option_searcher" ' does not exist.' fi - if test "$option_action" = 'has'; then - if test -z "$("$option_searcher" "$option_needle" || :)"; then + if [[ $option_action == 'has' ]]; then + if [[ -z "$("$option_searcher" "$option_needle" || :)" ]]; then return 1 else return 0 fi fi - if test -z "$option_comparer" || ! command -v "$option_comparer" >/dev/null; then + if [[ -z $option_comparer ]] || ! command -v "$option_comparer" >/dev/null; then help 'The provided comparer command ' --code="$option_comparer" ' does not exist.' fi - if test -z "$option_editer" || ! command -v "$option_editer" >/dev/null; then - help 'The provided editer command ' --code="$option_editer" ' does not exist.' + if [[ -z $option_editor ]] || ! command -v "$option_editor" >/dev/null; then + help 'The provided editor command ' --code="$option_editor" ' does not exist.' fi - if test -n "$option_applier" && ! command -v "$option_applier" >/dev/null; then + if [[ -n $option_applier ]] && ! command -v "$option_applier" >/dev/null; then help 'The provided applier command ' --code="$option_applier" ' does not exist.' fi @@ -294,17 +297,17 @@ function config_edit() ( # act local lines expected status - if test "$option_action" = 'add'; then + if [[ $option_action == 'add' ]]; then expected="$option_line" else expected='' fi - while true; do + while :; do lines="$("$option_searcher" "$option_needle" || :)" eval_capture --statusvar=status -- "$option_comparer" "$lines" "$expected" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then echo-style --invert="$option_name" ' is ' --positive='correctly' ' configured.' - if test -z "$expected"; then + if [[ -z $expected ]]; then echo-style --positive='It already has the undesired configuration removed.' else echo-style --positive='It already has these lines:' $'\n' --code="$option_line" | echo-trim-padding --stdin @@ -312,16 +315,16 @@ function config_edit() ( break else echo-style --invert="$option_name" ' is ' --negative='incorrectly' ' configured.' - if test -n "$lines"; then + if [[ -n $lines ]]; then echo-style --negative='These lines must be removed:' $'\n' --code="$lines" | echo-trim-padding --stdin fi - if test -n "$expected"; then + if [[ -n $expected ]]; then echo-style --positive='These lines must be added:' $'\n' --code="$expected" | echo-trim-padding --stdin fi - if test -n "$option_applier" && confirm --positive --ppid=$$ -- "$(echo-style --bold='Apply these changes ' --positive='automatically' --bold=', or ' --negative='manually' --bold='?')"; then + if [[ -n $option_applier ]] && confirm --positive --ppid=$$ -- "$(echo-style --bold='Apply these changes ' --positive='automatically' --bold=', or ' --negative='manually' --bold='?')"; then "$option_applier" "$option_needle" "$expected" # don't use lines, as applier uses regex, and found lines aren't escaped for regex elif confirm --positive --ppid=$$ -- "$(echo-style --bold='Ready to apply the changes ' --bold+negative='manually?')"; then - "$option_editer" + "$option_editor" else return 125 # ECANCELED 125 Operation cancelled fi @@ -330,6 +333,6 @@ function config_edit() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then config_edit "$@" fi diff --git a/commands/config-helper b/commands/config-helper index 003de4bd2..f7fd38546 100755 --- a/commands/config-helper +++ b/commands/config-helper @@ -174,7 +174,7 @@ function config_helper() ( If is empty, it will replace all empty lines with the replacement. If not found, an addition will occur. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -182,7 +182,7 @@ function config_helper() ( # process local item option_args=() option_file='' option_multiple='ok' option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -202,7 +202,7 @@ function config_helper() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_file"; then + if [[ -z $option_file ]]; then option_file="$item" else help "An unrecognised argument was provided: $item" @@ -212,17 +212,17 @@ function config_helper() ( done # check - if test "${#option_args[@]}" -eq 0; then + if [[ ${#option_args[@]} -eq 0 ]]; then help "Need arguments, otherwise what is the point?" fi # ensure file - if test -z "$option_file"; then + if [[ -z $option_file ]]; then help "Need a file to work with" fi # ensure file exists - if test ! -f "$option_file"; then + if [[ ! -f $option_file ]]; then touch "$option_file" fi @@ -235,7 +235,7 @@ function config_helper() ( # cycle function act { local option_search option_columns='1' option_quote='yes' option_replace search_pattern replace_pattern remove_pattern get_value_pattern addition find field value content count temp_file - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do # extract arguments option_search="$1" shift @@ -265,7 +265,7 @@ function config_helper() ( esac # adjust quote based on file - if [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then + if [[ $option_quote =~ ^(generic|yes)$ ]]; then if [[ $option_file == *'.bash' || $option_file == *'.sh' ]]; then option_quote='bash' else @@ -340,7 +340,7 @@ function config_helper() ( value="${option_replace#*=}" # check for field - if test -z "$field"; then + if [[ -z $field ]]; then help "--array=<$value> must be used with --field=" fi @@ -361,7 +361,7 @@ function config_helper() ( value="${option_replace#*=}" # check for field - if test -z "$field"; then + if [[ -z $field ]]; then help "--value=<$value> must be used with --field=" fi @@ -370,7 +370,7 @@ function config_helper() ( value="$(echo-escape-bash -- "$value")" elif [[ $option_quote == 'command' ]]; then value="$(echo-escape-command -- "$value")" - elif [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then + elif [[ $option_quote =~ ^(generic|yes)$ ]]; then value="$(echo-quote -- "$value")" fi addition="$field=$value" @@ -392,30 +392,30 @@ function config_helper() ( # ensure if there is a value, it only has a single trailing line, and if there isn't a value, it is empty content="$(cat "$option_file")" content="${content%$'\n'}" - if test -n "$content"; then + if [[ -n $content ]]; then content+=$'\n' fi addition="${addition%$'\n'}" - if test -n "$addition"; then + if [[ -n $addition ]]; then addition+=$'\n' fi replace_pattern="${replace_pattern%$'\n'}" - if test -n "$replace_pattern"; then + if [[ -n $replace_pattern ]]; then replace_pattern+=$'\n' fi # do the replacement or addition count="$(__print_value_strings_or_nothing "$content" | echo-regexp -cm --find="$search_pattern" || :)" - if test -z "$count" || test "$count" -eq 0; then + if [[ -z $count || $count -eq 0 ]]; then # it wasn't found, so add manually if it's not empty - if test -n "$addition"; then + if [[ -n $addition ]]; then __print_value_strings_or_nothing "$content" "$addition" >"$option_file" - UPDATED='yes' # a valid update occured, note for logging + UPDATED='yes' # a valid update occurred, note for logging fi - elif test "$count" -eq 1 -o "$option_multiple" = 'ok'; then + elif [[ $count -eq 1 || $option_multiple == 'ok' ]]; then # first match replaced, subsequent matches removed __print_value_strings_or_nothing "$content" | echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" >"$option_file" - UPDATED='yes' # a valid update occured, note for logging + UPDATED='yes' # a valid update occurred, note for logging else temp_file="$( fs-temp \ @@ -423,7 +423,7 @@ function config_helper() ( --directory="$(basename "$option_file")" \ --file --no-touch )" - if test "$option_multiple" = 'warn-skip'; then + if [[ $option_multiple == 'warn-skip' ]]; then # write update to temp file (for diff), do not update intended file __print_value_strings_or_nothing "$content" | echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" >"$temp_file" { @@ -431,11 +431,11 @@ function config_helper() ( fs-diff -- "$option_file" "$temp_file" || : echo-style --notice1=$'\n' } >/dev/stderr - elif test "$option_multiple" = 'warn-apply'; then + elif [[ $option_multiple == 'warn-apply' ]]; then # write backup to temp file, write update to intended file __print_value_strings_or_nothing "$content" >"$temp_file" echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" <"$temp_file" >"$option_file" - UPDATED='yes' # a valid update occured, note for logging + UPDATED='yes' # a valid update occurred, note for logging { echo-style --notice1='The configuration file ' --code-notice1="$option_file" --notice1=' ' --good1='was updated successfully.' $'\n' --info1='However,' --notice1=' only a single replacement is supported but multiple were performed. The changes that were made are a best effort upon evaluated values and may be incorrect if conditional values were used.' $'\n' --info1='Review the changes for correctness.' --notice1=' A backup has been made to ' --code-notice1="$temp_file" fs-diff -- "$temp_file" "$option_file" || : @@ -450,14 +450,14 @@ function config_helper() ( act "${option_args[@]}" # we do our own processing, as we need to support multiline matches - if test "$UPDATED" = 'yes' -a "$option_quiet" != 'yes'; then + if [[ $UPDATED == 'yes' && $option_quiet != 'yes' ]]; then echo-style --success="Updated configuration file: $option_file" >/dev/stderr fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then config_helper_test else config_helper "$@" diff --git a/commands/confirm b/commands/confirm index f08e19109..b23406039 100755 --- a/commands/confirm +++ b/commands/confirm @@ -110,7 +110,7 @@ function confirm_() ( Whether the prompt should persist afterwards. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -118,7 +118,7 @@ function confirm_() ( # prepare local item option_question=() option_mode='confirm' option_timeout='3600' option_ppid='' option_linger='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -145,21 +145,21 @@ function confirm_() ( done # mode - if test "$option_mode" = 'positive' -o "$option_mode" = 'negative'; then - if test -z "$option_timeout"; then + if [[ $option_mode =~ ^(positive|negative)$ ]]; then + if [[ -z $option_timeout ]]; then option_timeout='60' # one minute fi fi # checks - if test -z "$option_ppid"; then + if [[ -z $option_ppid ]]; then help ' is necessary' fi # question local question_title question_body - if test "${#option_question[@]}" -ne 0; then # bash v3 compat - if test -n "${option_question[0]}"; then + if [[ ${#option_question[@]} -ne 0 ]]; then # bash v3 compat + if [[ -n ${option_question[0]} ]]; then question_title="${option_question[0]}" question_body="$(__print_lines "${option_question[@]:1}")" else @@ -176,7 +176,7 @@ function confirm_() ( local terminal_device_file terminal_tty_support='yes' terminal_device_file="$(get-terminal-device-file)" - if test "$terminal_device_file" = '/dev/stderr'; then + if [[ $terminal_device_file == '/dev/stderr' ]]; then terminal_tty_support='no' fi @@ -194,8 +194,8 @@ function confirm_() ( # style the question local question_title_and_body='' question_title_result='' title_result_spacer legend_legend_spacer=' ' legend_indent=' ' - if test -n "$question_title"; then - if test -n "$question_body"; then + if [[ -n $question_title ]]; then + if [[ -n $question_body ]]; then question_title_and_body="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}"$'\n'"${style__question_body}${question_body}${style__end__question_body}"$'\n' else question_title_and_body="${style__question_title_prompt}${question_title}${style__end__question_title_prompt}"$'\n' @@ -208,7 +208,7 @@ function confirm_() ( # renders local options_unselected=() options_selected=() options_results=() selected_index='' selectable_count selectable_last yes_index no_index='' abort_index - if test "$option_mode" = 'positive' -o "$option_mode" = 'negative' -o "$option_mode" = 'bool'; then + if [[ $option_mode =~ ^(positive|negative|bool)$ ]]; then options_selected+=( "${legend_indent}${style__confirm_positive_active}" "${legend_legend_spacer}${style__confirm_negative_active}" @@ -223,15 +223,15 @@ function confirm_() ( "${title_result_spacer}${style__confirm_negative_result}" "${title_result_spacer}${style__confirm_abort_result}" ) - if test "$option_mode" = 'positive'; then + if [[ $option_mode == 'positive' ]]; then selected_index=0 - elif test "$option_mode" = 'negative'; then + elif [[ $option_mode == 'negative' ]]; then selected_index=1 fi # if bool, don't set index yes_index=0 no_index=1 abort_index=2 - elif test "$option_mode" = 'confirm'; then + elif [[ $option_mode == 'confirm' ]]; then options_selected+=( "${legend_indent}${style__confirm_proceed_active}" ) @@ -262,7 +262,7 @@ function confirm_() ( # re-render the keys prompt local str='' index for index in "${!options_unselected[@]}"; do - if test -n "$selected_index" && test "$index" -eq "$selected_index"; then + if [[ -n $selected_index && $index -eq $selected_index ]]; then str+="${options_selected[$index]}" else str+="${options_unselected[$index]}" @@ -272,7 +272,7 @@ function confirm_() ( } function on_confirm_terminate { RESULT=$? - if test -z "$RESULT" -o "$RESULT" -eq 0; then + if [[ -z $RESULT || $RESULT -eq 0 ]]; then RESULT=100 # EPROTO 100 Protocol error fi on_finish @@ -281,7 +281,7 @@ function confirm_() ( trap - SIGINT SIGTERM # this can run twice if ctrl+c - if test "$FINISHED" = 'yes'; then + if [[ $FINISHED == 'yes' ]]; then return "$RESULT" fi FINISHED='yes' @@ -291,10 +291,10 @@ function confirm_() ( # output the finale local result - if test "$RESULT" -le "$selectable_last"; then + if [[ $RESULT -le $selectable_last ]]; then # success response # inform if requested - if test "$option_linger" = 'yes'; then + if [[ $option_linger == 'yes' ]]; then result="${options_results[RESULT]}" __print_lines "$question_title_result$result$commentary" >"$terminal_device_file" fi @@ -302,15 +302,15 @@ function confirm_() ( # failure response # always inform result="${options_results[abort_index]}" - if test "$option_linger" = 'yes'; then + if [[ $option_linger == 'yes' ]]; then __print_lines "$question_title_result$result$commentary" >"$terminal_device_file" else # even if not lingering, ensure crash response is shown __print_lines "$question_title_result$result$commentary" >/dev/stderr fi # kill caller's parent - if test "$option_ppid" -ge 0; then - if test "$option_ppid" -ge 1; then + if [[ $option_ppid -ge 0 ]]; then + if [[ $option_ppid -ge 1 ]]; then # kill "-$option_ppid" ... # ^ fails because 130 isn't supported # kill -n ... ... @@ -348,23 +348,23 @@ function confirm_() ( # prep clearing of any input that leaked question_confirm_and_input_lines='' - if test "$terminal_tty_support" = 'no'; then + if [[ $terminal_tty_support == 'no' ]]; then for key in "${keys[@]}"; do - if test "$key" = 'enter' -o "$key" = 'line-buffer'; then + if [[ $key =~ ^(enter|line-buffer)$ ]]; then question_confirm_and_input_lines+=$'\n' fi done fi # handle status - if test "$read_status" -eq 60; then + if [[ $read_status -eq 60 ]]; then # timeout # default commentary="${style__result_commentary_spacer}${style__commentary_timeout_default}" - if test "$option_mode" = 'positive'; then + if [[ $option_mode == 'positive' ]]; then RESULT=0 break - elif test "$option_mode" = 'negative'; then + elif [[ $option_mode == 'negative' ]]; then RESULT=1 break else @@ -374,11 +374,11 @@ function confirm_() ( RESULT="$read_status" break fi - elif test "$read_status" -eq 94; then + elif [[ $read_status -eq 94 ]]; then # unknown character, send bell and continue __print_string $'\a' >"$terminal_device_file" # bell continue - elif test "$read_status" -ne 0; then + elif [[ $read_status -ne 0 ]]; then # failure commentary="${style__result_commentary_spacer}$(printf "$style__commentary_input_failure" "read status: $read_status")" RESULT="$read_status" @@ -389,7 +389,7 @@ function confirm_() ( for key in "${keys[@]}"; do case "$key" in 'Y' | 'y') - if test -n "${yes_index-}"; then + if [[ -n ${yes_index-} ]]; then RESULT="$yes_index" break else @@ -397,7 +397,7 @@ function confirm_() ( fi ;; 'N' | 'n') - if test -n "${no_index-}"; then + if [[ -n ${no_index-} ]]; then RESULT="$no_index" break else @@ -405,20 +405,21 @@ function confirm_() ( fi ;; 'left' | 'right' | 'up' | 'down') - if test -z "$selected_index" -o "$selectable_count" -eq 1; then - if test "$key" = 'right' -o "$key" = 'up'; then + # Yes the differences in combination is intentional... try it! + if [[ -z $selected_index || $selectable_count -eq 1 ]]; then + if [[ $key =~ ^(right|up)$ ]]; then selected_index="$selectable_last" - elif test "$key" = 'left' -o "$key" = 'down'; then + elif [[ $key =~ ^(left|down)$ ]]; then selected_index=0 fi - elif test "$key" = 'left' -o "$key" = 'up'; then - if test "$selected_index" -eq 0; then + elif [[ $key =~ ^(left|up)$ ]]; then + if [[ $selected_index -eq 0 ]]; then selected_index="$selectable_last" else selected_index="$((selected_index - 1))" fi - elif test "$key" = 'right' -o "$key" = 'down'; then - if test "$selected_index" -eq "$selectable_last"; then + elif [[ $key =~ ^(right|down)$ ]]; then + if [[ $selected_index -eq $selectable_last ]]; then selected_index=0 else selected_index="$((selected_index + 1))" @@ -426,11 +427,11 @@ function confirm_() ( fi ;; 'enter' | 'space') - if test -n "$selected_index"; then - if test "$selected_index" -eq 0; then + if [[ -n $selected_index ]]; then + if [[ $selected_index -eq 0 ]]; then RESULT=0 break - elif test "$selected_index" -eq 1; then + elif [[ $selected_index -eq 1 ]]; then # not possible in bool mode, as selected_index will never be 1, so nothing needed here RESULT=1 break @@ -449,7 +450,7 @@ function confirm_() ( done # check - if test -n "$RESULT"; then + if [[ -n $RESULT ]]; then break fi done @@ -458,8 +459,8 @@ function confirm_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then confirm_test else confirm_ "$@" diff --git a/commands/contains-line b/commands/contains-line index 770f3bdc7..d5f9991c2 100755 --- a/commands/contains-line +++ b/commands/contains-line @@ -31,10 +31,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -42,13 +42,13 @@ function contains_line() ( # process our own arguments, delegate everything else to stdinargs local item option_needles=() option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--needle='*) option_needles+=("${item#*=}") ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -56,7 +56,7 @@ function contains_line() ( ;; '--'*) option_args+=("$item") ;; *) - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then option_needles+=("$item") else option_args+=("$item") @@ -66,7 +66,7 @@ function contains_line() ( done # check - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -77,7 +77,7 @@ function contains_line() ( function on_line { local line="$1" needle for needle in "${option_needles[@]}"; do - if test "$needle" = "$line"; then + if [[ $needle == "$line" ]]; then # it was found, success case, exit immediately found='yes' return 210 # ECUSTOM 210 Processing complete, exit early @@ -86,13 +86,14 @@ function contains_line() ( # not found, continue reading } function on_finish { - test "$found" = 'yes' + [[ $found == 'yes' ]] + return } stdinargs "${option_args[@]}" ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then contains_line "$@" fi diff --git a/commands/cpr b/commands/cpr index cea35dd2c..674c77e79 100755 --- a/commands/cpr +++ b/commands/cpr @@ -2,6 +2,22 @@ # @todo figure out how to make --eval compatible with paths that have spaces +# @todo support these: +# https://github.com/abdfnx/tran +# https://github.com/dennis-tra/pcp +# https://github.com/devclub-iitd/SenData +# https://github.com/kern/filepizza +# https://github.com/lmangani/gunstore.io +# https://github.com/mozilla/send +# https://github.com/nils-werner/zget +# https://github.com/rockymadden/github-crypt +# https://github.com/schollz/croc +# https://github.com/ShareDropio/sharedrop +# https://github.com/SpatiumPortae/portal +# https://github.com/subins2000/WebDrop +# https://github.com/veeso/termscp +# https://github.com/zerotier/toss + function cpr_() ( source "$DOROTHY/sources/bash.bash" @@ -91,7 +107,7 @@ function cpr_() ( --owner=: if specified, uses this value for the ownership of copied files. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -102,7 +118,7 @@ function cpr_() ( local confirm='yes' dry='no' sudo='no' verify='no' local remove='no' checksum='no' linux='no' local owner='' # manual - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -133,7 +149,7 @@ function cpr_() ( linux="$(get-flag-value --affirmative --fallback="$linux" -- "$item")" ;; '--') - if test -n "$origin" -o -n "$destination"; then + if [[ -n $origin || -n $destination ]]; then help "[--] can only be used if and are not set via other means" fi origin="$1" @@ -142,11 +158,11 @@ function cpr_() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$item"; then + if [[ -z $item ]]; then help "Empty or argument provided." - elif test -z "$origin"; then + elif [[ -z $origin ]]; then origin="$item" - elif test -z "$destination"; then + elif [[ -z $destination ]]; then destination="$item" else help "An unrecognised argument was provided: $item" @@ -156,21 +172,21 @@ function cpr_() ( done # check origin and destination - if test -z "$origin" -o -z "$destination"; then + if [[ -z $origin || -z $destination ]]; then help "Both and must be specified." fi # ensure tool local tools=() - if test "$verify" = 'yes'; then - if test "${#verify_tools[@]}" -eq 0; then + if [[ $verify == 'yes' ]]; then + if [[ ${#verify_tools[@]} -eq 0 ]]; then help 'No verify tools are available.' || : return 42 # ENOPROTOOPT 42 Protocol not available else tools=("${verify_tools[@]}") fi else - if test "${#copy_tools[@]}" -eq 0; then + if [[ ${#copy_tools[@]} -eq 0 ]]; then help 'No copy tools are available.' || : return 42 # ENOPROTOOPT 42 Protocol not available else @@ -184,9 +200,9 @@ function cpr_() ( )" # rsync options - if test "$tool" = 'rsync'; then + if [[ $tool == 'rsync' ]]; then # ensure checksum, if copy or verify - if test -z "$checksum"; then + if [[ -z $checksum ]]; then if confirm --negative --ppid=$$ -- 'Compare files via checksum, instead of the quicker and default date and size comparison?'; then checksum='yes' else @@ -194,7 +210,7 @@ function cpr_() ( fi fi # ensure remove, if copy or verify - if test -z "$remove"; then + if [[ -z $remove ]]; then if confirm --negative --ppid=$$ -- 'Remove the source files after successful copies?'; then remove='yes' else @@ -202,7 +218,7 @@ function cpr_() ( fi fi # ensure linux, if copy or verify - if test -z "$linux"; then + if [[ -z $linux ]]; then if confirm --bool --ppid=$$ -- 'Are both machines Linux machines?'; then linux='yes' else @@ -215,7 +231,7 @@ function cpr_() ( function adjust_format { local thing="$1" value="$2" what last_char last_char="$(__substr "$value" -1)" - if test "$last_char" = '/'; then + if [[ $last_char == '/' ]]; then what='directory' else what='file' @@ -225,10 +241,10 @@ function cpr_() ( --question="Is the $thing a file or a directory? $(echo-style --code="$value")" \ --skip-default --default="$what" -- file directory )" - if test "$what" = 'file' -a "$last_char" = '/'; then + if [[ $what == 'file' && $last_char == '/' ]]; then value="$(__substr "$value" 0 -1)" # trim last char echo-style "Adjusted $thing to " --code="$value" >/dev/stderr - elif test "$what" = 'directory' -a "$last_char" != '/'; then + elif [[ $what == 'directory' && $last_char != '/' ]]; then value="${value}/" echo-style "Adjusted $thing to " --code="$value" >/dev/stderr fi @@ -237,7 +253,7 @@ function cpr_() ( function adjust_spaces { local thing="$1" value="$2" adjustment adjustment="$(echo-escape-spaces -- "$value")" - if test "$adjustment" != "$value"; then + if [[ $adjustment != "$value" ]]; then echo-style "Adjusted $thing to " --code="$value" >/dev/stderr fi __print_lines "$adjustment" @@ -245,7 +261,7 @@ function cpr_() ( function adjust_faux { local question suggestion tangent suggestion="$(get-local-to-remote "$1" 2>/dev/null || :)" - if test -n "$suggestion"; then + if [[ -n $suggestion ]]; then question="$( echo-style \ --bold+underline='What would you like to do?' --newline \ @@ -262,10 +278,10 @@ function cpr_() ( 'ignore' 'ignore suggestion and continue anyway' \ 'different' 'attempt a different tool' )" - if test "$tangent" = 'ignore'; then + if [[ $tangent == 'ignore' ]]; then echo-style --dim='Faux Remotes: ignore and continue' return 0 - elif test "$tangent" = 'different'; then + elif [[ $tangent == 'different' ]]; then echo-style --dim='Faux Remotes: attempting a different tool' cpr --confirm="$confirm" --dry="$dry" --sudo="$sudo" --verify="$verify" -- "$origin" "$destination" return @@ -277,12 +293,12 @@ function cpr_() ( fi return 0 } - if test "$confirm" != 'no'; then + if [[ $confirm != 'no' ]]; then origin="$(adjust_format origin "$origin")" destination="$(adjust_format destination "$destination")" origin="$(adjust_spaces origin "$origin")" destination="$(adjust_spaces destination "$destination")" - if test "$tool" = 'rsync'; then + if [[ $tool == 'rsync' ]]; then adjust_faux "$origin" adjust_faux "$destination" fi @@ -295,12 +311,12 @@ function cpr_() ( local cmd=() # adjustments - if test "$sudo" = 'yes'; then + if [[ $sudo == 'yes' ]]; then cmd+=(sudo) fi # tool - if test "$tool" = 'rsync'; then + if [[ $tool == 'rsync' ]]; then # via rsync, file by file progress cmd+=(rsync) @@ -327,7 +343,7 @@ function cpr_() ( ) # verify - if test "$verify" = 'yes'; then + if [[ $verify == 'yes' ]]; then # -i, --itemize-changes output a change-summary for all updates # -n, --dry-run show what would have been transferred cmd+=( @@ -337,7 +353,7 @@ function cpr_() ( fi # linux - if test "$linux" = 'yes'; then + if [[ $linux == 'yes' ]]; then # --acls, -A: preserve ACLs (implies --perms) # --xattrs, -X: preserve extended attributes # --atimes, -U: preserve access (use) times @@ -353,18 +369,18 @@ function cpr_() ( fi # checksum - if test "$checksum" = 'yes'; then + if [[ $checksum == 'yes' ]]; then # --checksum, -c: skip based on checksum, not mod-time & size cmd+=('--checksum') fi # remove - if test "$remove" = 'yes'; then + if [[ $remove == 'yes' ]]; then cmd+=('--remove-source-files') fi # owner - if test -n "$owner"; then + if [[ -n $owner ]]; then cmd+=("--chown=${owner}") fi @@ -376,7 +392,7 @@ function cpr_() ( # workaround for rsync always returning success exit code # https://superuser.com/q/1700581/32418 - if test "$verify" = 'yes'; then + if [[ $verify == 'yes' ]]; then function on_complete { if grep --quiet --regexp='^>' "$1"; then return 1 @@ -386,12 +402,12 @@ function cpr_() ( } fi - elif test "$tool" = 'gcp'; then + elif [[ $tool == 'gcp' ]]; then # via gcp, overall progress cmd+=('gcp') # sanity check - if test "$verify" = 'yes'; then + if [[ $verify == 'yes' ]]; then help "[--tool=gcp] is incompatible with [--verify]" fi @@ -412,12 +428,12 @@ function cpr_() ( "$destination" ) - elif test "$tool" = 'scp'; then + elif [[ $tool == 'scp' ]]; then # via scp cmd+=('scp') # sanity check - if test "$verify" = 'yes'; then + if [[ $verify == 'yes' ]]; then help "[--tool=scp] is incompatible with [--verify]" fi @@ -436,18 +452,18 @@ function cpr_() ( "$destination" ) - elif test "$tool" = 'cp'; then + elif [[ $tool == 'cp' ]]; then # via cp cmd+=('cp') # sanity check - if test "$verify" = 'yes'; then + if [[ $verify == 'yes' ]]; then help "[--tool=cp] is incompatible with [--verify]" fi # -a: Same as -pPR options. Preserves structure and attributes of files but not directory structure. # -P; If the -R option is specified, no symbolic links are followed. This is the default. - # -R: When source_file designates a directory, cp copies the directory and the entire subtree connected at that point. If the source_file ends in a /, the contents of the directory are copied rather than the directory itself. This option also causes symbolic links to be copied, rather than indirected through, and for cp to create spe- cial files rather than copying them as normal files. Created directories have the same mode as the corresponding source direc- tory, unmodified by the process' umask. + # -R: When source_file designates a directory, cp copies the directory and the entire subtree connected at that point. If the source_file ends in a /, the contents of the directory are copied rather than the directory itself. This option also causes symbolic links to be copied, rather than indirected through, and for cp to create special files rather than copying them as normal files. Created directories have the same mode as the corresponding source directory, unmodified by the process' umask. # # -L: If the -R option is specified, all symbolic links are followed. # -p: Cause cp to preserve the following attributes of each source file in the copy: modification time, access time, file flags, file mode, user ID, and group ID, as allowed by permissions. Access Control Lists (ACLs) and Extended Attributes (EAs), including resource forks, will also be preserved. @@ -467,11 +483,11 @@ function cpr_() ( "$destination" ) - elif test "$tool" = 'diff'; then + elif [[ $tool == 'diff' ]]; then cmd+=('diff') # sanity check - if test "$verify" != 'yes'; then + if [[ $verify != 'yes' ]]; then help "[--tool=diff] requires [--verify]" fi @@ -488,14 +504,14 @@ function cpr_() ( "$destination" ) - elif test "$tool" = 'git'; then + elif [[ $tool == 'git' ]]; then cmd+=( 'git' 'diff' ) # sanity check - if test "$verify" != 'yes'; then + if [[ $verify != 'yes' ]]; then help "[--tool=git] requires [--verify]" fi @@ -514,9 +530,9 @@ function cpr_() ( # run local output - if test "$dry" = 'yes'; then + if [[ $dry == 'yes' ]]; then __print_lines "${cmd[@]}" - elif test "$(type -t 'on_complete')" = 'function'; then + elif [[ "$(type -t 'on_complete')" == 'function' ]]; then __print_lines "${cmd[@]}" output="$(mktemp)" "${cmd[@]}" | tee "$output" @@ -528,6 +544,6 @@ function cpr_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then cpr_ "$@" fi diff --git a/commands/date-helper b/commands/date-helper index a1ddd3750..379dd1d4b 100755 --- a/commands/date-helper +++ b/commands/date-helper @@ -16,7 +16,7 @@ function date_helper() ( OPTIONS: --format=... - Set the outut format to use, e.g. '+%Y-%m-%d' + Set the output format to use, e.g. '+%Y-%m-%d' --month-ago | --monthago Makes the output date one month in the past. @@ -36,7 +36,7 @@ function date_helper() ( -- ... Forwards to the argument to the native date command. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -44,7 +44,7 @@ function date_helper() ( # process local item option_args=() option_format='' option_utc='' option_next_year='no' option_month_ago='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -98,13 +98,13 @@ function date_helper() ( # ===================================== # Action - if test "$option_utc" = 'yes'; then + if [[ $option_utc == 'yes' ]]; then # -u Display or set the date in UTC (Coordinated Universal) time. By default date displays the time in the time zone described by /etc/localtime or the TZ environment variable. option_args+=('-u') fi local year month - if test "$option_month_ago" = 'yes'; then + if [[ $option_month_ago == 'yes' ]]; then if is-mac; then args+=('-v' '-1m') else @@ -115,8 +115,8 @@ function date_helper() ( option_args+=("--date=$(date "+%Y")-$month-$(date "+%d")") fi fi - if test "$option_next_year" = 'yes'; then - if test "$option_month_ago" = 'yes'; then + if [[ $option_next_year == 'yes' ]]; then + if [[ $option_month_ago == 'yes' ]]; then help "Cannot use both --month-ago and --next-year" fi if is-mac; then @@ -131,9 +131,9 @@ function date_helper() ( fi # format - if test -n "$option_format"; then + if [[ -n $option_format ]]; then option_args+=("$option_format") - elif test "${#option_args[@]}" -eq 0; then + elif [[ ${#option_args[@]} -eq 0 ]]; then # https://stackoverflow.com/a/34138409/130638 option_args+=("+%FT%T%z") fi @@ -141,6 +141,6 @@ function date_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then date_helper "$@" fi diff --git a/commands/debug-bash b/commands/debug-bash index 72ba845cb..40f530af3 100755 --- a/commands/debug-bash +++ b/commands/debug-bash @@ -26,7 +26,7 @@ function debug_bash() ( # process local item option_bash='' bash_args=('-x') cmd=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -46,12 +46,12 @@ function debug_bash() ( done # check - if test "${#cmd[@]}" -eq 0; then + if [[ ${#cmd[@]} -eq 0 ]]; then help 'No was provided.' fi # fallback - if test -z "$option_bash"; then + if [[ -z $option_bash ]]; then option_bash="$(type -P bash)" fi @@ -61,7 +61,7 @@ function debug_bash() ( # invoke the command or function local cmd_path cmd_path="$(type -P "${cmd[0]}" 2>/dev/null || :)" - if test -n "$cmd_path" && [[ $cmd_path == "$DOROTHY"* ]]; then + if [[ -n $cmd_path && $cmd_path == "$DOROTHY"* ]]; then # command cmd[0]="$cmd_path" "$option_bash" "${bash_args[@]}" "${cmd[@]}" @@ -75,6 +75,6 @@ function debug_bash() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then debug_bash "$@" fi diff --git a/commands/debug-network b/commands/debug-network index d986faf55..8b91495ae 100755 --- a/commands/debug-network +++ b/commands/debug-network @@ -2,7 +2,7 @@ # can't do subshell wrapper, as in [sh] not [bash] # do not use any dorothy command, as this is eval'd on environments and situations where dorothy may not be available -if test "$*" = '--help'; then +if [ "$*" = '--help' ]; then cat <<-EOF >/dev/stderr ABOUT: Runs a series of network debugging commands. diff --git a/commands/debug-terminal-stdin b/commands/debug-terminal-stdin index 7a42cc073..379d18b1f 100755 --- a/commands/debug-terminal-stdin +++ b/commands/debug-terminal-stdin @@ -107,6 +107,6 @@ function debug_terminal_stdin() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then debug_terminal_stdin "$@" fi diff --git a/commands/debug-terminal-tty b/commands/debug-terminal-tty index a28f1c3ac..0a7b5814f 100755 --- a/commands/debug-terminal-tty +++ b/commands/debug-terminal-tty @@ -28,7 +28,7 @@ function debug_terminal_tty() ( file /dev/fd/* || : # /dev/pts/* - if test -e /dev/pts; then + if [[ -e /dev/pts ]]; then __print_lines '' '/dev/pts/*:' ls -l /dev/pts/* || : file /dev/pts/* || : @@ -56,28 +56,28 @@ function debug_terminal_tty() ( ( (tty -s && __print_lines tty_s_pass) || __print_lines tty_s_fail) || : - ( (test -t 0 && __print_lines test_t_stdin_pass) || __print_lines test_t_stdin_fail) || : - ( (test -t 1 && __print_lines test_t_stdout_pass) || __print_lines test_t_stdout_fail) || : - ( (test -t 2 && __print_lines test_t_stderr_pass) || __print_lines test_t_stderr_fail) || : + ( ([[ -t 0 ]] && __print_lines test_t_stdin_pass) || __print_lines test_t_stdin_fail) || : + ( ([[ -t 1 ]] && __print_lines test_t_stdout_pass) || __print_lines test_t_stdout_fail) || : + ( ([[ -t 2 ]] && __print_lines test_t_stderr_pass) || __print_lines test_t_stderr_fail) || : - ( (test -p /dev/stdin && __print_lines test_p_stdin_pass) || __print_lines test_p_stdin_fail) || : - ( (test -p /dev/stdout && __print_lines test_p_stdout_pass) || __print_lines test_p_stdout_fail) || : - ( (test -p /dev/stderr && __print_lines test_p_stderr_pass) || __print_lines test_p_stderr_fail) || : - ( (test -p /dev/tty && __print_lines test_p_tty_pass) || __print_lines test_p_tty_fail) || : + ( ([[ -p /dev/stdin ]] && __print_lines test_p_stdin_pass) || __print_lines test_p_stdin_fail) || : + ( ([[ -p /dev/stdout ]] && __print_lines test_p_stdout_pass) || __print_lines test_p_stdout_fail) || : + ( ([[ -p /dev/stderr ]] && __print_lines test_p_stderr_pass) || __print_lines test_p_stderr_fail) || : + ( ([[ -p /dev/tty ]] && __print_lines test_p_tty_pass) || __print_lines test_p_tty_fail) || : - ( (test -c /dev/stdin && __print_lines test_c_stdin_pass) || __print_lines test_c_stdin_fail) || : - ( (test -c /dev/stdout && __print_lines test_c_stdout_pass) || __print_lines test_c_stdout_fail) || : - ( (test -c /dev/stderr && __print_lines test_c_stderr_pass) || __print_lines test_c_stderr_fail) || : - ( (test -c /dev/tty && __print_lines test_c_tty_pass) || __print_lines test_c_tty_fail) || : + ( ([[ -c /dev/stdin ]] && __print_lines test_c_stdin_pass) || __print_lines test_c_stdin_fail) || : + ( ([[ -c /dev/stdout ]] && __print_lines test_c_stdout_pass) || __print_lines test_c_stdout_fail) || : + ( ([[ -c /dev/stderr ]] && __print_lines test_c_stderr_pass) || __print_lines test_c_stderr_fail) || : + ( ([[ -c /dev/tty ]] && __print_lines test_c_tty_pass) || __print_lines test_c_tty_fail) || : # check if reading is even possible (If TIMEOUT is 0, read returns immediately, without trying to read any data, returning success only if input is available on the specified file descriptor.) - ( (read -t 0 && __print_lines read_default_pass) || __print_lines read_default_fail) || : - ( (read -t 0 /dev/stderr && __print_lines noop_to_stderr_pass) || __print_lines noop_to_stderr_fail) || : ( (: >/dev/tty && __print_lines noop_to_tty_pass) || __print_lines noop_to_tty_fail) || : - ( (: /dev/stdin && __print_lines noop_bidirectonal_stdin_pass) || __print_lines noop_bidirectonal_stdin_fail) || : - ( (: /dev/stdout && __print_lines noop_bidirectonal_stdout_pass) || __print_lines noop_bidirectonal_stdout_fail) || : - ( (: /dev/stderr && __print_lines noop_bidirectonal_stderr_pass) || __print_lines noop_bidirectonal_stderr_fail) || : - ( (: /dev/tty && __print_lines noop_bidirectonal_tty_pass) || __print_lines noop_bidirectonal_tty_fail) || : + ( (: /dev/stdin && __print_lines noop_bidirectional_stdin_pass) || __print_lines noop_bidirectional_stdin_fail) || : + ( (: /dev/stdout && __print_lines noop_bidirectional_stdout_pass) || __print_lines noop_bidirectional_stdout_fail) || : + ( (: /dev/stderr && __print_lines noop_bidirectional_stderr_pass) || __print_lines noop_bidirectional_stderr_fail) || : + ( (: /dev/tty && __print_lines noop_bidirectional_tty_pass) || __print_lines noop_bidirectional_tty_fail) || : } local stdout stderr @@ -187,6 +187,6 @@ function debug_terminal_tty() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then debug_terminal_tty "$@" fi diff --git a/commands/dorothy b/commands/dorothy index ad7870e5e..4cee8f2fe 100755 --- a/commands/dorothy +++ b/commands/dorothy @@ -20,11 +20,11 @@ # ===================================== # Command -# do not use the cmd_ convention, as that would interefere with the dorothy worker +# do not use the cmd_ convention, as that would interfere with the dorothy worker function dorothy_() ( # sanity check that we are running in bash # sanity check, as [function .. () (], [elif], [&>], [local] are all bashisms, so other shells should have failed by now - if test -z "${BASH_VERSION-}"; then + if [[ -z ${BASH_VERSION-} ]]; then printf '%s\n' "Dorothy requires bash to run. Refer to the installation instructions: $dorothy_install" >/dev/stderr exit 6 # ENXIO 6 Device not configured fi @@ -34,24 +34,24 @@ function dorothy_() ( # vars that should be exported to subshells, which may or may not be inherited, as setup-environment-commands has not run yet local self="${BASH_SOURCE:-"$0"}" is_remote - if [[ $self == *'.local/share/dorothy/commands/dorothy' ]] && test -f "$self"; then + if [[ $self == *'.local/share/dorothy/commands/dorothy' && -f $self ]]; then is_remote='no' else is_remote='yes' fi export DOROTHY ZDOTDIR PATH XDG_CONFIG_HOME XDG_CACHE_HOME XDG_BIN_HOME XDG_DATA_HOME XDG_STATE_HOME - if test -z "${DOROTHY-}"; then + if [[ -z ${DOROTHY-} ]]; then # handle (1) fresh installation situation and (2) cron situation (dorothy is installed, however environment is empty) # `env -i "$(which dorothy)" run env` <-- whoami returns user who has dorothy installed: # `sudo env -i "$(which dorothy)" run env` <-- whoami returns root, who does not have dorothy installed - if test "$is_remote" = 'no'; then + if [[ $is_remote == 'no' ]]; then # handle fresh environment situation / cron situation DOROTHY="${self%/commands/dorothy*}" - if test -z "${HOME-}"; then + if [[ -z ${HOME-} ]]; then export HOME HOME="${self%/.local/share/dorothy/commands/dorothy*}" fi - if test -z "${USER-}"; then + if [[ -z ${USER-} ]]; then export USER USER="$(basename "$HOME")" fi @@ -60,22 +60,22 @@ function dorothy_() ( DOROTHY='' fi fi - if test -z "${ZDOTDIR-}"; then + if [[ -z ${ZDOTDIR-} ]]; then ZDOTDIR='' fi - if test -z "${XDG_CONFIG_HOME-}"; then + if [[ -z ${XDG_CONFIG_HOME-} ]]; then XDG_CONFIG_HOME="$HOME/.config" fi - if test -z "${XDG_CACHE_HOME-}"; then + if [[ -z ${XDG_CACHE_HOME-} ]]; then XDG_CACHE_HOME="$HOME/.cache" fi - if test -z "${XDG_BIN_HOME-}"; then + if [[ -z ${XDG_BIN_HOME-} ]]; then XDG_BIN_HOME="$HOME/.local/bin" fi - if test -z "${XDG_DATA_HOME-}"; then + if [[ -z ${XDG_DATA_HOME-} ]]; then XDG_DATA_HOME="$HOME/.local/share" fi - if test -z "${XDG_STATE_HOME-}"; then + if [[ -z ${XDG_STATE_HOME-} ]]; then XDG_STATE_HOME="$HOME/.local/state" fi @@ -138,12 +138,16 @@ function dorothy_() ( dorothy run -- [...] - Invoke the within the Dorothy environment, without the need to isntall Dorothy to access the command. + Invoke the within the Dorothy environment, without the need to install Dorothy to access the command. dorothy update Updates Dorothy to the latest version. + dorothy commands + + List all the Dorothy commands that are available to you. + dorothy permissions Correct permissions of new Dorothy commands, ensuring they are executable, and that git is aware of them. @@ -175,17 +179,19 @@ function dorothy_() ( dorothy lint Automatically format and check Dorothy's source code to its conventions. + Files are processed by most recently modified first. dorothy test Run tests on Dorothy's source code. + Files are processed by most recently modified first. dorothy todos Output information about possible improvements to make to Dorothy. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then # use printf, as __print_line not loaded yet printf '%s' "ERROR: $*" >/dev/stderr fi @@ -195,7 +201,7 @@ function dorothy_() ( # process action arguments # @todo prevent double-action overwrite local item action='' option_xdg='' option_slug option_branch option_reference option_remote_name option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -264,12 +270,15 @@ function dorothy_() ( 'todo' | 'todos') action='todos' ;; + 'commands') + action='commands' + ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) help "An unrecognised argument was provided: $item" ;; esac done - if test -z "$action"; then + if [[ -z $action ]]; then help " must be provided." fi @@ -292,7 +301,7 @@ function dorothy_() ( local the_upstream_git_https="https://github.com/$the_slug.git" local the_bash_url="https://raw.githubusercontent.com/$the_slug/$the_reference/sources/bash.bash" local the_browse_url - if test "$the_slug" = "$dorothy_slug" -a "$the_reference" = "$dorothy_reference"; then + if [[ $the_slug == "$dorothy_slug" && $the_reference == "$dorothy_reference" ]]; then the_browse_url="https://github.com/$the_slug" else the_browse_url="https://github.com/$the_slug/tree/$the_reference" @@ -302,7 +311,7 @@ function dorothy_() ( # Sourcing # the local installation exists, and the invoked [dorothy] command is of the local installation - if test "$is_remote" = 'no' -a -n "$DOROTHY" -a -f "$DOROTHY/sources/bash.bash"; then + if [[ $is_remote == 'no' && -n $DOROTHY && -f "$DOROTHY/sources/bash.bash" ]]; then # match with the local installation's [bash.bash] source "$DOROTHY/sources/bash.bash" else @@ -315,29 +324,33 @@ function dorothy_() ( # see [commands/is-mac] for details function __is_mac { - test "$(uname -s)" = 'Darwin' + [[ "$(uname -s)" == 'Darwin' ]] + return } # see [commands/is-linux] for details function __is_linux { - test "$(uname -s)" = 'Linux' + [[ "$(uname -s)" == 'Linux' ]] + return } # see [commands/is-manjaro] for details function __is_manjaro { uname -r | grep --quiet --ignore-case --fixed-strings --regexp='manjaro' + return } # see [commands/is-alpine] for details function __is_alpine { - test -f /etc/os-release && grep --quiet --ignore-case --regexp='ID=alpine' /etc/os-release 2>/dev/null + [[ -f /etc/os-release ]] && grep --quiet --ignore-case --regexp='ID=alpine' /etc/os-release 2>/dev/null + return } # see [commands/fs-realpath] for details function __fs_realpath { # options local item paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -365,7 +378,7 @@ function dorothy_() ( if __is_mac; then # usage: realpath [-q] [path ...] fallback_realpath='realpath' - elif __is_alpine && test "$(realpath "$(type -P 'realpath')" || :)" = "$(type -P busybox || :)"; then + elif __is_alpine && [[ "$(realpath "$(type -P 'realpath')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: realpath FILE... fallback_realpath='realpath' elif __is_linux; then @@ -378,7 +391,7 @@ function dorothy_() ( if __is_mac; then # usage: readlink [-fn] [file ...] fallback_readlink='readlink' - elif __is_alpine && test "$(readlink -f "$(type -P 'readlink')" || :)" = "$(type -P busybox || :)"; then + elif __is_alpine && [[ "$(readlink -f "$(type -P 'readlink')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: readlink [-fnv] FILE fallback_readlink='readlink' elif __is_linux; then @@ -389,16 +402,16 @@ function dorothy_() ( # --resolve=yes --validate=yes --relative=no local path for path in "${paths[@]}"; do - if test -n "$gnu_realpath"; then + if [[ -n $gnu_realpath ]]; then "$gnu_realpath" --canonicalize-existing "$path" return - elif test -n "$gnu_readlink"; then + elif [[ -n $gnu_readlink ]]; then "$gnu_readlink" --canonicalize-existing "$path" return - elif test -n "$fallback_readlink"; then + elif [[ -n $fallback_readlink ]]; then "$fallback_readlink" -f "$path" return - elif test -n "$fallback_realpath"; then + elif [[ -n $fallback_realpath ]]; then "$fallback_realpath" "$path" return elif __command_exists -- fish; then @@ -417,7 +430,7 @@ function dorothy_() ( function __eval_wrap { # trim -- prefix - if test "${1-}" = '--'; then + if [[ ${1-} == '--' ]]; then shift fi # proceed @@ -460,8 +473,8 @@ function dorothy_() ( __try_sudo urpmi --auto bash curl git gawk elif __command_exists -- dnf; then # for fedora - __try_sudo dnf --assumeyes --refresh --best --allowerasing install bash curl git coreutils moreutils gawk - elif test -n "${HOMEBREW_PREFIX-}" -a -x "${HOMEBREW_PREFIX-}/bin/brew"; then + __try_sudo dnf install --assumeyes --refresh --best --allowerasing bash curl git coreutils moreutils gawk + elif [[ -n ${HOMEBREW_PREFIX-} && -x "$HOMEBREW_PREFIX/bin/brew" ]]; then # this is here for consistency only, as it is unnecessary, all the commands already exist on macos without any need for homebrew "$HOMEBREW_PREFIX/bin/brew" install bash curl git coreutils moreutils gawk elif __command_exists -- xbps-install; then @@ -493,7 +506,7 @@ function dorothy_() ( # ensure coreutils is installed on alpine, otherwise [env] won't support [-S] # Usage: env [-i0] [-u NAME]... [-] [NAME=VALUE]... [PROG ARGS] - if __is_alpine && test "$(realpath "$(type -P 'env')" || :)" = "$(type -P busybox || :)"; then + if __is_alpine && [[ "$(realpath "$(type -P 'env')" || :)" == "$(type -P busybox || :)" ]]; then __try_sudo apk add coreutils fi } @@ -545,7 +558,7 @@ function dorothy_() ( function assert_dorothy_configured { # DOROTHY is determined earlier, so this checks it is installed - if test ! -d "$DOROTHY"; then + if [[ ! -d $DOROTHY ]]; then echo-style \ --error='Dorothy is not installed.' $'\n' \ 'Nothing was found at: ' --code="$DOROTHY" $'\n' \ @@ -565,7 +578,7 @@ function dorothy_() ( } function assert_user_cloned { - if test ! -d "$DOROTHY/user/commands.local" -o ! -d "$DOROTHY/user/commands" -o ! -d "$DOROTHY/user/config.local" -o ! -d "$DOROTHY/user/config"; then + if [[ ! -d "$DOROTHY/user/commands.local" || ! -d "$DOROTHY/user/commands" || ! -d "$DOROTHY/user/config.local" || ! -d "$DOROTHY/user/config" ]]; then echo-style \ --error='Dorothy user configuration is not configured correctly.' $'\n' \ --warning='Run' --code=' dorothy install ' --warning='to install, then you can run' --code=" dorothy $action" @@ -591,7 +604,7 @@ function dorothy_() ( local file for file in "$@"; do # -s (file exists and is not empty) - if test -s "$file"; then + if [[ -s $file ]]; then echo-file -- "$file" if confirm --positive --ppid=$$ -- 'Unless you are an experienced user who has already made modifications to this shell configuration file, it can be safely removed for a lighter installation. Remove this file?'; then rm "$file" @@ -603,7 +616,7 @@ function dorothy_() ( function make_executable { # makes sure chmod isn't running on nothing, which can happen upon initial user installs - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then chmod -f +x "$@" fi } @@ -613,10 +626,10 @@ function dorothy_() ( local source="$1" destination="$2" temp # if source is a symlink, resolve it and remove it - if test -L "$source"; then + if [[ -L $source ]]; then temp="$source" # resolve where the source actually is - if test -e "$destination"; then + if [[ -e $destination ]]; then source="$(__fs_realpath -- "$source")" else source='' @@ -626,8 +639,8 @@ function dorothy_() ( fi # if destination is a symlink, resolve it, move it, and remove it - if test -L "$destination"; then - if test -e "$destination"; then + if [[ -L $destination ]]; then + if [[ -e $destination ]]; then temp="$(__fs_realpath -- "$destination")" # remove the destination symlink rm -f "$destination" @@ -640,7 +653,7 @@ function dorothy_() ( fi # do we need to move and merge the source with the destination? - if test -n "$source" -a -n "$destination" -a "$source" != "$destination" -a -e "$source"; then + if [[ -n $source && -n $destination && $source != "$destination" && -e $source ]]; then mv -fv "$source" "$destination" fi @@ -663,17 +676,17 @@ function dorothy_() ( fi # cleanup accidents symlink="$symlink/$(basename "$destination")" - if test -L "$symlink"; then + if [[ -L $symlink ]]; then rm -f "$symlink" fi } function relocate_dorothy_if_necessary { # move if necessary - if test -d "$DOROTHY" -o -d "$XDG_DATA_HOME/dorothy"; then - if test "$option_xdg" = 'yes'; then + if [[ -d $DOROTHY || -d "$XDG_DATA_HOME/dorothy" ]]; then + if [[ $option_xdg == 'yes' ]]; then relocate_then_symlink_if_necessary "$DOROTHY" "$XDG_DATA_HOME/dorothy" - elif test "$option_xdg" = 'no'; then + elif [[ $option_xdg == 'no' ]]; then relocate_then_symlink_if_necessary "$XDG_DATA_HOME/dorothy" "$DOROTHY" relocate_then_symlink_if_necessary "$HOME/.local/share/dorothy" "$DOROTHY" else @@ -688,11 +701,11 @@ function dorothy_() ( function relocate_user_if_necessary { # move if necessary - if test -d "$DOROTHY/user" -o -d "$XDG_CONFIG_HOME/dorothy"; then - if test "$option_xdg" = 'yes'; then + if [[ -d "$DOROTHY/user" || -d "$XDG_CONFIG_HOME/dorothy" ]]; then + if [[ $option_xdg == 'yes' ]]; then # ensure $DOROTHY/user always exists relocate_then_symlink_if_necessary "$DOROTHY/user" "$XDG_CONFIG_HOME/dorothy" - elif test "$option_xdg" = 'no'; then + elif [[ $option_xdg == 'no' ]]; then relocate_then_symlink_if_necessary "$XDG_CONFIG_HOME/dorothy" "$DOROTHY/user" else # invalid option_xdg value @@ -700,10 +713,10 @@ function dorothy_() ( fi # ensure correct permissions - if test -d "$DOROTHY/user/commands"; then + if [[ -d "$DOROTHY/user/commands" ]]; then make_executable "$DOROTHY/user/commands/"* fi - if test -d "$DOROTHY/user/commands.local"; then + if [[ -d "$DOROTHY/user/commands.local" ]]; then make_executable "$DOROTHY/user/commands.local/"* fi fi # else fresh install and no prior clones exist @@ -712,12 +725,12 @@ function dorothy_() ( function relocate_zsh_if_necessary { # https://zsh.sourceforge.io/Intro/intro_3.html # determine ZDOTDIR - if test "$option_xdg" = 'yes'; then - if test -z "$ZDOTDIR"; then + if [[ $option_xdg == 'yes' ]]; then + if [[ -z $ZDOTDIR ]]; then ZDOTDIR="$XDG_CONFIG_HOME/zsh" fi - elif test "$option_xdg" = 'no'; then - if test -z "$ZDOTDIR"; then + elif [[ $option_xdg == 'no' ]]; then + if [[ -z $ZDOTDIR ]]; then ZDOTDIR="$HOME" fi else @@ -736,12 +749,12 @@ function dorothy_() ( function prepare_dorothy { # if no DOROTHY env override, attempt DOROTHY from possible existing installations - if test -z "$DOROTHY"; then - if test -d "$XDG_DATA_HOME/dorothy"; then + if [[ -z $DOROTHY ]]; then + if [[ -d "$XDG_DATA_HOME/dorothy" ]]; then DOROTHY="$XDG_DATA_HOME/dorothy" - elif test -d "$HOME/.local/share/dorothy"; then + elif [[ -d "$HOME/.local/share/dorothy" ]]; then DOROTHY="$HOME/.local/share/dorothy" - elif test -x ./commands/dorothy; then + elif [[ -x ./commands/dorothy ]]; then DOROTHY="$(pwd)" # github actions, cron, etc else # no existing installation @@ -752,7 +765,7 @@ function dorothy_() ( fi # adjust XDG based on determination above - if test "$DOROTHY" = "$XDG_DATA_HOME/dorothy"; then + if [[ $DOROTHY == "$XDG_DATA_HOME/dorothy" ]]; then option_xdg='yes' else option_xdg='no' @@ -832,12 +845,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 { @@ -855,7 +862,7 @@ function dorothy_() ( __print_lines "Installing Dorothy into $DOROTHY from $the_browse_url" # setup repo with branch - if test -n "$the_branch"; then + if [[ -n $the_branch ]]; then clone_args+=( '--branch' "$the_branch" @@ -894,12 +901,12 @@ function dorothy_() ( # check if [[ $action =~ ^(repl|run)$ ]]; then # in trial mode, only install dorothy if necessary, do not update - if test ! -d "$DOROTHY"; then + if [[ ! -d $DOROTHY ]]; then install_dorothy fi else # in install and update mode, do whatever is appropriate - if test -d "$DOROTHY"; then + if [[ -d $DOROTHY ]]; then update_dorothy else install_dorothy @@ -924,7 +931,7 @@ function dorothy_() ( hostname="$( ask --linger --question='What should the new hostname be?' )" - if test -n "$hostname"; then + if [[ -n $hostname ]]; then set-hostname "$hostname" else __print_lines "Proceeding with the existing hostname: $hostname" @@ -944,7 +951,7 @@ function dorothy_() ( # helpers function install_user { # sanity check - if test -d "$DOROTHY/user"; then + if [[ -d "$DOROTHY/user" ]]; then # should have called update return 29 # ESPIPE 29 Illegal seek fi @@ -955,7 +962,7 @@ function dorothy_() ( repo_description="My user configuration for the Dorothy dotfile ecosystem: $dorothy_homepage" # handle github and gitlab prefixes, local value, custom urls, and default github usernames - if test -n "$user"; then + if [[ -n $user ]]; then if [[ $user =~ ^(gh|github): ]]; then where='github' user="${user#*:}" @@ -971,7 +978,7 @@ function dorothy_() ( where='github' fi fi - if test -z "$where"; then + if [[ -z $where ]]; then if get-terminal-reactivity-support --quiet; then where="$( choose --linger --required --label \ @@ -982,7 +989,7 @@ function dorothy_() ( gitlab \ 'Use a GitLab repository for your user configuration.' \ custom \ - 'Manually specify a repository URL for your user configration.' \ + 'Manually specify a repository URL for your user configuration.' \ 'local' \ "Don't use a repository for your user configuration, just store it locally for now." )" @@ -990,16 +997,16 @@ function dorothy_() ( where='local' fi fi - if test "$where" = 'github' && __command_exists -- gh; then + if [[ $where == 'github' ]] && __command_exists -- gh; then cli='gh' - elif test "$where" = 'gitlab' && __command_exists -- glab; then + elif [[ $where == 'gitlab' ]] && __command_exists -- glab; then cli='glab' fi - if test "$where" = 'github' -o "$where" = 'gitlab'; then + if [[ $where =~ ^(github|gitlab)$ ]]; then # prepare username default, and clear generic usernames - if test -z "$user"; then + if [[ -z $user ]]; then user="$(get-profile username || :)" - if test -n "$user" && is-generic -- "$user"; then + if [[ -n $user ]] && is-generic -- "$user"; then user='' fi user="$( @@ -1019,7 +1026,7 @@ function dorothy_() ( # confirm it exists if fetch --ok "$repo_url"; then - if test -n "$cli"; then + if [[ -n $cli ]]; then if "$cli" repo view "$repo_name" | grep --quiet --ignore-case --fixed-strings --regexp="$dorothy_homepage" || :; then echo-style --green+bold="$repo_url" --green=' verified as a Dorothy user configuration repository.' elif confirm --bool --ppid=$$ -- "$repo_url exists, but does not yet seem to be a Dorothy user configuration repository, would you like to use it anyway (Y), or select an alternative (N)?"; then @@ -1031,11 +1038,11 @@ function dorothy_() ( fi else echo-style --yellow+bold="$repo_url" --yellow=' does not yet exist, let us create it...' - if test "$cli" = 'gh'; then + if [[ $cli == 'gh' ]]; then if ! gh repo create "$repo_name" --description "$repo_description" --homepage "$dorothy_homepage" --public --confirm --enable-wiki=false; then repo_url='' fi - elif test "$cli" = 'glab'; then + elif [[ $cli == 'glab' ]]; then # @todo untested if ! glab repo create "$repo_name" --description "$repo_description" --public; then repo_url='' @@ -1045,10 +1052,10 @@ function dorothy_() ( fi # if the repo doesn't exist, get the user to manually create it - if test -z "$repo_url" -a "$where" != 'local'; then - if test "$where" = 'github'; then + if [[ -z $repo_url && $where != 'local' ]]; then + if [[ $where == 'github' ]]; then open 'https://github.com/new' - elif test "$where" = 'gitlab'; then + elif [[ $where == 'gitlab' ]]; then open 'https://gitlab.com/projects/new#blank_project' fi question="$( @@ -1076,7 +1083,7 @@ function dorothy_() ( fi # if we have a repo url, then clone it - if test -n "$repo_url"; then + if [[ -n $repo_url ]]; then # check if it is strongbox if fetch --ok "$(git-helper protocol-format "$repo_url" https | sed -E 's#.git$#/raw/HEAD/.strongbox-keyid#')"; then echo-style --notice='Strongbox repository detected.' @@ -1102,7 +1109,7 @@ function dorothy_() ( EOF )" - if test ! -f "$HOME/.strongbox_keyring" && confirm --ppid=$$ -- "$question"; then + if [[ ! -f "$HOME/.strongbox_keyring" ]] && confirm --ppid=$$ -- "$question"; then edit --wait -- "$HOME/.strongbox_keyring" fi fi @@ -1120,7 +1127,7 @@ function dorothy_() ( fi # if we haven't initialised, then manually initialise - if test ! -d "$DOROTHY/user"; then + if [[ ! -d "$DOROTHY/user" ]]; then git_init "$DOROTHY/user" echo-style --yellow='Manually initialised git repository at ' --yellow+bold="$DOROTHY/user" fi @@ -1136,7 +1143,7 @@ function dorothy_() ( } # update or install the user configuration based on its presence - if test -d "$DOROTHY/user"; then + if [[ -d "$DOROTHY/user" ]]; then # it already exists, so update it update_user else @@ -1163,13 +1170,13 @@ function dorothy_() ( # ensure README.md mentions dorothy file="$DOROTHY/user/README.md" - if ! (test -f "$file" && grep --quiet --ignore-case --fixed-strings --regexp="$dorothy_homepage" "$file"); then + if ! ([[ -f $file ]] && grep --quiet --ignore-case --fixed-strings --regexp="$dorothy_homepage" "$file"); then # README.md does not mention dorothy, time to add it name="$(get-profile name || get-profile username || :)" url="$(get-profile url || :)" - if test -n "$name" -a -n "$url"; then + if [[ -n $name && -n $url ]]; then who="[${name}'s](${url})" - elif test -n "$name"; then + elif [[ -n $name ]]; then who="${name}'s" else who='my' @@ -1181,7 +1188,7 @@ function dorothy_() ( This is ${who} user configuration for the [Dorothy](${dorothy_homepage}) dotfile ecosystem. EOF )" - if test ! -f "$file"; then + if [[ ! -f $file ]]; then __print_lines "$header" >"$file" else content="$(cat "$file")" @@ -1218,7 +1225,7 @@ function dorothy_() ( xonsh Xonsh zsh Zsh ) - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -1233,7 +1240,7 @@ function dorothy_() ( echo-style --h2='Dorothy Shell Configuration' # if not uninstalling - if test "$option_uninstall" = 'no'; then + if [[ $option_uninstall == 'no' ]]; then # configure default selected shells by checking for their existence local cmd for cmd in "${supported_shells[@]}"; do @@ -1254,12 +1261,12 @@ function dorothy_() ( use_shells+=('bash v3 compat workaround') # cleanup - if test "$option_clean" = 'yes'; then + if [[ $option_clean == 'yes' ]]; then rm_deprecated "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bashrc" "$XDG_CONFIG_HOME/fish/config.fish" "$ZDOTDIR/.zshrc" "$ZDOTDIR/.zprofile" fi # bash - if is-needle bash -- "${use_shells[@]}"; then + if is-needle --needle='bash' -- "${use_shells[@]}"; then echo-style --h3='Configuring Bash' setup-util-bash @@ -1280,7 +1287,7 @@ function dorothy_() ( fi # dash - if is-needle dash -- "${use_shells[@]}"; then + if is-needle --needle='dash' -- "${use_shells[@]}"; then echo-style --h3='Configuring Dash' setup-util-dash @@ -1289,13 +1296,10 @@ function dorothy_() ( --find='.+? # Dorothy' --replace=". \"$DOROTHY/init.sh\" # Dorothy" echo-style --g3='Configuring Dash' - elif ! is-needle bash -- "${use_shells[@]}" && ! is-needle ksh -- "${use_shells[@]}"; then - config-helper --file="$HOME/.profile" -- \ - --find='.+? # Dorothy' --replace='' fi # elvish - if is-needle elvish -- "${use_shells[@]}"; then + if is-needle --needle='elvish' -- "${use_shells[@]}"; then # https://elv.sh/ref/command.html#rc-file # https://elv.sh/ref/runtime.html # https://github.com/elves/elvish/issues/1726 @@ -1315,7 +1319,7 @@ function dorothy_() ( fi # fish - if is-needle fish -- "${use_shells[@]}"; then + if is-needle --needle='fish' -- "${use_shells[@]}"; then echo-style --h3='Configuring Fish' setup-util-fish __mkdirp "$XDG_CONFIG_HOME/fish" @@ -1332,7 +1336,7 @@ function dorothy_() ( fi # ksh - if is-needle ksh -- "${use_shells[@]}"; then + if is-needle --needle='ksh' -- "${use_shells[@]}"; then echo-style --h3='Configuring KSH' setup-util-ksh @@ -1341,13 +1345,10 @@ function dorothy_() ( --find='.+? # Dorothy' --replace=". '$DOROTHY/init.sh' # Dorothy" echo-style --g3='Configuring KSH' - elif ! is-needle bash -- "${use_shells[@]}" && ! is-needle dash -- "${use_shells[@]}"; then - config-helper --file="$HOME/.profile" -- \ - --find='.+? # Dorothy' --replace='' fi # nu - if is-needle nu -- "${use_shells[@]}"; then + if is-needle --needle='nu' -- "${use_shells[@]}"; then echo-style --h3='Configuring Nu' setup-util-nu @@ -1362,7 +1363,7 @@ function dorothy_() ( fi # xonsh - if is-needle xonsh -- "${use_shells[@]}"; then + if is-needle --needle='xonsh' -- "${use_shells[@]}"; then # https://xon.sh/xonshrc.html echo-style --h3='Configuring Xonsh' setup-util-xonsh @@ -1381,7 +1382,7 @@ function dorothy_() ( # zsh # https://zsh.sourceforge.io/Intro/intro_3.html - if is-needle zsh -- "${use_shells[@]}"; then + if is-needle --needle='zsh' -- "${use_shells[@]}"; then echo-style --h3='Configuring Zsh' setup-util-zsh __mkdirp "$ZDOTDIR" @@ -1407,6 +1408,12 @@ function dorothy_() ( --find='ZDOTDIR=.+' --replace="ZDOTDIR='$ZDOTDIR'" fi + # no bash, dash, ksh + if ! is-needle --any --needle='bash' --needle='dash' --needle='ksh' -- "${use_shells[@]}"; then + config-helper --file="$HOME/.profile" -- \ + --find='.+? # Dorothy' --replace='' + fi + echo-style --g2='Dorothy Shell Configuration' } @@ -1416,7 +1423,7 @@ function dorothy_() ( function act_install { # process arguments local item option_clean='no' option_skip_install='no' option_user='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -1430,14 +1437,14 @@ function dorothy_() ( done # pre-requisites - if test "$option_skip_install" = 'no'; then + if [[ $option_skip_install == 'no' ]]; then ensure_dorothy_configured fi assert_dorothy_configured # action echo-style --h1='Install Dorothy' - if test "$option_skip_install" = 'no'; then + if [[ $option_skip_install == 'no' ]]; then ensure_minimal_dependencies ensure_machine_configured ensure_user_configured "$option_user" @@ -1463,13 +1470,13 @@ function dorothy_() ( echo-style --g1='Uninstall Dorothy' echo-style --success='Dorothy is now uninstalled.' ' ' --notice='Restart your terminal.' local paths=() - if test -d "$DOROTHY"; then + if [[ -d $DOROTHY ]]; then paths+=("$DOROTHY") fi - if test -d "$DOROTHY/user"; then + if [[ -d "$DOROTHY/user" ]]; then paths+=("$(__fs_realpath -- "$DOROTHY/user")") fi - if test "${#paths[@]}" -ne 0; then + if [[ ${#paths[@]} -ne 0 ]]; then __print_lines 'Once you have restarted your terminal, you can delete Dorothy by removing the following paths:' __print_lines "${paths[@]}" fi @@ -1495,7 +1502,7 @@ function dorothy_() ( function act_repl { # process arguments local item shell='' theme='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -1514,7 +1521,7 @@ function dorothy_() ( ensure_minimal_dependencies # ask - if test -z "$shell"; then + if [[ -z $shell ]]; then shell="$( choose --linger --required \ --question='Which shell to use?' \ @@ -1523,7 +1530,7 @@ function dorothy_() ( fi # act, installing the shell if it is missing - if test "$shell" = 'bash'; then + if [[ $shell == 'bash' ]]; then setup-util-bash --quiet # [bash --rcfile] works but not within a [--rcfile] # [--login] ignores [--rcfile] @@ -1535,29 +1542,29 @@ function dorothy_() ( DOROTHY_FORCE_LOAD=yes DOROTHY_THEME_OVERRIDE='$theme' . '$DOROTHY/init.sh' - echo-style 'Your are now using the ' --invert='$ACTIVE_POSIX_SHELL' ' shell with Dorothy loaded from ' --code='$DOROTHY' \$'\n' 'Use the ' --code='exit' ' command to return to your parent shell.' \$'\n' 'Use ' --code='ls $DOROTHY/commands' ' to see available commands.' + echo-style 'Your are now using the ' --invert='$ACTIVE_POSIX_SHELL' ' shell with Dorothy loaded from ' --code='$DOROTHY' \$'\n' 'Use the ' --code='exit' ' command to return to your parent shell.' \$'\n' 'Use ' --code='dorothy commands' ' to see available commands.' EOF ) return - elif test "$shell" = 'nu'; then + elif [[ $shell == 'nu' ]]; then setup-util-nu --quiet "$shell" --interactive --login --no-config-file --execute "$( cat <<-EOF #!/usr/bin/env nu \$env.DOROTHY_THEME_OVERRIDE = '$theme' source '$DOROTHY/init.nu' - echo-style 'Your are now using the ' --invert='nu' ' shell with Dorothy loaded from ' --code='$DOROTHY' "\n" 'Use the ' --code='exit' ' command to return to your parent shell.' "\n" 'Use ' --code='ls $DOROTHY/commands' ' to see available commands.' + echo-style 'Your are now using the ' --invert='nu' ' shell with Dorothy loaded from ' --code='$DOROTHY' "\n" 'Use the ' --code='exit' ' command to return to your parent shell.' "\n" 'Use ' --code='dorothy commands' ' to see available commands.' EOF )" return - elif test "$shell" = 'fish'; then + elif [[ $shell == 'fish' ]]; then setup-util-fish --quiet "$shell" --interactive --login --no-config --init-command="$( cat <<-EOF #!/usr/bin/env fish set DOROTHY_THEME_OVERRIDE '$theme' source '$DOROTHY/init.fish' - echo-style 'Your are now using the ' --invert='fish' ' shell with Dorothy loaded from ' --code='$DOROTHY' \n 'Use the ' --code='exit' ' command to return to your parent shell.' \n 'Use ' --code='ls $DOROTHY/commands' ' to see available commands.' + echo-style 'Your are now using the ' --invert='fish' ' shell with Dorothy loaded from ' --code='$DOROTHY' \n 'Use the ' --code='exit' ' command to return to your parent shell.' \n 'Use ' --code='dorothy commands' ' to see available commands.' EOF )" return @@ -1570,7 +1577,7 @@ function dorothy_() ( function act_run { # process arguments local item args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -1599,6 +1606,20 @@ function dorothy_() ( # ===================================== # Actions: ... + function act_commands { + # pre-requisites + assert_dorothy_configured + + local paths=() + mapfile -t paths < <(echo-if-present -- "$DOROTHY/commands" "$DOROTHY/commands.beta" "$DOROTHY/user/commands" "$DOROTHY/user/commands.local") + setup-util-eza --quiet --optional + if __command_exists eza; then + eza "${paths[@]}" + else + ls "${paths[@]}" + fi + } + function act_permissions { # pre-requisites assert_dorothy_configured @@ -1618,7 +1639,7 @@ function dorothy_() ( echo-style --h1='Edit Dorothy' # ensure_minimal_dependencies <-- if they are editing, then we assume they are already setup ensure_permissions_configured - if test "$(edit --dry --only-editor)" == 'code'; then + if [[ "$(edit --dry --only-editor)" == 'code' ]]; then edit -- "$DOROTHY/.vscode/workspace.code-workspace" else edit -- "$DOROTHY" @@ -1670,16 +1691,16 @@ function dorothy_() ( source "$DOROTHY/sources/config.sh" dorothy-config 'interactive.sh' -- \ --find='export DOROTHY_THEME=(.*)' --replace="export DOROTHY_THEME=$(echo-escape-bash -- "$theme")" - if test -f "$DOROTHY/user/config/interactive.nu"; then + if [[ -f "$DOROTHY/user/config/interactive.nu" ]]; then # dorothy-config 'interactive.nu' -- --string-find="\$env.DOROTHY_THEME" --string-replace="\$env.DOROTHY_THEME = $(echo-quote -- "$theme")" # ^ don't use that, as theming will always be in the config not config.local # ^ at some point, [dorothy-config] should use [config-edit] config-edit --file="$DOROTHY/user/config/interactive.nu" --needle="\$env.DOROTHY_THEME" --line="\$env.DOROTHY_THEME = $(echo-quote -- "$theme")" --add fi - if test -f "$DOROTHY/user/config/interactive.xsh"; then + if [[ -f "$DOROTHY/user/config/interactive.xsh" ]]; then config-edit --file="$DOROTHY/user/config/interactive.xsh" --needle="\$DOROTHY_THEME" --line="\$DOROTHY_THEME = $(echo-quote -- "$theme")" --add fi - if test -f "$DOROTHY/user/config/interactive.elv"; then + if [[ -f "$DOROTHY/user/config/interactive.elv" ]]; then config-edit --file="$DOROTHY/user/config/interactive.elv" --needle="DOROTHY_THEME" --line="set-env DOROTHY_THEME $(echo-quote -- "$theme")" --add fi @@ -1724,7 +1745,7 @@ function dorothy_() ( # adjust for ci local args=() - if test -n "${CI-}"; then + if [[ -n ${CI-} ]]; then args+=('--no-progress') # don't use [--ci] as it doesn't apply fixes, and our CI suite expects fixes fi @@ -1738,7 +1759,7 @@ function dorothy_() ( echo-style --g3="$DOROTHY" # on user configuration - if test -d "$DOROTHY/user/.trunk"; then + if [[ -d "$DOROTHY/user/.trunk" ]]; then echo-style --h3="$DOROTHY/user" ( cd "$DOROTHY/user" @@ -1761,7 +1782,7 @@ function dorothy_() ( # adjust for ci local args=() - if test -n "${CI-}"; then + if [[ -n ${CI-} ]]; then args+=('--ci' '--no-progress') fi @@ -1775,7 +1796,7 @@ function dorothy_() ( echo-style --g3="$DOROTHY" # on user configuration - if test -d "$DOROTHY/user/.trunk"; then + if [[ -d "$DOROTHY/user/.trunk" ]]; then echo-style --h3="$DOROTHY/user" ( cd "$DOROTHY/user" @@ -1806,21 +1827,21 @@ function dorothy_() ( # adjust for ci local args=() - if test -n "${CI-}"; then + if [[ -n ${CI-} ]]; then args+=('--ci' '--no-progress') fi - # prepre scan paths + # prepare scan paths local commands filepath scan_paths=( "$DOROTHY/commands/" "$DOROTHY/commands.beta/" "$DOROTHY/commands.deprecated/" ) - if test -d "$DOROTHY/user"; then - if test -d "$DOROTHY/user/commands"; then + if [[ -d "$DOROTHY/user" ]]; then + if [[ -d "$DOROTHY/user/commands" ]]; then scan_paths+=("$DOROTHY/user/commands/") fi - if test -d "$DOROTHY/user/commands.local"; then + if [[ -d "$DOROTHY/user/commands.local" ]]; then scan_paths+=("$DOROTHY/user/commands.local/") fi fi @@ -1828,19 +1849,19 @@ function dorothy_() ( # check for unsafe function calls, by checking for __ convention # ((if|[&][&]|[|][|]|[!])\s)[a-z]+_[a-z_]+ # \s[a-z]+_[a-z_]+( ([&][&]|[|][|])) - # @tood improve upon this by checking if the function also calls function, as any calling of a function must also be safe + # @todo improve upon this by checking if the function also calls function, as any calling of a function must also be safe local unsafe_regexp='((if|[&][&]|[|][|]|[!])\s)[a-z]+_[a-z_]+|\s[a-z]+_[a-z_]+( ([&][&]|[|][|]))' unsafe_contents='' unsafe_bundle='' unsafe_count=0 - mapfile -t commands < <(rg --files-with-matches --regexp="$unsafe_regexp" "${scan_paths[@]}" | sort) - if test "${#commands[@]}" -ne 0; then + mapfile -t commands < <(rg --files-with-matches --sortr modified --regexp="$unsafe_regexp" "${scan_paths[@]}") + if [[ ${#commands[@]} -ne 0 ]]; then for filepath in "${commands[@]}"; do unsafe_contents="$(rg --regexp="$unsafe_regexp" "$filepath" | grep --invert-match --fixed-strings --regexp='for ' || :)" - if test -z "$unsafe_contents"; then + if [[ -z $unsafe_contents ]]; then continue fi unsafe_count="$((unsafe_count + 1))" unsafe_bundle+="$(echo-style --element="$filepath")"$'\n'"$unsafe_contents"$'\n'"$(echo-style --/element="$filepath")"$'\n' done - if test "$unsafe_count" -ne 0; then + if [[ $unsafe_count -ne 0 ]]; then echo-style --error='The following commands have unsafe function calls:' $'\n' "$unsafe_bundle" --e1='Dorothy Lint' return 1 fi @@ -1851,7 +1872,7 @@ function dorothy_() ( ( cd "$DOROTHY" # upgrade - if test "$upgrade_linting" = 'yes'; then + if [[ $upgrade_linting == 'yes' ]]; then if __command_exists -- npx npm; then npx npm-check-updates -u npm install @@ -1863,7 +1884,7 @@ function dorothy_() ( trunk check --all "${args[@]}" # update meta, only do this for balupton, as we can't guarantee up to date Node.js, as this requires Node.js 20 # https://github.com/bevry/dorothy/actions/runs/7636822585/job/20804540714#step:13:14 - if __command_exists -- npm && test "$USER" = 'balupton'; then + if __command_exists -- npm && [[ $USER == 'balupton' ]]; then # if balupton, update meta with auth # github-auth -- npm run our:meta # @todo currently broken # update the readme formatting to be consistent again @@ -1873,12 +1894,12 @@ function dorothy_() ( echo-style --g3="$DOROTHY" # on user configuration - if test -d "$DOROTHY/user/.trunk"; then + if [[ -d "$DOROTHY/user/.trunk" ]]; then echo-style --h3="$DOROTHY/user" ( cd "$DOROTHY/user" # upgrade - if test "$upgrade_linting" = 'yes'; then + if [[ $upgrade_linting == 'yes' ]]; then if __command_exists -- npx npm; then npx npm-check-updates -u npm install @@ -1905,6 +1926,11 @@ function dorothy_() ( echo-style --h1='Dorothy Tests' source "$DOROTHY/sources/ripgrep.bash" + # on CI set the modification times to reflect those from the git repository, rather than from clone + if is-ci; then + git-helper --path="$DOROTHY" umt + fi + # run relevant debugs # __print_lines '' 'debug-terminal-stdin:' # debug-terminal-stdin || : @@ -1915,7 +1941,7 @@ function dorothy_() ( # able to test on bash v3? local bash has_macos_bash_v3='no' bash="$(type -P bash)" - if __is_mac && test "$bash" != '/bin/bash' && [[ "$(/bin/bash --version || :)" == 'GNU bash, version 3.'* ]]; then + if __is_mac && [[ $bash != '/bin/bash' && "$(/bin/bash --version || :)" == 'GNU bash, version 3.'* ]]; then has_macos_bash_v3=yes fi @@ -1925,17 +1951,17 @@ function dorothy_() ( "$DOROTHY/commands.beta/" # "$DOROTHY/commands.deprecated/" <-- don't run deprecated tests, as that will cause deprecated warnings, which cases dorothy-warnings test to fail ) - if test -d "$DOROTHY/user"; then - if test -d "$DOROTHY/user/commands"; then + if [[ -d "$DOROTHY/user" ]]; then + if [[ -d "$DOROTHY/user/commands" ]]; then scan_paths+=("$DOROTHY/user/commands/") fi - if test -d "$DOROTHY/user/commands.local"; then + if [[ -d "$DOROTHY/user/commands.local" ]]; then scan_paths+=("$DOROTHY/user/commands.local/") fi fi # run tests on commands that have them - mapfile -t commands < <(rg --files-with-matches --fixed-strings --regexp='--test' "${scan_paths[@]}" | sort) + mapfile -t commands < <(rg --files-with-matches --sortr modified --fixed-strings --regexp='--test' "${scan_paths[@]}") for filepath in "${commands[@]}"; do filename="$(basename "$filepath")" @@ -1945,7 +1971,7 @@ function dorothy_() ( fi # check if we want to run only specific tests - if test "${#only[@]}" -ne 0 && ! is-needle --needle="$filename" -- "${only[@]}"; then + if [[ ${#only[@]} -ne 0 ]] && ! is-needle --needle="$filename" -- "${only[@]}"; then continue fi @@ -1968,7 +1994,7 @@ function dorothy_() ( } # check if we can test on bash v3 - if test "$has_macos_bash_v3" = 'yes'; then + if [[ $has_macos_bash_v3 == 'yes' ]]; then # we can test, but do we want to test this one on bash v3? if is-needle --needle="$filename" -- "${no_bash_v3[@]}"; then continue @@ -2000,7 +2026,7 @@ function dorothy_() ( fi # check for failures - if test "${#failures[@]}" -ne 0; then + if [[ ${#failures[@]} -ne 0 ]]; then echo-style --error='The following tests were failures:' __print_lines "${failures[@]}" echo-style --e1='Dorothy Tests' @@ -2030,7 +2056,7 @@ function dorothy_() ( file_status=0 # skip tests on these - if test "$filename" = 'dorothy'; then + if [[ $filename == 'dorothy' ]]; then continue fi @@ -2042,7 +2068,7 @@ function dorothy_() ( ) fi - # does the file yuse a subshell + # does the file use a subshell if ! rg --quiet --regexp="${filename//-/_}[_]?[(][)] [(]" "$filepath"; then file_bad+=( ' ' @@ -2060,7 +2086,7 @@ function dorothy_() ( # is the file used by other commands? matches="$(rg --files-with-matches --fixed-strings --regexp="$filename" "${scan_paths[@]}" || :)" - if test "$matches" = "$filepath" -o -z "$matches"; then + if [[ $matches == "$filepath" || -z $matches ]]; then file_bad+=( ' ' --magenta='is not used anywhere besides itself.' @@ -2076,7 +2102,7 @@ function dorothy_() ( fi # log - if test "${#file_bad[@]}" -ne 0; then + if [[ ${#file_bad[@]} -ne 0 ]]; then file_status=1 fi echo-style --element/="$filename" --status="$file_status" "${file_good[@]}" "${file_bad[@]}" @@ -2097,8 +2123,8 @@ function dorothy_() ( # such that the following [choose] will be found prepare_dorothy >/dev/stderr # don't conflate stdout, don't use /dev/tty as tty doesn't exist on CI - if test "$(type -t "act_$action")" = 'function'; then - if test "${#option_args[@]}" -eq 0; then # bash v3 compat + if [[ "$(type -t "act_$action")" == 'function' ]]; then + if [[ ${#option_args[@]} -eq 0 ]]; then # bash v3 compat "act_$action" return else @@ -2112,6 +2138,6 @@ function dorothy_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then dorothy_ "$@" fi diff --git a/commands/dorothy-config b/commands/dorothy-config index b4aee4a67..7513ebf6b 100755 --- a/commands/dorothy-config +++ b/commands/dorothy-config @@ -71,7 +71,7 @@ function dorothy_config() ( --reason= The reason for the change, displays in prompts and whatnot. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -79,7 +79,7 @@ function dorothy_config() ( # process local item option_packages_var='' option_filename='' option_reason='' option_prefer='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -95,7 +95,7 @@ function dorothy_config() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_filename"; then + if [[ -z $option_filename ]]; then option_filename="$item" else help "An unrecognised argument was provided: $item" @@ -105,8 +105,8 @@ function dorothy_config() ( done # ensure filename - if test -z "$option_filename"; then - if test -n "$option_packages_var"; then + if [[ -z $option_filename ]]; then + if [[ -n $option_packages_var ]]; then option_filename='setup.bash' else help 'A filename must be provided.' @@ -128,18 +128,18 @@ function dorothy_config() ( # SETUP_UTILS should have already been loaded, but let's create and load it if it hasn't # we need to do it this way, otherwise we would wipe pre-existing custom configuration - if test -z "${SETUP_UTILS-}"; then + if [[ -z ${SETUP_UTILS-} ]]; then SETUP_UTILS=() load_dorothy_config 'setup.bash' fi - if test -z "$option_filename"; then + if [[ -z $option_filename ]]; then option_filename='setup.bash' fi # remove inputs with dedicated installers for item in "${option_args[@]}"; do installer="$(get-installer --quiet -- "$item" || :)" - if test -n "$installer"; then + if [[ -n $installer ]]; then if [[ $installer == 'setup-util-'* ]]; then util="${installer#*setup-util-}" reason+="$( @@ -160,7 +160,7 @@ function dorothy_config() ( done # update configuration if necessary - if test "$reconfigure" = 'yes'; then + if [[ $reconfigure == 'yes' ]]; then dorothy-config "$option_filename" --prefer="$option_prefer" --reason="$reason" -- \ --field="$option_packages_var" --array="$(__print_lines "${revised_items[@]}" | sort --ignore-case | uniq)" \ --field='SETUP_UTILS' --array="$(__print_lines "${SETUP_UTILS[@]}" | sort --ignore-case | uniq)" @@ -175,35 +175,35 @@ function dorothy_config() ( local user_filepath='' temp_filepath='' source_filepath='' default_filepath="$DOROTHY/config/$option_filename" local_filepath="$DOROTHY/user/config.local/$option_filename" public_filepath="$DOROTHY/user/config/$option_filename" displayed_reason='no' # reset default filepath if it doesn't exist - if test ! -f "$default_filepath"; then + if [[ ! -f $default_filepath ]]; then default_filepath='' fi # which location is preferred (the default option) - if test "$option_prefer" = 'local'; then + if [[ $option_prefer == 'local' ]]; then user_filepath="$local_filepath" - elif test "$option_prefer" = 'public'; then + elif [[ $option_prefer == 'public' ]]; then user_filepath="$public_filepath" fi - # enforece location to verified preference - if test -f "$local_filepath" -a -f "$public_filepath"; then + # enforce location to verified preference + if [[ -f $local_filepath && -f $public_filepath ]]; then user_filepath="$( choose --linger --required \ --question="$(echo-style --notice1='The configuration file ' --code-notice1="$option_filename" --notice1=' is pending updates. Select the specific configuration file to update.')"$'\n'"$option_reason" \ --default="$user_filepath" -- "$public_filepath" "$local_filepath" )" displayed_reason='yes' - elif test -f "$local_filepath"; then + elif [[ -f $local_filepath ]]; then user_filepath="$local_filepath" - elif test -f "$public_filepath"; then + elif [[ -f $public_filepath ]]; then user_filepath="$public_filepath" else user_filepath="$public_filepath" fi # show the reason - if test -n "$option_reason" -a "$displayed_reason" = 'no'; then + if [[ -n $option_reason && $displayed_reason == 'no' ]]; then echo-style --notice1='The configuration file ' --code-notice1="$user_filepath" --notice1=' will be updated to:' $'\n' "$option_reason" >/dev/stderr fi @@ -211,19 +211,19 @@ function dorothy_config() ( __mkdirp "$(dirname "$user_filepath")" # check if we have to make the filepath - if test ! -f "$user_filepath"; then + if [[ ! -f $user_filepath ]]; then temp_filepath="$( fs-temp \ --directory='config-helper' \ --file --touch )" - if test -n "$default_filepath"; then + if [[ -n $default_filepath ]]; then # start with the header of the default configuration file echo-lines-before --needle='' --stdin <"$default_filepath" >"$temp_filepath" echo >>"$temp_filepath" # inject the sourcing of the default configuration file - if test "$extension" = 'nu'; then + if [[ $extension == 'nu' ]]; then # nu doesn't support dynamic sourcing source_filepath="${default_filepath/"$DOROTHY"/"~/.local/share/dorothy"}" cat <<-EOF >>"$temp_filepath" @@ -231,7 +231,7 @@ function dorothy_config() ( source '$source_filepath' EOF - elif test "$extension" = 'sh'; then + elif [[ $extension == 'sh' ]]; then # sh uses [.] instead of [source] source_filepath="${default_filepath/"$DOROTHY"/'$DOROTHY'}" cat <<-EOF >>"$temp_filepath" @@ -264,14 +264,14 @@ function dorothy_config() ( fi # now that the file definitely exists, update it if we have values to update it - if test "${#option_args[@]}" -ne 0; then + if [[ ${#option_args[@]} -ne 0 ]]; then config-helper --file="$user_filepath" --multiple=warn-skip \ -- "${option_args[@]}" fi } # perform the correct action - if test -z "$option_packages_var"; then + if [[ -z $option_packages_var ]]; then update_configuration else prune_utilities_from_packages @@ -279,8 +279,8 @@ function dorothy_config() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then dorothy_config_test else dorothy_config "$@" diff --git a/commands/dorothy-warnings b/commands/dorothy-warnings index 3b8f5d512..34f668ead 100755 --- a/commands/dorothy-warnings +++ b/commands/dorothy-warnings @@ -24,12 +24,12 @@ function dorothy_warnings() ( Returns [0] always. --warn | warn - Output the warnings file with appropropriate messaging, only if there are new warnings from last time. + Output the warnings file with appropriate messaging, only if there are new warnings from last time. Returns [0] always. This is the default action. --list | list - Output the warnings file with appropropriate messaging. + Output the warnings file with appropriate messaging. Returns [0] always. This is the default action. @@ -42,7 +42,7 @@ function dorothy_warnings() ( # process local item action='warn' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -101,6 +101,6 @@ function dorothy_warnings() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then dorothy_warnings "$@" fi diff --git a/commands/down b/commands/down index c174c7802..521ddf6d4 100755 --- a/commands/down +++ b/commands/down @@ -1,5 +1,10 @@ #!/usr/bin/env bash +# @todo support these: +# https://github.com/rs/curlie +# https://github.com/ducaale/xh +# https://github.com/mihaigalos/aim + function down_() ( source "$DOROTHY/sources/bash.bash" source "$(type -P eval-helper)" @@ -26,7 +31,7 @@ function down_() ( tool='' # if no tools are available, install preference - if test "${#available_tools[@]}" -eq 0; then + if [[ ${#available_tools[@]} -eq 0 ]]; then get-installer --first-success --invoke --quiet -- "${all_tools[@]}" down "$@" return @@ -66,7 +71,7 @@ function down_() ( --directory= Place downloaded file(s) inside . - If ommitted, the current working directory will be used. + If omitted, the current working directory will be used. --file= If only a single file was downloaded, rename it to . @@ -89,15 +94,15 @@ function down_() ( ${available_tools[*]} EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_quiet='' url='' tool='' archive_format='' archive_glob='' directory='' file='' filepath='' retry='2' option_bearer_token='' - while test "$#" -ne 0; do + local item option_quiet='' url='' tool='' archive_format='' archive_glob='' directory='' file='' filepath='' retry='2' option_bearer_token='' option_progress='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -117,9 +122,12 @@ function down_() ( '--filepath='*) filepath="${item#*=}" ;; '--retry='*) retry="${item#*=}" ;; '--url='*) url="${item#*=}" ;; + '--no-progress'* | '--progress'*) + option_progress="$(get-flag-value --affirmative --fallback="$option_progress" -- "$item")" + ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$url"; then + if [[ -z $url ]]; then url="$item" else help "An unrecognised argument was provided: $item" @@ -128,13 +136,18 @@ function down_() ( esac done + # disable progress bars if CI + if [[ -z $option_progress ]] && is-ci; then + option_progress='no' + fi + # assert url - if test -z "$url"; then + if [[ -z $url ]]; then help "No URL was provided." fi # ensure tool - if test -z "$tool"; then + if [[ -z $tool ]]; then tool="${available_tools[0]}" elif ! is-needle --needle="$tool" -- "${available_tools[@]}"; then help "The specified tool is not available: $tool" @@ -147,22 +160,22 @@ function down_() ( fi # ensure filepath, directory, file - if test -n "$filepath"; then + if [[ -n $filepath ]]; then # filepath is a directory + file combination filepath="$(fs-absolute -- "$filepath")" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -n "$directory" -a -n "$file"; then + elif [[ -n $directory && -n $file ]]; then # directory + file filepath="$(fs-absolute -- "$directory/$file")" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -z "$directory" -a -n "$file"; then + elif [[ -z $directory && -n $file ]]; then # file, without directory filepath="$(pwd)/$file" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -n "$directory" -a -z "$file"; then + elif [[ -n $directory && -z $file ]]; then # directory, without file directory="$(fs-absolute -- "$directory")" filepath='' # it is for dir+file combos only @@ -178,16 +191,16 @@ function down_() ( function act { # if zip, then download to a temporary/random directory first, filename must be valid but keep extension for unziptar (so trim special) local download_directory download_file - if test -n "$archive_format" -o -n "$archive_glob"; then + 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 { @@ -201,12 +214,12 @@ function down_() ( --quiet # its multiline bar doesn't reset itself, just dumps more bars to stdout --no-conf ) - if test -n "$option_bearer_token"; then + if [[ -n $option_bearer_token ]]; then aria2c_options+=( "--header=Authorization: Bearer $option_bearer_token" ) fi - if test -n "$download_file"; then + if [[ -n $download_file ]]; then aria2c_options+=( --out="$download_file" ) @@ -219,13 +232,13 @@ function down_() ( local curl_options=( '-L' ) - if test -n "$option_bearer_token"; then + if [[ -n $option_bearer_token ]]; then curl_options+=( '--header' "Authorization: Bearer $option_bearer_token" ) fi - if test -n "$download_file"; then + if [[ -n $download_file ]]; then curl_options+=(-o "$download_file") else curl_options+=(-O) @@ -239,13 +252,13 @@ function down_() ( function do_got { # https://github.com/melbahja/got#command-line-tool-usage local got_options=() - if test -n "$option_bearer_token"; then + if [[ -n $option_bearer_token ]]; then got_options+=( '--header' "Authorization: Bearer $option_bearer_token" ) fi - if test -n "$download_file"; then + if [[ -n $download_file ]]; then got_options+=(-o "$download_file") fi got_options+=("$url") @@ -259,13 +272,13 @@ function down_() ( local http_options=( '--download' ) - if test -n "$option_bearer_token"; then + if [[ -n $option_bearer_token ]]; then http_options+=( '--auth' "Bearer $option_bearer_token" ) fi - if test -n "$download_file"; then + if [[ -n $download_file ]]; then http_options+=( '--continue' # --continue only works with --output '--output' "$download_file" @@ -288,18 +301,20 @@ function down_() ( # --show-progress display the progress bar in any verbosity mode # WARNING: timestamping does nothing in combination with -O. See the manual for details. local wget_options=() - if ! is-apk; then - # wget on apk doesn't have the --prgoress option: - # https://github.com/bevry/dorothy/actions/runs/7498226466/job/20414036419#step:4:64 + # wget on apk doesn't have the --progress option: + # https://github.com/bevry/dorothy/actions/runs/7498226466/job/20414036419#step:4:64 + if [[ $option_progress == 'no' ]] || is-apk; then + wget_options+=('--no-verbose') # wget doesn't have a --progress=off or equivalent, instead this is it + else # bar is nicer than dot, and noscroll prevents issues with our clearing wget_options+=('--progress=bar:noscroll') fi - if test -n "$option_bearer_token"; then + if [[ -n $option_bearer_token ]]; then wget_options+=( "--header=Authorization: Bearer $option_bearer_token" ) fi - if test -n "$download_file"; then + if [[ -n $download_file ]]; then wget_options+=( "--output-document=$download_file" ) @@ -315,7 +330,7 @@ function down_() ( ) } function do_download { - if test "$(type -t "do_$tool")" = 'function'; then + if [[ "$(type -t "do_$tool")" == 'function' ]]; then "do_$tool" else help "Unrecognised tool: $tool" @@ -324,16 +339,16 @@ function down_() ( # invoke the download with retry support, capturing exit codes local download_status - while test "$retry" -ge 0; do + while [[ $retry -ge 0 ]]; do eval_capture --statusvar=download_status -- do_download - if test "$download_status" -eq 0; then + if [[ $download_status -eq 0 ]]; then break fi retry=$((retry - 1)) done # double confirm it was created, in case exit code passed but it still was not created - if test -n "$download_file"; then + if [[ -n $download_file ]]; then if is-missing -- "$download_directory/$download_file"; then echo-error "Failed to download " --code="$url" ' to ' --code="$download_directory/$download_file" return 1 @@ -341,10 +356,10 @@ function down_() ( fi # log - echo-style --success='Downloaded!' + echo-style --success="Downloaded with $tool successfully!" # if desired, perform extraction of the temporary file - if test -n "$archive_format" -o -n "$archive_glob"; then + if [[ -n $archive_format || -n $archive_glob ]]; then echo-style --notice='Unzipping...' unziptar "$download_directory/$download_file" \ --prune \ @@ -355,7 +370,7 @@ function down_() ( --glob="$archive_glob" # check extraction - if test -n "$file"; then + if [[ -n $file ]]; then # assert it was created if is-missing -- "$filepath"; then echo-error 'Failed to extract ' --code="$url" ' to ' --code="$filepath" @@ -372,7 +387,7 @@ function down_() ( local pending='Downloading' local success='Downloaded' local failure='Failed to download' - if test -n "$archive_format" -o -n "$archive_glob"; then + if [[ -n $archive_format || -n $archive_glob ]]; then pending='Downloading and extracting' success='Downloaded and extracted' failure='Failed to download and extract' @@ -396,6 +411,6 @@ function down_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then down_ "$@" fi diff --git a/commands/echo-checksum b/commands/echo-checksum index 30539ab18..baedec386 100755 --- a/commands/echo-checksum +++ b/commands/echo-checksum @@ -154,7 +154,7 @@ function echo_checksum() ( e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317 # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -162,13 +162,13 @@ function echo_checksum() ( # process our own arguments, delegate everything else to stdinargs local item option_algorithm='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--algorithm='*) option_algorithm="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -179,7 +179,7 @@ function echo_checksum() ( done # ensure algorithm - if test -z "$option_algorithm"; then + if [[ -z $option_algorithm ]]; then # ensure default option_algorithm="${algorithms[0]}" else @@ -192,7 +192,7 @@ function echo_checksum() ( fi # confirm algorithm - if test "$(type -t "do_$option_algorithm")" != 'function'; then + if [[ "$(type -t "do_$option_algorithm")" != 'function' ]]; then help "Unrecognised tool: $tool" fi @@ -214,6 +214,6 @@ function echo_checksum() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_checksum "$@" fi diff --git a/commands/echo-clear-lines b/commands/echo-clear-lines index 553ee0527..628070841 100755 --- a/commands/echo-clear-lines +++ b/commands/echo-clear-lines @@ -37,7 +37,7 @@ function echo_clear_lines() ( printf 'a\nb\nc' echo-clear-lines --stdin < <(printf 'a\nb\nc') # ^ retains: sup - # ^ Use this instead of the arguments otpion, and do not use <<< as it prints a trailing newline, which would erase 'sup'. + # ^ Use this instead of the arguments option, and do not use <<< as it prints a trailing newline, which would erase 'sup'. echo 'sup' printf 'a\nb\nc' @@ -50,7 +50,7 @@ function echo_clear_lines() ( echo-clear-lines < "\$file" # ^ retains: sup EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -58,7 +58,7 @@ function echo_clear_lines() ( # process our own arguments, delegate everything else to stdinargs local item option_here_string='no' option_columns='' option_count='' option_count_only='no' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -72,7 +72,7 @@ function echo_clear_lines() ( option_here_string='yes' option_args+=('--stdin') ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -92,15 +92,15 @@ function echo_clear_lines() ( local count=0 had_inline='no' function clear_lines { # adapt - if test "$option_here_string" = 'yes' -o "$had_inline" = 'yes'; then - if test "$count" -ne 0; then + if [[ $option_here_string == 'yes' || $had_inline == 'yes' ]]; then + if [[ $count -ne 0 ]]; then count="$((count - 1))" fi fi # clear - if test "$option_count_only" = 'yes'; then + if [[ $option_count_only == 'yes' ]]; then __print_lines "$count" - elif test "$count" -eq 0; then + elif [[ $count -eq 0 ]]; then printf '\e[G\e[J' else printf '\e[%dF\e[J' "$count" @@ -108,7 +108,7 @@ function echo_clear_lines() ( } function on_line { local input="$1" lines - if test -z "$input"; then + if [[ -z $input ]]; then # it is a complete line, so add 1 to count # we don't send empty input to echo-wrap/echo-count-lines as empty input will appear as 0 count="$((count + 1))" @@ -129,7 +129,7 @@ function echo_clear_lines() ( function on_finish { clear_lines } - if test -n "$option_count"; then + if [[ -n $option_count ]]; then count="$option_count" clear_lines else @@ -138,6 +138,6 @@ function echo_clear_lines() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_clear_lines "$@" fi diff --git a/commands/echo-count-lines b/commands/echo-count-lines index 4153d2da3..c81cfa1b1 100755 --- a/commands/echo-count-lines +++ b/commands/echo-count-lines @@ -115,7 +115,7 @@ function echo_count_lines() ( QUIRKS: This does not wrap lines. If you want wrapped lines, send to echo-wrap first, then pipe to echo-count-lines. - Only a single argument is supported, as multiple arguments is ambigious, do you want the count for each argument, or the total? + Only a single argument is supported, as multiple arguments is ambiguous, do you want the count for each argument, or the total? EXAMPLE: @@ -145,7 +145,7 @@ function echo_count_lines() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -165,8 +165,8 @@ function echo_count_lines() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_count_lines_test else echo_count_lines "$@" diff --git a/commands/echo-error b/commands/echo-error index 92e551237..3c6c1d8eb 100755 --- a/commands/echo-error +++ b/commands/echo-error @@ -19,7 +19,7 @@ function echo_error() ( # process local item option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,6 +39,6 @@ function echo_error() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_error "$@" fi diff --git a/commands/echo-escape-backslashes b/commands/echo-escape-backslashes index b402cdf59..7ad18c09e 100755 --- a/commands/echo-escape-backslashes +++ b/commands/echo-escape-backslashes @@ -31,7 +31,7 @@ function echo_escape_backslashes() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -50,6 +50,6 @@ function echo_escape_backslashes() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_backslashes "$@" fi diff --git a/commands/echo-escape-bash b/commands/echo-escape-bash index eb21b5b11..1b2d1d9ac 100755 --- a/commands/echo-escape-bash +++ b/commands/echo-escape-bash @@ -41,6 +41,6 @@ function echo_escape_bash() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_bash "$@" fi diff --git a/commands/echo-escape-command b/commands/echo-escape-command index bafd4e9a9..2b04b676f 100755 --- a/commands/echo-escape-command +++ b/commands/echo-escape-command @@ -36,6 +36,6 @@ function echo_escape_command() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_command "$@" fi diff --git a/commands/echo-escape-newlines b/commands/echo-escape-newlines index 2bb069e1a..62773d0bd 100755 --- a/commands/echo-escape-newlines +++ b/commands/echo-escape-newlines @@ -46,7 +46,7 @@ function echo_escape_newlines() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -71,8 +71,8 @@ function echo_escape_newlines() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_escape_newlines_test else echo_escape_newlines "$@" diff --git a/commands/echo-escape-regexp b/commands/echo-escape-regexp index faf0aa54d..1eeeb7f86 100755 --- a/commands/echo-escape-regexp +++ b/commands/echo-escape-regexp @@ -31,7 +31,7 @@ function echo_escape_regexp() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -62,6 +62,6 @@ function echo_escape_regexp() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_regexp "$@" fi diff --git a/commands/echo-escape-regexp-replacement b/commands/echo-escape-regexp-replacement index 42d8ed0c9..c927514cd 100755 --- a/commands/echo-escape-regexp-replacement +++ b/commands/echo-escape-regexp-replacement @@ -19,7 +19,7 @@ function echo_escape_regexp_replacement() ( $(stdinargs_options_help --) QUIRKS: - If you need backslashes escaped, pipe it to: echo-escape-basklashes + If you need backslashes escaped, pipe it to: echo-escape-backslashes EXAMPLE: @@ -34,7 +34,7 @@ function echo_escape_regexp_replacement() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -53,6 +53,6 @@ function echo_escape_regexp_replacement() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_regexp_replacement "$@" fi diff --git a/commands/echo-escape-spaces b/commands/echo-escape-spaces index 3c49e4728..ed3806556 100755 --- a/commands/echo-escape-spaces +++ b/commands/echo-escape-spaces @@ -31,7 +31,7 @@ function echo_escape_spaces() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -50,6 +50,6 @@ function echo_escape_spaces() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_escape_spaces "$@" fi diff --git a/commands/echo-file b/commands/echo-file index 8e1301a29..642a2891b 100755 --- a/commands/echo-file +++ b/commands/echo-file @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# @todo support these: +# https://github.com/swsnr/mdcat +# https://github.com/efugier/smartcat + function echo_file() ( source "$DOROTHY/sources/stdinargs.bash" @@ -29,7 +33,7 @@ function echo_file() ( $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -37,7 +41,7 @@ function echo_file() ( # process our own arguments, delegate everything else to stdinargs local item option_bat='' option_raw='no' option_plain='no' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -51,7 +55,7 @@ function echo_file() ( '--no-plain'* | '--plain'*) option_plain="$(get-flag-value --affirmative --fallback="$option_plain" -- "$item")" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -62,9 +66,9 @@ function echo_file() ( done # install bat if yes - if test "$option_bat" = 'yes'; then + if [[ $option_bat == 'yes' ]]; then setup-util-bat --quiet - elif test -z "$option_bat"; then + elif [[ -z $option_bat ]]; then if __command_exists -- bat; then option_bat='yes' else @@ -75,21 +79,21 @@ function echo_file() ( # ===================================== # Action - if test "$option_raw" = 'yes'; then + if [[ $option_raw == 'yes' ]]; then # raw function echo_files { local file result=0 - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do file="$1" shift - if test -f "$file"; then + if [[ -f $file ]]; then echo-trim-padding --stdin <"$file" else echo-style --error="The file does not exist." >/dev/stderr result=2 # ENOENT No such file or directory # ^ dont like this, so that all files are noted before crash fi - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then __print_lines '' '' fi done @@ -101,7 +105,7 @@ function echo_file() ( bat --paging=never ) - if test "$option_plain" = 'yes'; then + if [[ $option_plain == 'yes' ]]; then bat_cmd+=( --plain ) @@ -111,13 +115,13 @@ function echo_file() ( --color=always ) fi - if test -z "${BAT_THEME-}"; then + if [[ -z ${BAT_THEME-} ]]; then terminal_theme="$(get-terminal-theme --ignore-cache || :)" - if test "$terminal_theme" = 'light'; then + if [[ $terminal_theme == 'light' ]]; then bat_cmd+=( --theme=ansi ) - elif test "$terminal_theme" = 'dark'; then + elif [[ $terminal_theme == 'dark' ]]; then bat_cmd+=( --theme=1337 ) @@ -126,7 +130,7 @@ function echo_file() ( function echo_file_bat { local file="$1" - if test "$option_plain" = 'yes'; then + if [[ $option_plain == 'yes' ]]; then echo-style --element="$file" "${bat_cmd[@]}" "$file" # eval echo-style --/element="$file" @@ -136,14 +140,14 @@ function echo_file() ( } function echo_files { local file result=0 bat_status=1 - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do file="$1" shift - if test -f "$file"; then - if test "$option_bat" = 'yes'; then + if [[ -f $file ]]; then + if [[ $option_bat == 'yes' ]]; then eval_capture --statusvar=bat_status -- echo_file_bat "$file" fi - if test "$bat_status" -ne 0; then + if [[ $bat_status -ne 0 ]]; then echo-style \ --element="$file" --newline \ --code="$(echo-trim-padding --stdin <"$file")" --newline \ @@ -157,7 +161,7 @@ function echo_file() ( result=2 # ENOENT No such file or directory # ^ dont like this, so that all files are noted before crash fi - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then __print_lines '' '' fi done @@ -177,6 +181,6 @@ function echo_file() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_file "$@" fi diff --git a/commands/echo-filenames b/commands/echo-filenames index bea92a141..e4c92baed 100755 --- a/commands/echo-filenames +++ b/commands/echo-filenames @@ -31,7 +31,7 @@ function echo_filenames() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,6 +48,6 @@ function echo_filenames() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_filenames "$@" fi diff --git a/commands/echo-first-line b/commands/echo-first-line index f842d00db..5a6317f48 100755 --- a/commands/echo-first-line +++ b/commands/echo-first-line @@ -14,7 +14,7 @@ function echo_first_line() ( USAGE: | echo-first-line EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function echo_first_line() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,6 +57,6 @@ function echo_first_line() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_first_line "$@" fi diff --git a/commands/echo-fstab-decode b/commands/echo-fstab-decode index fd9ea8b9d..336182b19 100755 --- a/commands/echo-fstab-decode +++ b/commands/echo-fstab-decode @@ -28,7 +28,7 @@ function echo_fstab_decode() ( | |=|,|\|#| EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -56,6 +56,6 @@ function echo_fstab_decode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_fstab_decode "$@" fi diff --git a/commands/echo-fstab-encode b/commands/echo-fstab-encode index b418b1b70..0548948d4 100755 --- a/commands/echo-fstab-encode +++ b/commands/echo-fstab-encode @@ -29,7 +29,7 @@ function echo_fstab_encode() ( |\040|\075|\054|\134|\043| EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -60,6 +60,6 @@ function echo_fstab_encode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_fstab_encode "$@" fi diff --git a/commands/echo-gnu-command b/commands/echo-gnu-command index 849237307..ebb447974 100755 --- a/commands/echo-gnu-command +++ b/commands/echo-gnu-command @@ -42,7 +42,7 @@ function echo_gnu_command() ( OPTIONS: - A GNU command, with or wihtout the 'g' prefix. + A GNU command, with or without the 'g' prefix. --[no-]fallback=[YES|no] Whether to fallback to the non-gnu command if the gnu command is not found. @@ -65,7 +65,7 @@ function echo_gnu_command() ( /opt/homebrew/bin/gsed EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -73,7 +73,7 @@ function echo_gnu_command() ( # process our own arguments, delegate everything else to stdinargs local item option_fallback='yes' option_install='yes' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -84,7 +84,7 @@ function echo_gnu_command() ( '--no-install'* | '--install'*) option_install="$(get-flag-value --affirmative --fallback="$option_install" -- "$item")" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -100,7 +100,7 @@ function echo_gnu_command() ( function on_input { local gcmd fcmd - if test "${1:0:1}" = 'g'; then + if [[ ${1:0:1} == 'g' ]]; then fcmd="${1:1}" gcmd="$1" else @@ -110,10 +110,10 @@ function echo_gnu_command() ( if type -P "$gcmd" 2>/dev/null; then return 0 fi - if test "$option_fallback" = 'yes' && type -P "$fcmd" 2>/dev/null; then + if [[ $option_fallback == 'yes' ]] && type -P "$fcmd" 2>/dev/null; then return 0 fi - if test "$option_install" = 'yes'; then + if [[ $option_install == 'yes' ]]; then get-installer --first-success --invoke --quiet -- "$gcmd" if type -P "$gcmd" 2>/dev/null; then return 0 @@ -125,8 +125,8 @@ function echo_gnu_command() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_gnu_command_test else echo_gnu_command "$@" diff --git a/commands/echo-html-coder b/commands/echo-html-coder index a793cfb32..af3133add 100755 --- a/commands/echo-html-coder +++ b/commands/echo-html-coder @@ -16,7 +16,7 @@ function echo_html_coder_test() ( local expected for action in "${actions[@]}"; do - if test "$action" = 'encode'; then + if [[ $action == 'encode' ]]; then expected="$expected_encode" else expected="$expected_decode" @@ -72,7 +72,7 @@ function echo_html_coder() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -80,7 +80,7 @@ function echo_html_coder() ( # process our own arguments, delegate everything else to stdinargs local item option_action='' option_tool='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -88,7 +88,7 @@ function echo_html_coder() ( '--encode' | '--decode') option_action="${item#--}" ;; '--action='*) option_action="${item#*=}" ;; '--tool='*) option_tool="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -99,12 +99,12 @@ function echo_html_coder() ( done # ensure tool - if test "$option_tool" = '?'; then + if [[ $option_tool == '?' ]]; then option_tool="$(choose --required 'Which tool to use?' -- "${all_tools[@]}")" if __command_missing -- "$option_tool"; then get-installer --first-success --invoke --quiet -- "$option_tool" fi - elif test -z "$option_tool"; then + elif [[ -z $option_tool ]]; then local item for item in "${all_tools[@]}"; do if __command_exists -- "$item"; then @@ -115,7 +115,7 @@ function echo_html_coder() ( fi # ensure action - if test "$option_action" = '?'; then + if [[ $option_action == '?' ]]; then option_action="$(choose --required 'Which action to perform?' -- 'encode' 'decode')" elif ! [[ $option_action =~ ^(encode|decode)$ ]]; then help "You must provide a valid " @@ -125,17 +125,17 @@ function echo_html_coder() ( # Action local script - # if test "$option_tool" = 'recode'; then + # if [[ "$option_tool" = 'recode' ]]; then # script="$(type -P echo-html-coder.recode)" - # elif test "$option_tool" = 'textutil'; then + # elif [[ "$option_tool" = 'textutil' ]]; then # script="$(type -P echo-html-coder.textutil)" - # elif test "$option_tool" = 'xmlstarlet'; then + # elif [[ "$option_tool" = 'xmlstarlet' ]]; then # script="$(type -P echo-html-coder.xmlstarlet)" - if test "$option_tool" = 'deno'; then + if [[ $option_tool == 'deno' ]]; then script="$(type -P echo-html-coder.ts)" - elif test "$option_tool" = 'php'; then + elif [[ $option_tool == 'php' ]]; then script="$(type -P echo-html-coder.php)" - elif test "$option_tool" = 'python3'; then + elif [[ $option_tool == 'python3' ]]; then script="$(type -P echo-html-coder.py)" else help "The tool [$option_tool] is not yet supported." @@ -148,8 +148,8 @@ function echo_html_coder() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_html_coder_test else echo_html_coder "$@" diff --git a/commands/echo-html-decode b/commands/echo-html-decode index fb74d4291..641555596 100755 --- a/commands/echo-html-decode +++ b/commands/echo-html-decode @@ -32,8 +32,8 @@ function echo_html_decode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_html_decode_test else echo_html_decode "$@" diff --git a/commands/echo-html-encode b/commands/echo-html-encode index 4e0db4a70..b0dbdadd1 100755 --- a/commands/echo-html-encode +++ b/commands/echo-html-encode @@ -32,8 +32,8 @@ function echo_html_encode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_html_encode_test else echo_html_encode "$@" diff --git a/commands/echo-if-command-exists b/commands/echo-if-command-exists index f4f17105e..afbb7057f 100755 --- a/commands/echo-if-command-exists +++ b/commands/echo-if-command-exists @@ -30,7 +30,7 @@ function echo_if_command_exists() ( # exit status: 1 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -51,6 +51,6 @@ function echo_if_command_exists() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_if_command_exists "$@" fi diff --git a/commands/echo-if-directory b/commands/echo-if-directory new file mode 100755 index 000000000..7e9b2296b --- /dev/null +++ b/commands/echo-if-directory @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +function echo_if_directory_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' \ + -- echo-if-directory -- + + eval-tester --name='empty args' \ + -- echo-if-directory -- '' '' + + eval-tester --name='missing' \ + -- echo-if-directory -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' --stdout="$DOROTHY" \ + -- echo-if-directory -- "$DOROTHY" + + eval-tester --name='file' \ + -- echo-if-directory -- "$DOROTHY/README.md" + + eval-tester --name='file then missing then dir' --stdout="$DOROTHY" \ + -- echo-if-directory -- "$DOROTHY/README.md" "$DOROTHY/this-does-not-exist" "$DOROTHY" + + eval-tester --name='dir then missing then file' --stdout="$DOROTHY" \ + -- echo-if-directory -- "$DOROTHY" "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='echo-if-directory' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='echo-if-directory' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='echo-if-directory' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='echo-if-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' --stdout="$dir_symlink" \ + -- echo-if-directory -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- echo-if-directory -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- echo-if-directory -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- echo-if-directory -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) +function echo_if_directory() ( + source "$DOROTHY/sources/stdinargs.bash" + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Output s that are a directory, or an unbroken symlink to a directory. + Companion to [is-directory]. + + USAGE: + echo-if-directory [...options] [--] ... + echo-lines ... | echo-if-directory [...options] + + OPTIONS: + --sudo= + --user= + --group= + Forwarded to [is-directory]. + + $(stdinargs_options_help --) + + EXAMPLES: + + echo-if-directory -- "$HOME" + + $HOME + # exit status: 0 + + echo-lines -- "$HOME" | echo-if-directory --stdin + + $HOME + # exit status: 0 + + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_args=() option_sudo='' 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#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + *) option_args+=("$item") ;; + esac + done + + # ===================================== + # Action + + local paths=() + function on_input { + local path="$1" + if is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + paths+=("$path") + fi + } + function on_finish { + if [[ ${#paths[@]} -ne 0 ]]; then + __print_lines "${paths[@]}" + fi + } + + stdinargs "${option_args[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + echo_if_directory_test + else + echo_if_directory "$@" + fi +fi diff --git a/commands/echo-if-executable b/commands/echo-if-executable new file mode 100755 index 000000000..c59f07790 --- /dev/null +++ b/commands/echo-if-executable @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +function echo_if_executable_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' \ + -- echo-if-executable -- + + eval-tester --name='empty args' \ + -- echo-if-executable -- '' '' + + eval-tester --name='missing' \ + -- echo-if-executable -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' --stdout="$DOROTHY" \ + -- echo-if-executable -- "$DOROTHY" + + eval-tester --name='file' --stdout="$DOROTHY/commands/dorothy" \ + -- echo-if-executable -- "$DOROTHY/commands/dorothy" + + eval-tester --name='file then missing then dir' --stdout="$DOROTHY/commands/dorothy"$'\n'"$DOROTHY" \ + -- echo-if-executable -- "$DOROTHY/commands/dorothy" "$DOROTHY/this-does-not-exist" "$DOROTHY" + + eval-tester --name='dir then missing then file' --stdout="$DOROTHY"$'\n'"$DOROTHY/commands/dorothy" \ + -- echo-if-executable -- "$DOROTHY" "$DOROTHY/this-does-not-exist" "$DOROTHY/commands/dorothy" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='echo-if-executable' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='echo-if-executable' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='echo-if-executable' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='echo-if-executable' --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' --stdout="$dir_symlink" \ + -- echo-if-executable -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- echo-if-executable -- "$file_symlink" + fs-own --quiet --permissions='+x' -- "$file_target" + eval-tester --name='symlink file' --stdout="$file_symlink" \ + -- echo-if-executable -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- echo-if-executable -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- echo-if-executable -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) +function echo_if_executable() ( + source "$DOROTHY/sources/stdinargs.bash" + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Output s that are paths that are executable (including that of symlinks, broken or otherwise). + Companion to [is-executable]. + + USAGE: + echo-if-executable [...options] [--] ... + echo-lines ... | echo-if-executable [...options] + + OPTIONS: + --sudo= + --user= + --group= + Forwarded to [is-executable]. + + $(stdinargs_options_help --) + + EXAMPLES: + + echo-if-executable -- "$HOME" + + $HOME + # exit status: 0 + + echo-lines -- "$HOME" | echo-if-executable --stdin + + $HOME + # exit status: 0 + + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_args=() option_sudo='' 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#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + *) option_args+=("$item") ;; + esac + done + + # ===================================== + # Action + + local paths=() + function on_input { + local path="$1" + if is-executable --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + paths+=("$path") + fi + } + function on_finish { + if [[ ${#paths[@]} -ne 0 ]]; then + __print_lines "${paths[@]}" + fi + } + + stdinargs "${option_args[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + echo_if_executable_test + else + echo_if_executable "$@" + fi +fi diff --git a/commands/echo-if-file b/commands/echo-if-file index 63581a2a7..a47cb5700 100755 --- a/commands/echo-if-file +++ b/commands/echo-if-file @@ -1,57 +1,142 @@ #!/usr/bin/env bash +function echo_if_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' \ + -- echo-if-file -- + + eval-tester --name='empty args' \ + -- echo-if-file -- '' '' + + eval-tester --name='missing' \ + -- echo-if-file -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' \ + -- echo-if-file -- "$DOROTHY" + + eval-tester --name='file' --stdout="$DOROTHY/README.md" \ + -- echo-if-file -- "$DOROTHY/README.md" + + eval-tester --name='file then missing then dir' --stdout="$DOROTHY/README.md" \ + -- echo-if-file -- "$DOROTHY/README.md" "$DOROTHY/this-does-not-exist" "$DOROTHY" + + eval-tester --name='dir then missing then file' --stdout="$DOROTHY/README.md" \ + -- echo-if-file -- "$DOROTHY" "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='echo-if-file' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='echo-if-file' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='echo-if-file' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='echo-if-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' \ + -- echo-if-file -- "$dir_symlink" + + eval-tester --name='symlink file' --stdout="$file_symlink" \ + -- echo-if-file -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' \ + -- echo-if-file -- "$dir_symlink" + + eval-tester --name='broken symlink file' \ + -- echo-if-file -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function echo_if_file() ( source "$DOROTHY/sources/stdinargs.bash" - # ===================================== - # Arguments - function help { cat <<-EOF >/dev/stderr ABOUT: - Output inputs that are files. + Output s that are a file, or an unbroken symlink to a file. + Companion to [is-file]. USAGE: echo-if-file [...options] [--] ... echo-lines ... | echo-if-file [...options] OPTIONS: + --sudo= + --user= + --group= + Forwarded to [is-file]. + $(stdinargs_options_help --) EXAMPLES: - touch file + echo-if-file -- "$DOROTHY/README.md" - echo-if-file -- file missing - - file + $DOROTHY/README.md # exit status: 0 - echo-lines -- file missing | echo-if-file --stdin + echo-lines -- "$DOROTHY/README.md" | echo-if-file --stdin - file + $DOROTHY/README.md # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } + # process + local item option_args=() option_sudo='' 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#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + *) option_args+=("$item") ;; + esac + done + # ===================================== # Action - function on_line { - if test -n "$1" -a -f "$1"; then - __print_lines "$1" + local paths=() + function on_input { + local path="$1" + if is-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + paths+=("$path") fi } - - stdinargs "$@" + function on_finish { + if [[ ${#paths[@]} -ne 0 ]]; then + __print_lines "${paths[@]}" + fi + } + stdinargs "${option_args[@]}" ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - echo_if_file "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + echo_if_file_test + else + echo_if_file "$@" + fi fi diff --git a/commands/echo-if-present b/commands/echo-if-present new file mode 100755 index 000000000..99723ff93 --- /dev/null +++ b/commands/echo-if-present @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +function echo_if_present_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + eval-tester --name='no args' \ + -- echo-if-present -- + + eval-tester --name='empty args' \ + -- echo-if-present -- '' '' + + eval-tester --name='missing' \ + -- echo-if-present -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' --stdout="$DOROTHY" \ + -- echo-if-present -- "$DOROTHY" + + eval-tester --name='file' --stdout="$DOROTHY/README.md" \ + -- echo-if-present -- "$DOROTHY/README.md" + + eval-tester --name='file then missing then dir' --stdout="$DOROTHY/README.md"$'\n'"$DOROTHY" \ + -- echo-if-present -- "$DOROTHY/README.md" "$DOROTHY/this-does-not-exist" "$DOROTHY" + + eval-tester --name='dir then missing then file' --stdout="$DOROTHY"$'\n'"$DOROTHY/README.md" \ + -- echo-if-present -- "$DOROTHY" "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" + + # test working symlinks + local dir_target dir_symlink file_target file_symlink + dir_target="$(fs-temp --directory='echo-if-present' --directory='dir_target' --touch)" + file_target="$(fs-temp --directory='echo-if-present' --file='file_target' --touch)" + dir_symlink="$(fs-temp --directory='echo-if-present' --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --directory='echo-if-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' --stdout="$dir_symlink" \ + -- echo-if-present -- "$dir_symlink" + + eval-tester --name='symlink file' --stdout="$file_symlink" \ + -- echo-if-present -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --stdout="$dir_symlink" \ + -- echo-if-present -- "$dir_symlink" + + eval-tester --name='broken symlink file' --stdout="$file_symlink" \ + -- echo-if-present -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) +function echo_if_present() ( + source "$DOROTHY/sources/stdinargs.bash" + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Output s that are paths that are present (including that of symlinks, broken or otherwise). + Companion to [is-missing], [is-present]. + + USAGE: + echo-if-present [...options] [--] ... + echo-lines ... | echo-if-present [...options] + + OPTIONS: + --sudo= + --user= + --group= + Forwarded to [is-present]. + + $(stdinargs_options_help --) + + EXAMPLES: + + echo-if-present -- "$HOME" + + $HOME + # exit status: 0 + + echo-lines -- "$HOME" | echo-if-present --stdin + + $HOME + # exit status: 0 + + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_args=() option_sudo='' 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#*=}" ;; + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options + '--') + option_args+=("$item" "$@") + shift $# + break + ;; + *) option_args+=("$item") ;; + esac + done + + # ===================================== + # Action + + local paths=() + function on_input { + local path="$1" + if is-present --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + paths+=("$path") + fi + } + function on_finish { + if [[ ${#paths[@]} -ne 0 ]]; then + __print_lines "${paths[@]}" + fi + } + + stdinargs "${option_args[@]}" +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + echo_if_present_test + else + echo_if_present "$@" + fi +fi diff --git a/commands/echo-join b/commands/echo-join index e4999e63a..702942025 100755 --- a/commands/echo-join +++ b/commands/echo-join @@ -43,7 +43,7 @@ function echo_join() ( QUIRKS: There will be no newline at the end, in case you your input or contains newlines, we do not wish to mangle your intention. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -52,13 +52,13 @@ function echo_join() ( # process our own arguments, delegate everything else to stdinargs local rand="$RANDOM" local item option_join="$rand" option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--join='*) option_join="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -66,7 +66,7 @@ function echo_join() ( ;; '--'*) option_args+=("$item") ;; *) - if test "$option_join" = "$rand"; then + if [[ $option_join == "$rand" ]]; then option_join="$item" else option_args+=("$item") @@ -76,7 +76,7 @@ function echo_join() ( done # default to empty space - if test "$option_join" = "$rand"; then + if [[ $option_join == "$rand" ]]; then option_join=' ' fi @@ -90,7 +90,7 @@ function echo_join() ( function on_finish { local index for index in "${!items[@]}"; do - if test "$index" -eq 0; then + if [[ $index -eq 0 ]]; then printf '%s' "${items[index]}" else printf '%s%s' "$option_join" "${items[index]}" @@ -102,8 +102,8 @@ function echo_join() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_join_test else echo_join "$@" diff --git a/commands/echo-lines b/commands/echo-lines index dd1e3a2f7..54a42e6c7 100755 --- a/commands/echo-lines +++ b/commands/echo-lines @@ -203,7 +203,7 @@ function echo_lines() ( $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -211,7 +211,7 @@ function echo_lines() ( # process our own arguments, delegate everything else to stdinargs local item option_columns=1 option_width='terminal' option_filler=' ' option_distance=2 option_spread='yes' option_shrink='yes' option_indent='' option_prefix='' option_suffix='' option_quote='no' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -238,7 +238,7 @@ function echo_lines() ( '--no-quote'* | '--quote'*) # will also support quoted option_quote="$(get-flag-value --affirmative --fallback="$option_quote" -- "$item")" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -261,7 +261,7 @@ function echo_lines() ( function on_input { items+=("$option_prefix$(echo-escape-command -- "$1")$option_suffix") } - elif [[ $option_quote == 'generic' || $option_quote == 'yes' ]]; then + elif [[ $option_quote =~ ^(generic|yes)$ ]]; then function on_input { items+=("$option_prefix$(echo-quote -- "$1")$option_suffix") } @@ -272,7 +272,7 @@ function echo_lines() ( fi function floor { local n="$1" d="$2" - if test "$d" -eq 1; then + if [[ $d -eq 1 ]]; then __print_lines "$n" return 0 fi @@ -280,7 +280,7 @@ function echo_lines() ( } function ceil { local n="$1" d="$2" - if test "$d" -eq 1; then + if [[ $d -eq 1 ]]; then __print_lines "$n" return 0 fi @@ -294,7 +294,7 @@ function echo_lines() ( # '\t\t'[0] = 16, '\t\t':0:1 = 8 local input="$1" size=0 for ((i = 0; i < ${#input}; i++)); do - if test "${input:i:1}" = $'\t'; then + if [[ ${input:i:1} == $'\t' ]]; then size="$((size + 8))" else size="$((size + 1))" @@ -316,43 +316,43 @@ function echo_lines() ( function output_single_column { # prepare the format local format='%s\n' - if test -n "$option_indent"; then + if [[ -n $option_indent ]]; then format="$option_indent$format" fi # output lines to the format - if test "${#items[@]}" -ne 0; then + if [[ ${#items[@]} -ne 0 ]]; then printf "$format" "${items[@]}" fi } function on_finish { # verify local -i columns terminal_size terminal_columns=0 - if test -z "$option_columns" -o "$option_columns" = '0'; then + if [[ -z $option_columns || $option_columns == '0' ]]; then columns=0 elif is-integer -- "$option_columns"; then columns="$option_columns" else help " must be a positive integer: $option_columns" fi - if test "$columns" -eq 1; then + if [[ $columns -eq 1 ]]; then # simple mode, as single column output_single_column return fi - if test "$columns" -le 1 -a "$option_width" = 'inputs'; then + if [[ $columns -le 1 && $option_width == 'inputs' ]]; then # simple mode, inputs requires at least 2 columns output_single_column return fi # if unable to autodetect, be simple - if test -z "$option_width"; then + if [[ -z $option_width ]]; then option_width='terminal' fi - if test "$option_width" = 'terminal'; then + if [[ $option_width == 'terminal' ]]; then mapfile -t terminal_size < <(get-terminal-lines-and-columns || :) - if test "${#terminal_size[@]}" -eq 2; then + if [[ ${#terminal_size[@]} -eq 2 ]]; then terminal_columns="${terminal_size[1]}" else # simple mode, as cannot determine terminal size @@ -362,10 +362,10 @@ function echo_lines() ( fi # verify - if test -z "$option_filler"; then + if [[ -z $option_filler ]]; then help " cannot be empty: $option_filler" fi - if ! is-integer -- "$option_distance" || test "$option_distance" -le 0; then + if ! is-integer -- "$option_distance" || [[ $option_distance -le 0 ]]; then help " must be a positive integer: $option_distance" fi @@ -378,11 +378,11 @@ function echo_lines() ( local item item_size largest_item_size=0 for item in "${items[@]}"; do item_size="${#item}" - if test "$item_size" -gt "$largest_item_size"; then + if [[ $item_size -gt $largest_item_size ]]; then largest_item_size="$item_size" fi done - if test "$largest_item_size" -eq 0; then + if [[ $largest_item_size -eq 0 ]]; then # nothing to output return 0 fi @@ -393,8 +393,8 @@ function echo_lines() ( generic_column_size="$(ceil "$generic_column_size" "$filler_size")" indent_column_size="$((indent_size + largest_item_size + option_distance))" indent_column_size="$(ceil "$indent_column_size" "$filler_size")" - if test "$columns" -eq 0; then - if test "$terminal_columns" -le 0; then + if [[ $columns -eq 0 ]]; then + if [[ $terminal_columns -le 0 ]]; then # simple mode, as cannot determine terminal size output_single_column return @@ -406,29 +406,29 @@ function echo_lines() ( # calculate canvas size local canvas_size - if test "$option_width" = 'inputs'; then + if [[ $option_width == 'inputs' ]]; then canvas_size="$content_size" - elif test "$option_width" = 'terminal'; then + elif [[ $option_width == 'terminal' ]]; then canvas_size="$terminal_columns" elif is-integer -- "$option_width"; then canvas_size="$option_width" else help " must be one of: inputs, terminal, or an integer: $option_width" fi - if test "$canvas_size" -le 0; then + if [[ $canvas_size -le 0 ]]; then # simple mode, as could not determine canvas_size output_single_column return fi - if test "$canvas_size" -le "$indent_column_size"; then + if [[ $canvas_size -le $indent_column_size ]]; then # simple mode, as columns are too large output_single_column return fi - if test "$canvas_size" -le "$content_size"; then + if [[ $canvas_size -le $content_size ]]; then # shrink the amount of columns, as there are too many columns="$((1 + (canvas_size - indent_column_size) / generic_column_size))" - if test "$columns" -le 1; then + if [[ $columns -le 1 ]]; then # simple mode, as single column output_single_column return @@ -439,18 +439,18 @@ function echo_lines() ( # shrink or expand if we care for that local unused_size=0 margin_size=0 - if test "$option_width" != 'inputs'; then + if [[ $option_width != 'inputs' ]]; then # if the canvas will be mostly empty, shrink it local temp_size - if test "$option_shrink" = 'yes'; then + if [[ $option_shrink == 'yes' ]]; then temp_size="$(get_inverse_proportion "$canvas_size" "$columns")" - if test "$temp_size" -gt "$content_size"; then + if [[ $temp_size -gt $content_size ]]; then canvas_size="$temp_size" fi fi # expand the column size to fill the canvas - if test "$option_spread" = 'yes'; then + if [[ $option_spread == 'yes' ]]; then unused_size="$((canvas_size - content_size))" unused_size="$(floor "$unused_size" "$filler_size")" margin_size="$((unused_size / columns))" @@ -462,7 +462,7 @@ function echo_lines() ( local item_column_size padding_size column=0 trailing_newline='no' for item in "${items[@]}"; do item_size="${#item}" - if test "$column" -eq 0; then + if [[ $column -eq 0 ]]; then # first column item_column_size="$indent_column_size" item_size="$((item_size + indent_size))" @@ -473,7 +473,7 @@ function echo_lines() ( printf '%s' "$item" fi column="$((column + 1))" - if test "$column" -eq "$columns"; then + if [[ $column -eq $columns ]]; then # last column trailing_newline='yes' printf '\n' @@ -487,7 +487,7 @@ function echo_lines() ( done fi done - if test "$trailing_newline" = 'no'; then + if [[ $trailing_newline == 'no' ]]; then printf '\n' fi return 0 @@ -497,8 +497,8 @@ function echo_lines() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_lines_test else echo_lines "$@" diff --git a/commands/echo-lines-after b/commands/echo-lines-after index 42b2b79cb..49a5bbdf6 100755 --- a/commands/echo-lines-after +++ b/commands/echo-lines-after @@ -70,7 +70,7 @@ function echo_lines_after() ( b # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -78,13 +78,13 @@ function echo_lines_after() ( # process our own arguments, delegate everything else to stdinargs local item option_needles=() option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--needle='*) option_needles+=("${item#*=}") ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -92,7 +92,7 @@ function echo_lines_after() ( ;; '--'*) option_args+=("$item") ;; *) - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then option_needles+=("$item") else option_args+=("$item") @@ -102,7 +102,7 @@ function echo_lines_after() ( done # check - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -112,18 +112,18 @@ function echo_lines_after() ( local output='no' found='no' function on_line { local line="$1" match='no' needle - if test "$output" = 'no'; then + if [[ $output == 'no' ]]; then for needle in "${option_needles[@]}"; do - if test "$line" = "$needle"; then + if [[ $line == "$needle" ]]; then match='yes' break fi done - if test "$match" = 'yes'; then + if [[ $match == 'yes' ]]; then # found, but not yet stopped finding, continue to next line found='yes' return 0 - elif test "$found" = 'yes'; then + elif [[ $found == 'yes' ]]; then # found, and stopped matching, start outputting output='yes' else @@ -138,8 +138,8 @@ function echo_lines_after() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_lines_after_test else echo_lines_after "$@" diff --git a/commands/echo-lines-before b/commands/echo-lines-before index 6f365da74..e717e1ed1 100755 --- a/commands/echo-lines-before +++ b/commands/echo-lines-before @@ -70,7 +70,7 @@ function echo_lines_before() ( a # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -78,13 +78,13 @@ function echo_lines_before() ( # process our own arguments, delegate everything else to stdinargs local item option_needles=() option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--needle='*) option_needles+=("${item#*=}") ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -92,7 +92,7 @@ function echo_lines_before() ( ;; '--'*) option_args+=("$item") ;; *) - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then option_needles+=("$item") else option_args+=("$item") @@ -102,7 +102,7 @@ function echo_lines_before() ( done # check - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -112,7 +112,7 @@ function echo_lines_before() ( function on_line { local line="$1" needle for needle in "${option_needles[@]}"; do - if test "$line" = "$needle"; then + if [[ $line == "$needle" ]]; then return 210 # ECUSTOM 210 Processing complete, exit early fi done @@ -123,8 +123,8 @@ function echo_lines_before() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_lines_before_test else echo_lines_before "$@" diff --git a/commands/echo-lowercase b/commands/echo-lowercase index 59d056499..868e388c8 100755 --- a/commands/echo-lowercase +++ b/commands/echo-lowercase @@ -33,7 +33,7 @@ function echo_lowercase() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -50,6 +50,6 @@ function echo_lowercase() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_lowercase "$@" fi diff --git a/commands/echo-magnet-hash b/commands/echo-magnet-hash index 12018e128..e974c7a69 100755 --- a/commands/echo-magnet-hash +++ b/commands/echo-magnet-hash @@ -41,7 +41,7 @@ function echo_magnet_hash() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -52,7 +52,7 @@ function echo_magnet_hash() ( function on_line { hash="$(echo-regexp -o 'btih:([\w\d]+)' '$1' -- "$1" || :)" - if test -n "$hash"; then + if [[ -n $hash ]]; then # convert to lowercase __lowercase_string -- "$hash" fi @@ -62,8 +62,8 @@ function echo_magnet_hash() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_magnet_hash_test else echo_magnet_hash "$@" diff --git a/commands/echo-math b/commands/echo-math index 4fbee82e8..e5520e94b 100755 --- a/commands/echo-math +++ b/commands/echo-math @@ -58,7 +58,7 @@ function echo_math() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Compute a mathmatical expression. + Compute a mathematical expression. USAGE: echo-math [...options] [--] ... @@ -75,7 +75,7 @@ function echo_math() ( $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -83,14 +83,14 @@ function echo_math() ( # process our own arguments, delegate everything else to stdinargs local item option_precision=6 option_tool='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--precision='*) option_precision="${item#*=}" ;; '--tool='*) option_tool="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -101,12 +101,12 @@ function echo_math() ( done # ensure tool - if test "$option_tool" = '?'; then + if [[ $option_tool == '?' ]]; then option_tool="$(choose --required 'Which math engine to use?' -- "${all_tools[@]}")" if __command_missing -- "$option_tool"; then get-installer --first-success --invoke --quiet -- "$option_tool" fi - elif test -z "$option_tool"; then + elif [[ -z $option_tool ]]; then local item for item in "${all_tools[@]}"; do if __command_exists -- "$item"; then @@ -124,19 +124,19 @@ function echo_math() ( # 0. becomes ., hence need for printf # awk: 6 decimals by default - if test "$option_tool" = 'bc'; then + if [[ $option_tool == 'bc' ]]; then function on_line { local formula="$1" printf "%.${option_precision}f\n" "$(bc -l <<<"$formula")" } - elif test "$option_tool" = 'deno'; then + elif [[ $option_tool == 'deno' ]]; then local deno_script deno_script="$(type -P echo-math.ts)" function on_line { local formula="$1" "$deno_script" "$option_precision" "$formula" } - elif test "$option_tool" = 'perl'; then + elif [[ $option_tool == 'perl' ]]; then function on_line { local formula="$1" perl -E "say sprintf '%.${option_precision}f', $formula" @@ -149,8 +149,8 @@ function echo_math() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_math_test else echo_math "$@" diff --git a/commands/echo-or-fail b/commands/echo-or-fail index 543bc59d1..23a18e31c 100755 --- a/commands/echo-or-fail +++ b/commands/echo-or-fail @@ -37,7 +37,7 @@ function echo_or_fail() ( # exit status: 96 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -58,6 +58,6 @@ function echo_or_fail() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_or_fail "$@" fi diff --git a/commands/echo-paths-and-basenames b/commands/echo-paths-and-basenames index 4ba81a1ec..1aaec6136 100755 --- a/commands/echo-paths-and-basenames +++ b/commands/echo-paths-and-basenames @@ -30,7 +30,7 @@ function echo_paths_and_basenames() ( # bar EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,6 +48,6 @@ function echo_paths_and_basenames() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_paths_and_basenames "$@" fi diff --git a/commands/echo-quote b/commands/echo-quote index 6fb181535..64e82e893 100755 --- a/commands/echo-quote +++ b/commands/echo-quote @@ -96,7 +96,7 @@ function echo_quote() ( $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -104,14 +104,14 @@ function echo_quote() ( # process our own arguments, delegate everything else to stdinargs local item option_quote_desired='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--double') option_quote_desired='double' ;; '--single') option_quote_desired='single' ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -191,8 +191,8 @@ function echo_quote() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_quote_test else echo_quote "$@" diff --git a/commands/echo-regexp b/commands/echo-regexp index a2b212202..7e10dd8e4 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 \ @@ -20,43 +20,43 @@ function echo_regexp_test() ( # -------- - eval-tester --name='wrap [bc] with squiglies' --stdout='a{b}cd' \ + eval-tester --name='wrap [bc] with squigglies' --stdout='a{b}cd' \ -- echo-regexp '([bc])' '{$1}' -- 'abcd' - eval-tester --name='wrap [bc] with squiglies' --stdout=$'a\n{b}\nc\nd' \ + eval-tester --name='wrap [bc] with squigglies' --stdout=$'a\n{b}\nc\nd' \ -- echo-regexp '([bc])' '{$1}' -- $'a\nb\nc\nd' - eval-tester --name='wrap [bc] with squiglies, -g' --stdout='a{b}{c}d' \ + eval-tester --name='wrap [bc] with squigglies, -g' --stdout='a{b}{c}d' \ -- echo-regexp -g '([bc])' '{$1}' -- 'abcd' - eval-tester --name='wrap [bc] with squiglies, -g' --stdout=$'a\n{b}\n{c}\nd' \ + eval-tester --name='wrap [bc] with squigglies, -g' --stdout=$'a\n{b}\n{c}\nd' \ -- echo-regexp -g '([bc])' '{$1}' -- $'a\nb\nc\nd' - eval-tester --name='wrap [bc] with squiglies, -go' --stdout='{b}{c}' \ + eval-tester --name='wrap [bc] with squigglies, -go' --stdout='{b}{c}' \ -- echo-regexp -go '([bc])' '{$1}' -- 'abcd' - eval-tester --name='wrap [bc] with squiglies, -go' --stdout='{b}{c}' \ + eval-tester --name='wrap [bc] with squigglies, -go' --stdout='{b}{c}' \ -- echo-regexp -go '([bc])' '{$1}' -- $'a\nb\nc\nd' - eval-tester --name='wrap [bc] with squiglies, -gon' --stdout=$'{b}\n{c}' \ + eval-tester --name='wrap [bc] with squigglies, -gon' --stdout=$'{b}\n{c}' \ -- echo-regexp -gon '([bc])' '{$1}' -- 'abcd' - eval-tester --name='wrap [bc] with squiglies, -gon' --stdout=$'{b}\n{c}' \ + eval-tester --name='wrap [bc] with squigglies, -gon' --stdout=$'{b}\n{c}' \ -- echo-regexp -gon '([bc])' '{$1}' -- $'a\nb\nc\nd' # -------- - eval-tester --name='wrap any with squiglies' --stdout=$'{a}\nb\nc\nd' \ + eval-tester --name='wrap any with squigglies' --stdout=$'{a}\nb\nc\nd' \ -- echo-regexp '(.+)' '{$1}' -- $'a\nb\nc\nd' - eval-tester --name='wrap any with squiglies, -g' --stdout=$'{a}\n{b}\n{c}\n{d}' \ + eval-tester --name='wrap any with squigglies, -g' --stdout=$'{a}\n{b}\n{c}\n{d}' \ -- echo-regexp -g '(.+)' '{$1}' -- $'a\nb\nc\nd' - eval-tester --name='wrap any with squiglies, -s' --stdout=$'{a\nb\nc\nd}' \ + eval-tester --name='wrap any with squigglies, -s' --stdout=$'{a\nb\nc\nd}' \ -- echo-regexp -s '(.+)' '{$1}' -- $'a\nb\nc\nd' if ! is-alpine; then - eval-tester --name='wrap any with squiglies, -s via group' --stdout=$'{a\nb\nc\nd}' \ + eval-tester --name='wrap any with squigglies, -s via group' --stdout=$'{a\nb\nc\nd}' \ -- echo-regexp '(?s:(.+))' '{$1}' -- $'a\nb\nc\nd' fi @@ -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' # -------- @@ -185,7 +185,7 @@ function echo_regexp() ( } EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -193,7 +193,7 @@ function echo_regexp() ( # args local item option_flags='' option_args=() option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -213,14 +213,14 @@ function echo_regexp() ( '--verbose') option_flags+='v' ;; '--fail') option_flags+='f' ;; '--find='* | '--search='* | '--regexp='*) - if test "${#option_args[@]}" -ne 0; then + if [[ ${#option_args[@]} -ne 0 ]]; then help ' must come before <...replacement>' else option_args+=("${item#*=}") fi ;; '--replace='* | '--replacement='*) - if test "${#option_args[@]}" -eq 0; then + if [[ ${#option_args[@]} -eq 0 ]]; then help ' must come after ' else option_args+=("${item#*=}") @@ -236,7 +236,7 @@ function echo_regexp() ( done # check for search pattern - if test "${#option_args[@]}" -eq 0; then + if [[ ${#option_args[@]} -eq 0 ]]; then help 'No pattern provided.' fi @@ -254,7 +254,7 @@ function echo_regexp() ( "$deno_script" "$option_flags" "${option_args[@]}" } - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then do_regexp else local input @@ -265,8 +265,8 @@ function echo_regexp() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_regexp_test else echo_regexp "$@" diff --git a/commands/echo-revolving-door b/commands/echo-revolving-door index 09d8df1ca..2d2ab1f4b 100755 --- a/commands/echo-revolving-door +++ b/commands/echo-revolving-door @@ -9,7 +9,7 @@ function echo_revolving_door() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Continously clear the output of a command, showing only the latest output, then clearing it upon completion. + Continuously clear the output of a command, showing only the latest output, then clearing it upon completion. USAGE: (echo-lines -- 1 2; sleep 2; echo-lines -- 3 4; sleep 2) | echo-revolving-door [...options] @@ -19,7 +19,7 @@ function echo_revolving_door() ( --columns= The number of columns to display. If not provided, the terminal's columns will be used. If the terminal's columns cannot be determined, or if <= 0, then the full line will be displayed. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function echo_revolving_door() ( # process local item option_columns='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,10 +39,10 @@ function echo_revolving_door() ( done # determine columns - if test -z "$option_columns"; then + if [[ -z $option_columns ]]; then local terminal_size=() mapfile -t terminal_size < <(get-terminal-lines-and-columns || :) - if test "${#terminal_size[@]}" -eq 2; then + if [[ ${#terminal_size[@]} -eq 2 ]]; then option_columns="${terminal_size[1]}" fi fi @@ -50,13 +50,13 @@ function echo_revolving_door() ( # ===================================== # Action - if test -z "$option_columns" || test "$option_columns" -le 0; then + if [[ -z $option_columns || $option_columns -le 0 ]]; then cat else local input negative_columns negative_columns="$((option_columns * -1))" - while IFS= read -r input || test -n "$input"; do - if test -z "$input"; then + while IFS= read -r input || [[ -n $input ]]; do + if [[ -z $input ]]; then continue fi # trim shapeshifting @@ -66,7 +66,7 @@ function echo_revolving_door() ( # get only the last columns input="$(__substr "$input" "$negative_columns")" # check we still have something left - if test -z "$input"; then + if [[ -z $input ]]; then continue fi # clear the single line, and output the new line @@ -77,6 +77,6 @@ function echo_revolving_door() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_revolving_door "$@" fi diff --git a/commands/echo-split b/commands/echo-split index e2578e21c..63bea8a50 100755 --- a/commands/echo-split +++ b/commands/echo-split @@ -19,7 +19,7 @@ function echo_split() ( OPTIONS: | --needle= The deliminator characters to split the at. - Use [--needle=] to provide multiple deliminators. + Use [--needle=] to provide multiple delimiters. $(stdinargs_options_help --) @@ -33,7 +33,7 @@ function echo_split() ( d # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -41,13 +41,13 @@ function echo_split() ( # process our own arguments, delegate everything else to stdinargs local item option_needles=() option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--needle='*) option_needles+=("${item#*=}") ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -55,7 +55,7 @@ function echo_split() ( ;; '--'*) option_args+=("$item") ;; *) - if test "${#option_needles[@]}" -eq 0; then + if [[ ${#option_needles[@]} -eq 0 ]]; then option_needles+=("$item") else option_args+=("$item") @@ -64,8 +64,8 @@ function echo_split() ( esac done - # checck - if test "${#option_needles[@]}" -eq 0; then + # check + if [[ ${#option_needles[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -85,6 +85,6 @@ function echo_split() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_split "$@" fi diff --git a/commands/echo-style b/commands/echo-style index deff2362a..81c9ef264 100755 --- a/commands/echo-style +++ b/commands/echo-style @@ -81,7 +81,7 @@ function echo_style() ( # process local item items=() option_trail='yes' option_debug='no' option_color option_color="$(get-terminal-color-support --fallback=yes)" # parse env only, as flags are handled by us to support color and nocolor modifiers - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -136,7 +136,7 @@ function echo_style() ( function append_style { # this should be similar to refresh_style_cache in styles.bash local style="$1" var='' found='no' - if test "$ITEM_COLOR" = 'yes'; then + if [[ $ITEM_COLOR == 'yes' ]]; then # begin var="style__color__${style}" if __is_var_set "$var"; then @@ -214,20 +214,20 @@ function echo_style() ( fi fi # only respect found on versions of bash that can detect accurately detect it, as otherwise empty values will be confused as not found - if test "$found" = 'no' -a "$IS_BASH_VERSION_OUTDATED" = 'no'; then + if [[ $found == 'no' && $IS_BASH_VERSION_OUTDATED == 'no' ]]; then MISSING_STYLES+=("${style}") fi } for item in "${items[@]}"; do # check flag status - if test "${item:0:2}" != '--' -o "$item" = '--' -o "$item" = '--='; then - # not a flag, just item content, e.g. 'Hello', '--', '--=' - buffer_left+="$item" - continue - elif test "${item:0:3}" = '--='; then - # empty flag, just item content, e.g. '--=Hello' + if [[ ${item:0:3} == '--=' ]]; then + # empty flag, just item content, e.g. '--=Hello', --=--= buffer_left+="${item:3}" continue + elif [[ ${item:0:2} != '--' || $item == '--' ]]; then + # not a flag, just item content, e.g. 'Hello', '--' + buffer_left+="$item" + continue fi flag="${item:2}" item_content='' @@ -235,7 +235,7 @@ function echo_style() ( # get the flag and value combo for ((i = 0; i < ${#flag}; i++)); do - if test "${flag:i:1}" = '='; then + if [[ ${flag:i:1} == '=' ]]; then generic='no' item_content="${flag:i+1}" flag="${flag:0:i}" @@ -251,7 +251,7 @@ function echo_style() ( ITEM_BEGIN='' ITEM_END='' for ((current_char_index = 0; current_char_index <= ${#flag}; current_char_index++)); do - if test "${flag:current_char_index:1}" = '+' -o "$current_char_index" -eq "${#flag}"; then + if [[ ${flag:current_char_index:1} == '+' || $current_char_index -eq ${#flag} ]]; then style="${flag:last_char_index:current_char_index-last_char_index}" last_char_index="$((current_char_index + 1))" style="${style//-/_}" # convert hyphens to underscores @@ -263,7 +263,7 @@ function echo_style() ( /*) style="slash_${style:1}" ;; */) style="$(__substr "$style" 0 -1)_slash" ;; status) - if test "$item_content" -eq 0; then + if [[ $item_content -eq 0 ]]; then style='good3' else style='error3' @@ -287,7 +287,7 @@ function echo_style() ( continue ;; tty) - if test -z "${terminal_device_file-}"; then + if [[ -z ${terminal_device_file-} ]]; then terminal_device_file="$(get-terminal-device-file)" fi item_target="$terminal_device_file" @@ -307,14 +307,14 @@ function echo_style() ( # handle nocolor and color correctly, as in conditional output based on NO_COLOR=true # e.g. env COLOR=false echo-style --color=yes --nocolor=no # outputs no # e.g. env COLOR=true echo-style --color=yes --nocolor=no # outputs yes - if test "$option_color" != "$ITEM_COLOR"; then + if [[ $option_color != "$ITEM_COLOR" ]]; then continue fi # if it is generic, add the styles (except disable) to the buffer instead - if test "$generic" = 'yes'; then + if [[ $generic == 'yes' ]]; then # flush buffer if necessary - if test "$item_target" != "$buffer_target"; then + if [[ $item_target != "$buffer_target" ]]; then __print_string "${buffer_left}" >"$buffer_target" buffer_left='' buffer_target="$item_target" @@ -327,7 +327,7 @@ function echo_style() ( buffer_right="${ITEM_END}${buffer_right}" else # flush buffer if necessary - if test "$item_target" != "$buffer_target"; then + if [[ $item_target != "$buffer_target" ]]; then __print_string "${buffer_left}" >"$buffer_target" buffer_left='' __print_string "${ITEM_BEGIN}${item_content}${ITEM_END}" >"$item_target" @@ -338,17 +338,17 @@ function echo_style() ( done # close the buffer - if test "$option_trail" = 'yes'; then + if [[ $option_trail == 'yes' ]]; then buffer_right+=$'\n' fi __print_string "${buffer_left}${buffer_disable}${buffer_right}" >"$buffer_target" - if test "$option_debug" = 'yes' -a "${#MISSING_STYLES[@]}" -ne 0; then + if [[ $option_debug == 'yes' || ${#MISSING_STYLES[@]} -ne 0 ]]; then __print_lines 'ERROR: MISSING STYLES:' "${MISSING_STYLES[@]}" >/dev/stderr return 22 # EINVAL 22 Invalid argument fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_style "$@" fi diff --git a/commands/echo-subpaths b/commands/echo-subpaths index e2b013138..051994602 100755 --- a/commands/echo-subpaths +++ b/commands/echo-subpaths @@ -27,7 +27,7 @@ function echo_subpaths() ( echo-subpaths -- . echo-subpaths -- ./* EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -37,7 +37,7 @@ function echo_subpaths() ( # Action function on_line { - if test -d "$1"; then + if [[ -d $1 ]]; then fs-absolute -- "$1/"* else fs-absolute -- "$1" @@ -48,6 +48,6 @@ function echo_subpaths() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_subpaths "$@" fi diff --git a/commands/echo-substr b/commands/echo-substr index 4b882b1e5..6ce1c9665 100755 --- a/commands/echo-substr +++ b/commands/echo-substr @@ -58,7 +58,7 @@ function echo_substr() ( $(stdinargs_options_help --) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -66,23 +66,23 @@ function echo_substr() ( # process our own arguments, delegate everything else to stdinargs local item option_start='' option_length='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--start='*) option_start="${item#*=}" ;; '--length='*) option_length="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# break ;; *) - if test -z "$option_start"; then + if [[ -z $option_start ]]; then option_start="$item" - elif test -z "$option_length"; then + elif [[ -z $option_length ]]; then option_length="$item" else option_args+=("$item") @@ -102,8 +102,8 @@ function echo_substr() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_substr_test else echo_substr "$@" diff --git a/commands/echo-trim-colors b/commands/echo-trim-colors index 1c86c32e7..0fd5683a0 100755 --- a/commands/echo-trim-colors +++ b/commands/echo-trim-colors @@ -44,7 +44,7 @@ function echo_trim_colors() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -72,8 +72,8 @@ function echo_trim_colors() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_trim_colors_test else echo_trim_colors "$@" diff --git a/commands/echo-trim-each-line b/commands/echo-trim-each-line index 7d4db733f..f4fcd98e0 100755 --- a/commands/echo-trim-each-line +++ b/commands/echo-trim-each-line @@ -53,7 +53,7 @@ function echo_trim_each_line() ( b EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -78,8 +78,8 @@ function echo_trim_each_line() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_trim_each_line_test else echo_trim_each_line "$@" diff --git a/commands/echo-trim-empty-lines b/commands/echo-trim-empty-lines index 1fd4151cf..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] [--] ... @@ -33,7 +34,7 @@ function echo_trim_empty_lines() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -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 } @@ -52,6 +53,6 @@ function echo_trim_empty_lines() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_trim_empty_lines "$@" fi diff --git a/commands/echo-trim-padding b/commands/echo-trim-padding index 467f5f307..48cc10fe6 100755 --- a/commands/echo-trim-padding +++ b/commands/echo-trim-padding @@ -27,7 +27,7 @@ function echo_trim_padding() ( b EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -41,13 +41,13 @@ 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 test "$started" = 'yes'; then + if is-whitespace -- "$1"; then + if [[ $started == 'yes' ]]; then queue+=("$1") fi else started='yes' - if test "${#queue[@]}" -ne 0; then + if [[ ${#queue[@]} -ne 0 ]]; then for line in "${queue[@]}"; do __print_lines "$line" done @@ -61,6 +61,6 @@ function echo_trim_padding() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_trim_padding "$@" fi diff --git a/commands/echo-trim-special b/commands/echo-trim-special index ea628d83b..558c8af99 100755 --- a/commands/echo-trim-special +++ b/commands/echo-trim-special @@ -34,7 +34,7 @@ function echo_trim_special() ( This will trim slashes and question marks, etc. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -59,6 +59,6 @@ function echo_trim_special() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_trim_special "$@" fi diff --git a/commands/echo-trim-zero-length b/commands/echo-trim-zero-length index 57fd85655..db5f093f3 100755 --- a/commands/echo-trim-zero-length +++ b/commands/echo-trim-zero-length @@ -59,7 +59,7 @@ function echo_trim_zero_length() ( false EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -69,7 +69,7 @@ function echo_trim_zero_length() ( # Action function on_line { - if test -n "$1"; then + if [[ -n $1 ]]; then __print_lines "$1" fi } @@ -78,8 +78,8 @@ function echo_trim_zero_length() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_trim_zero_length_test else echo_trim_zero_length "$@" diff --git a/commands/echo-url-coder b/commands/echo-url-coder index a49a56760..4e6846e9d 100755 --- a/commands/echo-url-coder +++ b/commands/echo-url-coder @@ -15,7 +15,7 @@ function echo_url_coder_test() ( local expected for action in "${actions[@]}"; do - if test "$action" = 'encode'; then + if [[ $action == 'encode' ]]; then expected="$expected_encode" else expected="$expected_decode" @@ -83,7 +83,7 @@ function echo_url_coder() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -91,7 +91,7 @@ function echo_url_coder() ( # process our own arguments, delegate everything else to stdinargs local item option_action='' option_tool='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -99,7 +99,7 @@ function echo_url_coder() ( '--encode' | '--decode') option_action="${item#--}" ;; '--action='*) option_action="${item#*=}" ;; '--tool='*) option_tool="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -110,12 +110,12 @@ function echo_url_coder() ( done # ensure tool - if test "$option_tool" = '?'; then + if [[ $option_tool == '?' ]]; then option_tool="$(choose --required 'Which tool to use?' -- "${all_tools[@]}")" if __command_missing -- "$option_tool"; then get-installer --first-success --invoke --quiet -- "$option_tool" fi - elif test -z "$option_tool"; then + elif [[ -z $option_tool ]]; then local item for item in "${all_tools[@]}"; do if __command_exists -- "$item"; then @@ -126,7 +126,7 @@ function echo_url_coder() ( fi # ensure action - if test "$option_action" = '?'; then + if [[ $option_action == '?' ]]; then option_action="$(choose --required 'Which action to perform?' -- 'encode' 'decode')" elif ! [[ $option_action =~ ^(encode|decode)$ ]]; then help "You must provide a valid " @@ -138,13 +138,13 @@ function echo_url_coder() ( # recode doesn't support url encoding/decoding: https://github.com/rrthomas/recode/issues/51 # xmlstarlet is only for html/xml encoding local script - if test "$option_tool" = 'deno'; then + if [[ $option_tool == 'deno' ]]; then script="$(type -P echo-url-coder.ts)" - elif test "$option_tool" = 'node'; then + elif [[ $option_tool == 'node' ]]; then script="$(type -P echo-url-coder.js)" - # elif test "$option_tool" = 'php'; then + # elif [[ "$option_tool" = 'php' ]]; then # script="$(type -P echo-url-coder.php)" - elif test "$option_tool" = 'python3'; then + elif [[ $option_tool == 'python3' ]]; then script="$(type -P echo-url-coder.py)" else help "The tool [$option_tool] is not yet supported." @@ -157,8 +157,8 @@ function echo_url_coder() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_url_coder_test else echo_url_coder "$@" diff --git a/commands/echo-url-decode b/commands/echo-url-decode index 4cedaca7e..304fa82f7 100755 --- a/commands/echo-url-decode +++ b/commands/echo-url-decode @@ -31,8 +31,8 @@ function echo_url_decode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_url_decode_test else echo_url_decode "$@" diff --git a/commands/echo-url-encode b/commands/echo-url-encode index d4889b901..446aa0638 100755 --- a/commands/echo-url-encode +++ b/commands/echo-url-encode @@ -31,8 +31,8 @@ function echo_url_encode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_url_encode_test else echo_url_encode "$@" diff --git a/commands.beta/echo-values b/commands/echo-values similarity index 75% rename from commands.beta/echo-values rename to commands/echo-values index b94149425..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] @@ -39,7 +40,7 @@ function echo_values() ( [3] = [z] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -55,6 +56,6 @@ function echo_values() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_values "$@" fi diff --git a/commands/echo-verbose b/commands/echo-verbose index 7ef55e397..900172a99 100755 --- a/commands/echo-verbose +++ b/commands/echo-verbose @@ -33,7 +33,7 @@ function echo_verbose() ( [2] = [c] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -56,6 +56,6 @@ function echo_verbose() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then echo_verbose "$@" fi diff --git a/commands/echo-wait b/commands/echo-wait index 463f06e41..c0535edf5 100755 --- a/commands/echo-wait +++ b/commands/echo-wait @@ -112,7 +112,7 @@ function echo_wait() ( [...] A file to write to. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -120,7 +120,7 @@ function echo_wait() ( # process local item option_sponge='' option_sudo='no' option_files=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -142,24 +142,24 @@ function echo_wait() ( done # install sponge if needed - if test "$option_sponge" = 'yes'; then + if [[ $option_sponge == 'yes' ]]; then setup-util-moreutils --quiet # sponge fi - if test "$option_sponge" = 'no' || __command_missing -- sponge; then + if [[ $option_sponge == 'no' ]] || __command_missing -- sponge; then option_sponge='no' else option_sponge='yes' fi # check sudo - if test "$option_sudo" = 'yes' -a "${#option_files[@]}" -eq 0; then + if [[ $option_sudo == 'yes' && ${#option_files[@]} -eq 0 ]]; then help " requires s" fi # adapt commands for sudo and files, and check sudo local sponge_output_command=() tee_output_command=() - if test "${#option_files[@]}" -ne 0; then - if test "$option_sudo" = 'yes'; then + if [[ ${#option_files[@]} -ne 0 ]]; then + if [[ $option_sudo == 'yes' ]]; then sponge_output_command+=( sudo-helper --reason="Your sudo/root/login password is required to update the files: ${option_files[*]}" @@ -171,7 +171,7 @@ function echo_wait() ( -- ) fi - if test "$option_sponge" = 'yes'; then + if [[ $option_sponge == 'yes' ]]; then sponge_output_command+=( "$(type -P sponge)" "${option_files[@]}" @@ -187,8 +187,8 @@ function echo_wait() ( # ===================================== # Action - if test "$option_sponge" = 'yes'; then - if test "${#option_files[@]}" -ne 0; then # bash v3 compat + if [[ $option_sponge == 'yes' ]]; then + if [[ ${#option_files[@]} -ne 0 ]]; then # bash v3 compat "${sponge_output_command[@]}" /dev/null # ^ /dev/null to be consistent with sponge else @@ -212,8 +212,8 @@ function echo_wait() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_wait_test else echo_wait "$@" diff --git a/commands/echo-wrap b/commands/echo-wrap index 32db56f56..60f7437cd 100755 --- a/commands/echo-wrap +++ b/commands/echo-wrap @@ -75,7 +75,7 @@ function echo_wrap() ( QUIRKS: Tabs are converted into 4 spaces. Broken spacing (spacing at the end or start of a segment) is trimmed. Spacing is preserved at the start and end of input lines. Backticks are trimmed as they are used internally as a delimiter. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -83,13 +83,13 @@ function echo_wrap() ( # process local item option_columns='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--columns='* | '--width='*) option_columns="${item#*=}" ;; - # forward to stdinargs, however support mixing and matching of our options, with stdinarg options + # forward to stdinargs, however support mixing and matching of our options, with stdinargs options '--') option_args+=("$item" "$@") shift $# @@ -101,14 +101,14 @@ function echo_wrap() ( done # try to detect width, otherwise default to 0 - if test -z "$option_columns"; then + if [[ -z $option_columns ]]; then local terminal_size=() mapfile -t terminal_size < <(get-terminal-lines-and-columns || :) - if test "${#terminal_size[@]}" -eq 2; then + if [[ ${#terminal_size[@]} -eq 2 ]]; then option_columns="${terminal_size[1]}" fi fi - if test -z "$option_columns"; then + if [[ -z $option_columns ]]; then option_columns=0 fi @@ -124,7 +124,7 @@ function echo_wrap() ( function on_inline { buffer+="$1" } - if test "$option_columns" -le 0; then + if [[ $option_columns -le 0 ]]; then function on_line { buffer+="$1" } @@ -145,8 +145,8 @@ function echo_wrap() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then echo_wrap_test else echo_wrap "$@" diff --git a/commands/echo-wrap.awk b/commands/echo-wrap.awk index 486670c53..090f032e0 100755 --- a/commands/echo-wrap.awk +++ b/commands/echo-wrap.awk @@ -1,5 +1,5 @@ # https://stackoverflow.com/a/72278299/130638 -# Signficantly modified to: +# Significantly modified to: # - include all the ANSI escape codes that Dorothy is aware of # - support tabs # don't use l as that results in: awk: towc: multibyte conversion failure on diff --git a/commands/edit b/commands/edit index 019d9d838..ecc675488 100755 --- a/commands/edit +++ b/commands/edit @@ -38,7 +38,7 @@ function edit_() ( --gui If enabled, enforce a gui editor. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -46,7 +46,7 @@ function edit_() ( # process, @todo rewrite with option_ prefix local item dry='no' prompt='yes' wait='no' only_editor='no' sudo='no' terminal='' gui='' args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -86,7 +86,7 @@ function edit_() ( done # check that terminal and gui are not both no - if test "$terminal" = 'no' -a "$gui" = 'no'; then + if [[ $terminal == 'no' && $gui == 'no' ]]; then help "either [--terminal] or [--gui] must be enabled." fi @@ -100,10 +100,10 @@ function edit_() ( load_dorothy_config 'editors.bash' # check config - if test "${#TERMINAL_EDITORS[@]}" -eq 0; then + if [[ ${#TERMINAL_EDITORS[@]} -eq 0 ]]; then echo-style --warning="TERMINAL_EDITORS was undefined" >/dev/stderr fi - if test "${#GUI_EDITORS[@]}" -eq 0; then + if [[ ${#GUI_EDITORS[@]} -eq 0 ]]; then echo-style --warning="GUI_EDITORS was undefined" >/dev/stderr fi @@ -112,7 +112,7 @@ function edit_() ( # determine the editors local editors=() - if test -z "$terminal" -a -z "$gui"; then + if [[ -z $terminal && -z $gui ]]; then # no terminal or gui preference, determine sensible defaults if is-vscode; then # if running within vscode, add vscode as first preference @@ -129,11 +129,11 @@ function edit_() ( terminal='yes' fi fi - if test "$gui" = 'yes'; then + if [[ $gui == 'yes' ]]; then # add the gui editors if desired editors+=("${GUI_EDITORS[@]}") fi - if test "$terminal" = 'yes'; then + if [[ $terminal == 'yes' ]]; then # add the terminal editors if desired editors+=("${TERMINAL_EDITORS[@]}") fi @@ -143,7 +143,7 @@ function edit_() ( for needle in "${editors[@]}"; do array=() command="${needle%% *}" - if test -n "$command"; then + if [[ -n $command ]]; then if __command_exists -- "$command"; then mapfile -t array < <(echo-split ' ' -- "$needle") @@ -153,7 +153,7 @@ function edit_() ( # handle fancy editors fancifully case "$command" in 'code') - if test "$prompt" = 'no' -o "$sudo" = 'yes'; then + if [[ $prompt == 'no' || $sudo == 'yes' ]]; then continue fi if is-kde; then @@ -163,7 +163,7 @@ function edit_() ( fi array+=('--password-store=gnome-libsecret') # kwallet5 fi - if test "$wait" = 'yes'; then + if [[ $wait == 'yes' ]]; then array+=('-w') fi if is-wsl; then @@ -174,18 +174,18 @@ function edit_() ( fi ;; 'atom' | 'subl' | 'zed') - if test "$prompt" = 'no' -o "$sudo" = 'yes'; then + if [[ $prompt == 'no' || $sudo == 'yes' ]]; then continue fi - if test "$sudo" = 'yes'; then + if [[ $sudo == 'yes' ]]; then continue fi - if test "$wait" = 'yes'; then + if [[ $wait == 'yes' ]]; then array+=('-w') fi ;; 'emacs') - if test "$prompt" = 'no'; then + if [[ $prompt == 'no' ]]; then continue fi array+=('--no-window-system') @@ -194,13 +194,13 @@ function edit_() ( # we have a suitable editor, leave the search break - elif test "$gui" = 'yes' && is-needle --needle="$needle" -- "${GUI_EDITORS[@]}" && get-app --quiet -- "$command"; then + elif [[ $gui == 'yes' ]] && is-needle --needle="$needle" -- "${GUI_EDITORS[@]}" && get-app --quiet -- "$command"; then # ignore gui editors that don't support sudo - if test "$sudo" = 'yes' && [[ $command =~ ^(TextEdit)$ ]]; then + if [[ $sudo == 'yes' && $command =~ ^(TextEdit)$ ]]; then continue fi # apps require prompts - if test "$prompt" = 'yes'; then + if [[ $prompt == 'yes' ]]; then mapfile -t array < <(echo-split ' ' -- "$needle") array=('open' '-a' "${array[@]}") # we have a suitable editor, leave the search @@ -212,10 +212,10 @@ function edit_() ( done # check editor - if test "${#array[@]}" -eq 0; then - if test "$dry" = 'yes' && is-ci; then + if [[ ${#array[@]} -eq 0 ]]; then + if [[ $dry == 'yes' ]] && is-ci; then : # if running on ci, and if dry, then suppress error - else + elif [[ ${#array[@]} -ne 0 ]]; then echo-style --error="$0: None of the configured editors were available:" >/dev/stderr echo-verbose "${array[@]}" >/dev/stderr fi @@ -223,14 +223,14 @@ function edit_() ( fi # only output the editor command - if test "$only_editor" != 'no'; then + if [[ $only_editor != 'no' ]]; then __print_lines "${array[0]}" return fi # generate the entire command local cmd=() - if test "$sudo" = 'yes'; then + if [[ $sudo == 'yes' ]]; then cmd+=( 'sudo-helper' '--inherit' @@ -238,12 +238,12 @@ function edit_() ( ) fi cmd+=("${array[@]}") - if test "${#args[@]}" -ne 0; then + if [[ ${#args[@]} -ne 0 ]]; then cmd+=("${args[@]}") fi # invoke - if test "$dry" = 'yes'; then + if [[ $dry == 'yes' ]]; then # print a single line, with values space separated, such that [setup-git] works properly with [code --new-window] __print_lines "${cmd[*]}" else @@ -252,6 +252,6 @@ function edit_() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then edit_ "$@" fi diff --git a/commands/eval-capture b/commands/eval-capture index febb590e9..d21aaf43e 100755 --- a/commands/eval-capture +++ b/commands/eval-capture @@ -4,7 +4,7 @@ source "$DOROTHY/sources/bash.bash" # disable strict mode so that we can test it specifically set +eu -if test "$*" = '--test'; then +if [[ $* == '--test' ]]; then expected="$( cat <<-EOF root start @@ -28,7 +28,7 @@ if test "$*" = '--test'; then eval-tester --name='as expected' --status='1' --stdout="$expected" \ -- eval-capture -- exit $? -elif test "$*" = '--'; then +elif [[ $* == '--' ]]; then : # all good, run else echo-error 'eval-capture only exists for testing, you actually want to use eval_capture which is provided by sourcing bash.bash' @@ -51,7 +51,7 @@ function testing_middle { __print_lines 'middle start' eval_capture --statusvar=middle_status -- testing_failure trap -p | grep --regexp='ERR$' || : # use grep as CI has SIGPIPE traps - if test "$middle_status" -eq 0; then + if [[ $middle_status -eq 0 ]]; then __print_lines "middle ok $middle_status" else __print_lines "middle failure $middle_status" @@ -63,7 +63,7 @@ function testing_root { __print_lines 'root start' eval_capture --statusvar=root_status -- testing_middle trap -p | grep --regexp='ERR$' || : # use grep as CI has SIGPIPE traps - if test "$root_status" -eq 0; then + if [[ $root_status -eq 0 ]]; then __print_lines "root ok $root_status" else __print_lines "root failure $root_status" @@ -74,7 +74,7 @@ function testing_root { core_status= eval_capture --statusvar=core_status -- testing_root trap -p | grep --regexp='ERR$' || : # use grep as CI has SIGPIPE traps -if test "$core_status" -eq 0; then +if [[ $core_status -eq 0 ]]; then __print_lines "core ok $core_status" else __print_lines "core failure $core_status" diff --git a/commands/eval-helper b/commands/eval-helper index 0434c90bd..8941655ba 100755 --- a/commands/eval-helper +++ b/commands/eval-helper @@ -54,16 +54,16 @@ function eval_helper() ( Unless [--no-quiet] is used, then stdout+stderr will be merged into stderr. Messages are output to TTY if available, otherwise stderr. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_quiet option_cmd=() option_wrap='yes' option_trim='no' option_confirm='no' option_shapeshifter='no' option_pending='' option_success='' option_failure='' option_command_string='' option_terminal_device_file='' + local item option_quiet option_cmd=() option_wrap='yes' option_trim='no' option_confirm='no' option_shapeshifter='' option_pending='' option_success='' option_failure='' option_command_string='' option_terminal_device_file='' option_quiet="$(get-terminal-quiet-support || :)" - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -101,58 +101,69 @@ function eval_helper() ( esac done + # ensure items that write to TTY are cleared correctly + if [[ -z $option_shapeshifter ]]; then + if [[ ${option_cmd[0]} == 'sudo-helper' ]]; then + option_shapeshifter='yes' + else + option_shapeshifter='no' + fi + fi + # ===================================== # Action # terminal - local terminal_device_file="$option_terminal_device_file" - if test -z "$terminal_device_file"; then + local terminal_device_file="$option_terminal_device_file" messages_device_file + if [[ -z $terminal_device_file ]]; then terminal_device_file="$(get-terminal-device-file)" fi + messages_device_file="$terminal_device_file" # element local element_open='' - if test -z "$option_command_string"; then + if [[ -z $option_command_string ]]; then option_command_string="$(echo-escape-command -- "${option_cmd[@]}")" fi - if test "$option_wrap" = 'yes'; then + if [[ $option_wrap == 'yes' ]]; then element_open="$(echo-style --element="$option_command_string")" else element_open="$(echo-style --code="$option_command_string")" fi # confirm - if test "$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" + if [[ $option_confirm == 'yes' ]] && ! confirm --positive --ppid=$$ -- 'Confirm execution of the command that is below:' "$element_open"; then + echo-style --notice1='Skipped execution of:' --code-notice1="$element_open" >"$terminal_device_file" return 0 fi + # messages + local messages='' + function flush_messages { + if [[ -n $messages ]]; then + __print_string "$messages" >"$messages_device_file" + messages='' + fi + } + # headers local header='' - if test -n "$option_pending"; then + if [[ -n $option_pending ]]; then header+="$option_pending"$'\n' fi - if test "$option_wrap" = 'yes'; then + if [[ $option_wrap == 'yes' ]]; then header+="$element_open"$'\n' fi - if test -n "$header"; then - __print_string "$header" >"$terminal_device_file" + if [[ -n $header ]]; then + messages+="$header" + flush_messages fi - # output - local output='' - function flush_output { - if test -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 test "${INSIDE_REVOLVING_DOOR-}" = 'yes' || test "$option_quiet" = 'no'; then + if [[ ${INSIDE_REVOLVING_DOOR-} == 'yes' || $option_quiet == 'no' ]]; then # body - if test "$option_trim" = 'yes'; then + if [[ $option_trim == 'yes' ]]; then eval_capture --statusvar=cmd_status -- "${option_cmd[@]}" \ > >(echo-trim-padding --stdin) \ 2> >(echo-trim-padding --stdin >/dev/stderr) @@ -161,28 +172,28 @@ function eval_helper() ( fi # add close - if test "$option_wrap" = 'yes'; then - output+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' + if [[ $option_wrap == 'yes' ]]; then + messages+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' fi # add success or failure - if test "$cmd_status" -eq 0; then - if test -n "$option_success"; then - output+="$option_success"$'\n' + if [[ $cmd_status -eq 0 ]]; then + if [[ -n $option_success ]]; then + messages+="$option_success"$'\n' fi else - if test -n "$option_failure"; then - output+="$option_failure"$'\n' + if [[ -n $option_failure ]]; then + messages+="$option_failure"$'\n' fi fi # output - flush_output + flush_messages else # not inside a revolving door, and not in verbose mode local terminal_size terminal_columns=0 body_file mapfile -t terminal_size < <(get-terminal-lines-and-columns || :) - if test "${#terminal_size[@]}" -eq 2; then + if [[ ${#terminal_size[@]} -eq 2 ]]; then terminal_columns="${terminal_size[1]}" fi body_file="$(mktemp)" @@ -192,7 +203,7 @@ function eval_helper() ( # https://github.com/bevry/dorothy/discussions/151 local INSIDE_REVOLVING_DOOR_original="${INSIDE_REVOLVING_DOOR:-"no"}" export INSIDE_REVOLVING_DOOR='yes' # use export, as env doesn't work when option_cmd[0] was a function - if test "$option_shapeshifter" = 'yes'; then + if [[ $option_shapeshifter == 'yes' ]]; then # this is used if the command writes to TTY # in which case echo-revolving-door fails to clear echo-style --no-trail --alternative-screen-buffer --="$header" >"$terminal_device_file" # redo header inside alt tty while its active @@ -201,9 +212,9 @@ function eval_helper() ( 2> >(tee -a "$body_file" >/dev/stderr) echo-style --no-trail --default-screen-buffer >"$terminal_device_file" else + # direct stdout and stderr to terminal_device_file, as they will be truncated and will be outputting clear ansi codes, which will confuse anything that is expecting legit TTY output eval_capture --statusvar=cmd_status -- "${option_cmd[@]}" \ - > >(tee -a "$body_file" | echo-revolving-door --columns="$terminal_columns") \ - 2> >(tee -a "$body_file" | echo-revolving-door --columns="$terminal_columns" >/dev/stderr) + &> >(tee -a "$body_file" | echo-revolving-door --columns="$terminal_columns" >"$terminal_device_file") # we cannot detect shapeshifting after the fact, as it occurs in the TTY, not stdout, nor stderr fi export INSIDE_REVOLVING_DOOR="$INSIDE_REVOLVING_DOOR_original" @@ -212,18 +223,18 @@ function eval_helper() ( echo-clear-lines --here-string <<<"$header" >"$terminal_device_file" # generate output - if test "$option_quiet" = 'no' -o "$cmd_status" -ne 0; then + if [[ $option_quiet == 'no' || $cmd_status -ne 0 ]]; then # pending message, command output and status, and success or failure message # add pending - if test -n "$option_pending"; then - output+="$option_pending"$'\n' + if [[ -n $option_pending ]]; then + messages+="$option_pending"$'\n' fi # add body local body - if test -s "$body_file"; then - if test "$option_trim" = 'yes'; then + if [[ -s $body_file ]]; then + if [[ $option_trim == 'yes' ]]; then body="$(echo-trim-padding --stdin <"$body_file")" else body="$(cat "$body_file")" @@ -231,46 +242,46 @@ function eval_helper() ( else body='' fi - if test "$option_wrap" = 'no'; then - if test -n "$body"; then - flush_output + if [[ $option_wrap == 'no' ]]; then + if [[ -n $body ]]; then + flush_messages __print_lines "$body" >/dev/stderr fi else - if test -n "$body"; then - output+="$(echo-style --element="$option_command_string")"$'\n' - flush_output + if [[ -n $body ]]; then + messages+="$(echo-style --element="$option_command_string")"$'\n' + flush_messages __print_lines "$body" >/dev/stderr - output+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' + messages+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' else - output+="$(echo-style --element="$option_command_string" --/fragment='' --status="$cmd_status")"$'\n' + messages+="$(echo-style --element="$option_command_string" --/fragment='' --status="$cmd_status")"$'\n' fi fi # add success or failure - if test "$cmd_status" -eq 0; then - if test -n "$option_success"; then - output+="$option_success"$'\n' + if [[ $cmd_status -eq 0 ]]; then + if [[ -n $option_success ]]; then + messages+="$option_success"$'\n' fi else - if test -n "$option_failure"; then - output+="$option_failure"$'\n' + if [[ -n $option_failure ]]; then + messages+="$option_failure"$'\n' fi fi - elif test -z "$option_quiet"; then + elif [[ -z $option_quiet ]]; then # pending message, command status, and success message # add pending - if test -n "$option_pending"; then - output+="$option_pending"$'\n' + if [[ -n $option_pending ]]; then + messages+="$option_pending"$'\n' fi # truncate body if wrapping - if test "$option_wrap" = 'yes'; then + if [[ $option_wrap == 'yes' ]]; then local body - if test -s "$body_file"; then - if test "$option_trim" = 'yes'; then + if [[ -s $body_file ]]; then + if [[ $option_trim == 'yes' ]]; then body="$(echo-trim-padding --stdin <"$body_file")" else body="$(cat "$body_file")" @@ -278,30 +289,30 @@ function eval_helper() ( else body='' fi - if test -n "$body"; then + if [[ -n $body ]]; then # self close as we are truncating - output+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' + messages+="$(echo-style --/element="$option_command_string" --status="$cmd_status")"$'\n' else # close fragment as there is no data - output+="$(echo-style --element="$option_command_string" --/fragment --status="$cmd_status")"$'\n' + messages+="$(echo-style --element="$option_command_string" --/fragment --status="$cmd_status")"$'\n' fi fi # add success - if test -n "$option_success"; then - output+="$option_success"$'\n' + if [[ -n $option_success ]]; then + messages+="$option_success"$'\n' fi - elif test "$option_quiet" = 'yes'; then + elif [[ $option_quiet == 'yes' ]]; then # only success message # add success - if test -n "$option_success"; then - output+="$option_success"$'\n' + if [[ -n $option_success ]]; then + messages+="$option_success"$'\n' fi fi # output - flush_output + flush_messages fi # done @@ -309,6 +320,6 @@ function eval_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then eval_helper "$@" fi diff --git a/commands/eval-no-color b/commands/eval-no-color index 1a4887312..702c3a0b5 100755 --- a/commands/eval-no-color +++ b/commands/eval-no-color @@ -14,7 +14,7 @@ function eval_no_color() ( USAGE: eval-no-color [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function eval_no_color() ( # process local item option_cmd=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,6 +57,6 @@ function eval_no_color() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then eval_no_color "$@" fi diff --git a/commands/eval-on-empty-stdin b/commands/eval-on-empty-stdin new file mode 100755 index 000000000..ba94aa116 --- /dev/null +++ b/commands/eval-on-empty-stdin @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +function eval_on_empty_stdin() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Execute the if stdin is empty; stdin content will continue to be directed to stdout. + Companion to [eval-on-not-empty-stdin]. + + USAGE: + eval-on-empty-stdin [--] ... + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_cmd=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--') + option_cmd+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) + option_cmd+=("$item" "$@") + shift $# + break + ;; + esac + done + + # checks + if [[ ${#option_cmd[@]} -eq 0 ]]; then + help "No was provided." + fi + + # ===================================== + # Action + + # test cases: + # echo sup | ( eval-on-empty-stdin echo no ) + # true | ( eval-on-empty-stdin echo no ) + # true | ( eval-on-empty-stdin cat ) + + # doesn't work: read -r + # doesn't work: [[ -t 0 ]] + # doesn't work: read -t 0 -N 0 + + # works: + if __command_exists -- ifne; then + ifne -n "${option_cmd[@]}" + return + else + # grep is essential for dorothy, so this is a safe fallback + if ! grep --regexp='^'; then + "${option_cmd[@]}" # eval + return + fi + return 0 + fi +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + eval_on_empty_stdin "$@" +fi diff --git a/commands/eval-on-not-empty-stdin b/commands/eval-on-not-empty-stdin new file mode 100755 index 000000000..f84f8cd27 --- /dev/null +++ b/commands/eval-on-not-empty-stdin @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +function eval_on_not_empty_stdin() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Execute the if stdin is not empty; the stdin content will still be available to whatever eventually reads it. + Companion to [eval-on-empty-stdin]. + + USAGE: + eval-on-not-empty-stdin [--] ... + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_cmd=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--') + option_cmd+=("$@") + shift $# + break + ;; + '--'*) help "An unrecognised flag was provided: $item" ;; + *) + option_cmd+=("$item" "$@") + shift $# + break + ;; + esac + done + + # checks + if [[ ${#option_cmd[@]} -eq 0 ]]; then + help "No was provided." + fi + + # ===================================== + # Action + + # test cases: + # echo-lines -- 1 2 | ( eval-on-not-empty-stdin cat ) + # true | ( eval-on-not-empty-stdin echo sup ) + + # doesn't work: grep --regexp='^' | "${option_cmd[@]}" # eval + + # doesn't work: + # if grep --quiet --regexp='^'; then + # "${option_cmd[@]}" # eval + # return + # fi + # return 0 + + # works: + setup-util-moreutils --quiet # ifne + ifne "${option_cmd[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + eval_on_not_empty_stdin "$@" +fi diff --git a/commands/eval-tester b/commands/eval-tester index 0f94b476b..91b35d64d 100755 --- a/commands/eval-tester +++ b/commands/eval-tester @@ -23,7 +23,7 @@ function eval_tester() ( --bash= Used to ensure that the command is invoked through a specific bash binary. Defaults to the env var EVAL_TESTER_BASH. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function eval_tester() ( # process local item option_cmd=() ignore_option=$'\e' option_name='' option_status='0' option_stdout='' option_stderr='' option_debug='no' option_bash="${EVAL_TESTER_BASH-}" - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -62,16 +62,16 @@ function eval_tester() ( # and debug-bash not possible local cmd_path cmd_path="$(type -P "${option_cmd[0]}" 2>/dev/null || :)" - if test -n "$cmd_path" && [[ $cmd_path == "$DOROTHY"* ]]; then + if [[ -n $cmd_path && $cmd_path == "$DOROTHY"* ]]; then option_cmd[0]="$cmd_path" - if test "$option_debug" = 'yes'; then + if [[ $option_debug == 'yes' ]]; then option_cmd=( 'debug-bash' "--bash=$option_bash" '--' "${option_cmd[@]}" ) - elif test -n "$option_bash"; then + elif [[ -n $option_bash ]]; then option_cmd=( "$option_bash" "${option_cmd[@]}" @@ -85,7 +85,7 @@ function eval_tester() ( # run the command local args=() cmd_status stdout stderr cmd_string fail='no' title='' using_tty_stderr_fallback='no' cmd_string="$(echo-escape-command -- "${option_cmd[@]}")" - if test -n "$option_name"; then + if [[ -n $option_name ]]; then title="$option_name: $cmd_string" else title="$cmd_string" @@ -93,17 +93,17 @@ function eval_tester() ( if ! get-terminal-tty-support --quiet; then using_tty_stderr_fallback='yes' fi - if test -n "$option_status" -a "$option_status" != "$ignore_option"; then + if [[ -n $option_status && $option_status != "$ignore_option" ]]; then args+=('--statusvar=cmd_status') fi - if test "$option_stdout" != "$ignore_option"; then + if [[ $option_stdout != "$ignore_option" ]]; then args+=('--stdoutvar=stdout') else args+=('--no-stdout') fi - if test "$option_debug" = 'yes' -o "$using_tty_stderr_fallback" = 'yes'; then - : # allow stdderr to go directly to stderr - elif test "$option_stderr" != "$ignore_option"; then + if [[ $option_debug == 'yes' || $using_tty_stderr_fallback == 'yes' ]]; then + : # allow stderr to go directly to stderr + elif [[ $option_stderr != "$ignore_option" ]]; then args+=('--stderrvar=stderr') else args+=('--no-stderr') @@ -111,9 +111,9 @@ function eval_tester() ( echo-style --h3="$title" eval_capture "${args[@]}" -- "${option_cmd[@]}" - # test exit code - if test -n "$option_status" -a "$option_status" != "$ignore_option"; then - if test "$option_status" -ne "$cmd_status"; then + # check exit code + if [[ -n $option_status && $option_status != "$ignore_option" ]]; then + if [[ $option_status -ne $cmd_status ]]; then fail='yes' echo-style --red="Actual Exit Code [$cmd_status] != Expected Exit Code [$option_status]" else @@ -121,9 +121,9 @@ function eval_tester() ( fi fi - # test stdout - if test "$option_stdout" != "$ignore_option"; then - if test "$option_stdout" != "$stdout"; then + # check stdout + if [[ $option_stdout != "$ignore_option" ]]; then + if [[ $option_stdout != "$stdout" ]]; then fail='yes' echo-style --red="Actual Stdout != Expected Stdout" $'\n' \ --header3='' $'\n' \ @@ -145,13 +145,13 @@ function eval_tester() ( fi fi - # test stderr - if test "$option_stderr" != "$ignore_option"; then - if test "$option_debug" = 'yes'; then + # check stderr + if [[ $option_stderr != "$ignore_option" ]]; then + if [[ $option_debug == 'yes' ]]; then echo-style --notice='Unable to test stderr, as debugging.' - elif test "$using_tty_stderr_fallback" = 'yes'; then + elif [[ $using_tty_stderr_fallback == 'yes' ]]; then echo-style --notice='Unable to test stderr, as it will be filled with TTY output.' - elif test "$option_stderr" != "$stderr"; then + elif [[ $option_stderr != "$stderr" ]]; then fail='yes' echo-style --red="Actual Stderr != Expected Stderr" $'\n' \ --header3='' $'\n' \ @@ -166,7 +166,7 @@ function eval_tester() ( fi # pass or fail - if test "$fail" = 'yes'; then + if [[ $fail == 'yes' ]]; then echo-style --e3="$title" $'\n' return 1 else @@ -175,6 +175,6 @@ function eval_tester() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then eval_tester "$@" fi diff --git a/commands/expand-path b/commands/expand-path index 151a3c4d0..5219ed113 100755 --- a/commands/expand-path +++ b/commands/expand-path @@ -33,7 +33,7 @@ function expand_path() ( USAGE: expand-path [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -41,7 +41,7 @@ function expand_path() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -58,7 +58,7 @@ function expand_path() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -67,7 +67,7 @@ function expand_path() ( # using zsh then bash, works around macos which has old bash, but new zsh # so this is needed so setup-paths-commands can do its thing - if test "$IS_BASH_VERSION_OUTDATED" = 'no' || ! is-globstar -- "$*"; then + if [[ $IS_BASH_VERSION_OUTDATED == 'no' ]] || ! is-globstar -- "$*"; then for path in "${option_paths[@]}"; do eval __print_lines "$(echo-escape-spaces -- "$path")" | echo-trim-zero-length --stdin done @@ -80,8 +80,8 @@ function expand_path() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then expand_path_test else expand_path "$@" diff --git a/commands/fetch b/commands/fetch index db0d067f2..2b25ae517 100755 --- a/commands/fetch +++ b/commands/fetch @@ -28,7 +28,7 @@ function fetch_() ( QUIRKS: If [curl] was required, but not found, it will be installed automatically. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -36,7 +36,7 @@ function fetch_() ( # process local item option_auth_token='' option_content_type='' option_body='' option_url='' option_status='no' option_ok='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,13 +44,13 @@ function fetch_() ( '--auth-token='*) option_auth_token="${item#*=}" ;; '--bot-token='*) option_auth_token="${item#*=}" - if test -n "$option_auth_token"; then + if [[ -n $option_auth_token ]]; then option_auth_token="Bot $option_auth_token" fi ;; '--bearer-token='*) option_auth_token="${item#*=}" - if test -n "$option_auth_token"; then + if [[ -n $option_auth_token ]]; then option_auth_token="Bearer $option_auth_token" fi ;; @@ -65,7 +65,7 @@ function fetch_() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url="$item" else help "An unrecognised argument was provided: $item" @@ -75,7 +75,7 @@ function fetch_() ( done # check - if test -z "$option_url"; then + if [[ -z $option_url ]]; then help "No URL was provided" fi @@ -99,19 +99,19 @@ function fetch_() ( # -S, --show-error When used with -s, --silent, it makes curl show an error message if it fails. function do_curl { local options=("$@") - if test -n "$option_auth_token"; then + if [[ -n $option_auth_token ]]; then options+=( '--header' "Authorization: $option_auth_token" ) fi - if test -n "$option_content_type"; then + if [[ -n $option_content_type ]]; then options+=( '--header' "Content-Type: $option_content_type" ) fi - if test -n "$option_body"; then + if [[ -n $option_body ]]; then options+=( '--data' "$option_body" @@ -124,7 +124,7 @@ function fetch_() ( } function do_wget { local options=("$@") - if test -n "$option_auth_token"; then + if [[ -n $option_auth_token ]]; then options+=( "--header=Authorization: $option_auth_token" ) @@ -149,7 +149,8 @@ function fetch_() ( # no wget equivalent local status status="$(fetch_status "$option_url")" - test "$status" -ge 200 -a "$status" -le 300 + [[ $status -ge 200 && $status -le 300 ]] + return } function fetch_contents { @@ -178,20 +179,21 @@ function fetch_() ( # Action # perform appropriate action - if test "$option_status" = 'yes'; then + if [[ $option_status == 'yes' ]]; then fetch_status - elif test "$option_ok" = 'yes'; then + elif [[ $option_ok == 'yes' ]]; then fetch_ok - elif test "$option_ok" = 'no'; then + elif [[ $option_ok == 'no' ]]; then local ok_status eval_capture --statusvar=ok_status -- fetch_ok - test "$ok_status" -ne 0 + [[ $ok_status -ne 0 ]] + return else fetch_contents fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fetch_ "$@" fi diff --git a/commands/find-directories b/commands/find-directories index 303f9b154..91332b415 100755 --- a/commands/find-directories +++ b/commands/find-directories @@ -19,7 +19,7 @@ function find_directories() ( Provide to specify which directory should be searched for files. If was not provided then the current working directory will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function find_directories() ( # options local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,7 +44,7 @@ function find_directories() ( done # adjust path - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then option_paths+=("$(pwd)") fi @@ -58,6 +58,6 @@ function find_directories() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then find_directories "$@" fi diff --git a/commands/flush-dns b/commands/flush-dns index 493718ff0..145550640 100755 --- a/commands/flush-dns +++ b/commands/flush-dns @@ -14,7 +14,7 @@ function flush_dns() ( USAGE: flush-dns EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function flush_dns() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -33,14 +33,14 @@ function flush_dns() ( done # ===================================== - # Acction + # Action echo-style --h1="Flush DNS" # https://support.apple.com/en-us/HT202516 if is-mac; then if __command_exists -- mDNSResponder; then - eval-helper --quiet \ + eval-helper --shapeshifter --quiet \ --pending="$(echo-style --bold='Restarting [mDNSResponder]...')" \ --success="$(echo-style --success='Restarted [mDNSResponder].')" \ --failure="$(echo-style --error='Failed to restart [mDNSResponder].')" \ @@ -48,7 +48,7 @@ function flush_dns() ( -- killall -HUP mDNSResponder fi if __command_exists -- dscacheutil; then - eval-helper --quiet \ + eval-helper --shapeshifter --quiet \ --pending="$(echo-style --bold='Flushing [dscacheutil]...')" \ --success="$(echo-style --success='Flushed [dscacheutil].')" \ --failure="$(echo-style --error='Failed to flush [dscacheutil].')" \ @@ -56,7 +56,7 @@ function flush_dns() ( -- dscacheutil -flushcache fi if __command_exists -- discoveryutil; then - eval-helper --quiet \ + eval-helper --shapeshifter --quiet \ --pending="$(echo-style --bold='Flushing [discoveryutil]...')" \ --success="$(echo-style --success='Flushed [discoveryutil].')" \ --failure="$(echo-style --error='Failed to flush [discoveryutil].')" \ @@ -64,7 +64,7 @@ function flush_dns() ( -- discoveryutil mdnsflushcache fi elif __command_exists -- resolvectl; then - eval-helper --quiet \ + eval-helper --shapeshifter --quiet \ --pending="$(echo-style --bold='Flushing DNS cache via [Systemd]...')" \ --success="$(echo-style --success='Flushed DNS cache via [Systemd]')" \ --failure="$(echo-style --error='Failed to flush DNS cache via [Systemd].' ' ' --notice='If using an alternative DNS service, then this is expected.')" \ @@ -79,6 +79,6 @@ function flush_dns() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then flush_dns "$@" fi diff --git a/commands/fs-absolute b/commands/fs-absolute index fc81b2d87..a8d6333b1 100755 --- a/commands/fs-absolute +++ b/commands/fs-absolute @@ -15,11 +15,11 @@ function fs_absolute() ( fs-absolute [...options] [--] ... OPTIONS: - --sudo - If specified, use sudo on filesystem interactions. + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. --user= --group= - If specified use this user and/or group for filesystem interactions. + Forwarded to [sudo-helper]. EXAMPLES: fs-absolute -- .. @@ -30,80 +30,47 @@ function fs_absolute() ( QUIRKS: Use [fs-realpath] if you want symlinks resolved. EOF - if test "$#" -ne 0; then + 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 test "$#" -ne 0; do + local item option_inputs=() option_sudo='13' 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")" + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" ;; '--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 test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # Act - # call this again, but inside sudo - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -n "$option_group"; then - sudo-helper --inherit --user="$option_user" --group="$option_group" \ - -- fs-absolute -- "${option_paths[@]}" - return - fi - - local path filename - for path in "${option_paths[@]}"; do - # don't use [pwd -P] as -P resolves symlinks - # and resolving symlinks is what [fs-realpath] is for - filename="$(basename "$path")" - if test "$filename" = '/'; then - # handles root - __print_lines '/' - elif test "$filename" = '..'; then - # handles parent - ( - cd "$(dirname "$path")/.." - pwd - ) - elif test "$filename" = '.'; then - # handles cwd - ( - cd "$(dirname "$path")" - pwd - ) - else - # handles files and directories - ( - cd "$(dirname "$path")" - __print_lines "$(pwd)/$filename" - ) - fi - done + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- fs-absolute.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_absolute "$@" fi diff --git a/commands/fs-absolute.bash b/commands/fs-absolute.bash new file mode 100755 index 000000000..4025ae057 --- /dev/null +++ b/commands/fs-absolute.bash @@ -0,0 +1,53 @@ +#!/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 + path="$1" + shift + + # Affirm accessibility + if [[ ! -e $path ]]; then + # discern if inaccessible + is-accessible.bash -- "$path" || exit $? + # if missing, then it only matters if the parent is missing, which the following will deal with + fi + + # It is accessible and exists + # don't use [pwd -P] as -P resolves symlinks + # and resolving symlinks is what [fs-realpath] is for + filename="$(basename "$path")" + if [[ $filename == '/' ]]; then + # handles root + printf '%s\n' '/' + elif [[ $filename == '..' ]]; then + # handles parent + ( + cd "$(dirname "$path")/.." + pwd + ) + elif [[ $filename == '.' ]]; then + # handles cwd + ( + cd "$(dirname "$path")" + pwd + ) + else + # handles files and directories + ( + cd "$(dirname "$path")" + printf '%s\n' "$(pwd)/$filename" + ) + fi + + # shift to next argument + shift +done +exit 0 diff --git a/commands/fs-bytes b/commands/fs-bytes index 6f0da6b69..42708620a 100755 --- a/commands/fs-bytes +++ b/commands/fs-bytes @@ -24,7 +24,7 @@ function fs_bytes() ( USAGE: fs-bytes [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function fs_bytes() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function fs_bytes() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -71,8 +71,8 @@ function fs_bytes() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then fs_bytes_test else fs_bytes "$@" diff --git a/commands/fs-dequarantine b/commands/fs-dequarantine index cfddaf183..3a7162624 100755 --- a/commands/fs-dequarantine +++ b/commands/fs-dequarantine @@ -9,73 +9,71 @@ 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 [--] ... + fs-dequarantine [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were not quarantined or successfully dequarantined + [1] if any s were quarantined but could not be dequarantined + [2] if any s were not accessible + [22] if empty arguments are provided EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_paths=() - while test "$#" -ne 0; do + local item option_inputs=() option_sudo='13' option_user='' option_group='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; - '--path='*) option_paths+=("${item#*=}") ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" + ;; + '--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 test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # Action - if ! is-mac || __command_missing -- xattr; then + # use [/usr/bin/xattr] as homebrew could install an unrelated xattr + if ! is-mac || [[ ! -x /usr/bin/xattr ]]; then return 0 # not needed fi - function disable_quarantine_on_path { - # https://apple.stackexchange.com/a/436677/15131 - # note that the -r option doesn't exist, will return [option -r not recognized] on Ventura and Sonoma - # cannot just -d directly, as will get a [No such xattr: com.apple.quarantine] error, so check for it first, this induces no errors - local path="$1" - if test -r "$path"; then - if xattr -l "$path" | grep --quiet --fixed-strings --regexp='com.apple.quarantine'; then - xattr -d com.apple.quarantine "$path" >/dev/stderr - return - fi - elif sudo-helper -- test -r "$path"; then - if sudo-helper -- xattr -l "$path" | grep --quiet --fixed-strings --regexp='com.apple.quarantine'; then - sudo-helper -- xattr -d com.apple.quarantine "$path" >/dev/stderr - return - fi - fi - return 0 - } - - local path - for path in "${option_paths[@]}"; do - disable_quarantine_on_path "$path" - done + # invoke with sudo escalation enabled by default + sudo-helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- fs-dequarantine.bash -- "${option_inputs[@]}" + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_dequarantine "$@" fi diff --git a/commands/fs-dequarantine.bash b/commands/fs-dequarantine.bash new file mode 100755 index 000000000..c01a5a495 --- /dev/null +++ b/commands/fs-dequarantine.bash @@ -0,0 +1,41 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -r $path ]]; then + # does exist: is readable + + # if it is quarantined, remove the quarantine attribute + # https://apple.stackexchange.com/a/436677/15131 + # note that the -r option doesn't exist, will return [option -r not recognized] on Ventura and Sonoma + # cannot just -d directly, as will get a [No such xattr: com.apple.quarantine] error, so check for it first, this induces no errors + if /usr/bin/xattr -l "$path" | grep --quiet --fixed-strings --regexp='com.apple.quarantine'; then + /usr/bin/xattr -d com.apple.quarantine "$path" >/dev/stderr || exit $? + fi + continue + elif [[ -e $path ]]; then + # does exist: is not readable + exit 13 # EACCES 13 Permission denied + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/fs-diff b/commands/fs-diff index f54201673..464ee1e75 100755 --- a/commands/fs-diff +++ b/commands/fs-diff @@ -27,7 +27,7 @@ function fs_diff() ( --after= | The new file for the comparison. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -35,7 +35,7 @@ function fs_diff() ( # process local item option_before='' option_after='' option_tool='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,23 +44,23 @@ function fs_diff() ( '--before='*) option_before="${item#*=}" ;; '--after='*) option_after="${item#*=}" ;; '--') - if test -z "$option_before"; then + if [[ -z $option_before ]]; then option_before="$1" shift fi - if test -z "$option_after"; then + if [[ -z $option_after ]]; then option_after="$1" shift fi - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then help "An unrecognised flag was provided: $*" fi ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_before"; then + if [[ -z $option_before ]]; then option_before="$item" - elif test -z "$option_after"; then + elif [[ -z $option_after ]]; then option_after="$item" else help "An unrecognised flag was provided: $item" @@ -69,17 +69,17 @@ function fs_diff() ( esac done - if test -z "$option_before" -o -z "$option_after"; then + if [[ -z $option_before || -z $option_after ]]; then help 'You must provide both and files.' fi # ensure tool - if test "$option_tool" = '?'; then + if [[ $option_tool == '?' ]]; then option_tool="$(choose --required 'Which diff tool to use?' -- "${all_tools[@]}")" if __command_missing -- "$option_tool"; then get-installer --first-success --invoke --quiet -- "$option_tool" fi - elif test -z "$option_tool"; then + elif [[ -z $option_tool ]]; then local item for item in "${all_tools[@]}"; do if __command_exists -- "$item"; then @@ -92,14 +92,17 @@ function fs_diff() ( # ===================================== # Action - if test "$option_tool" = 'delta'; then + # @todo support these: + # https://github.com/Wilfred/difftastic + + if [[ $option_tool == 'delta' ]]; then delta --paging never -s "$option_before" "$option_after" - elif test "$option_tool" = 'difft'; then + elif [[ $option_tool == 'difft' ]]; then # https://difftastic.wilfred.me.uk difft "$option_before" "$option_after" - elif test "$option_tool" = 'diff-so-fancy'; then + elif [[ $option_tool == 'diff-so-fancy' ]]; then diff -u -- "$option_before" "$option_after" | diff-so-fancy - elif test "$option_tool" = 'git'; then + elif [[ $option_tool == 'git' ]]; then git diff -- "$option_before" "$option_after" else help "The tool [$option_tool] is not yet supported." @@ -107,6 +110,6 @@ function fs_diff() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_diff "$@" fi diff --git a/commands/fs-dirname b/commands/fs-dirname index 4fdcdcbc3..5016099fb 100755 --- a/commands/fs-dirname +++ b/commands/fs-dirname @@ -31,7 +31,7 @@ function fs_dirname() ( [dirname ./symlinked-directory] outputs [.] [fs-dirname -- ./symlinked-directory] outputs [/Users] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -39,7 +39,7 @@ function fs_dirname() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -56,7 +56,7 @@ function fs_dirname() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -66,16 +66,16 @@ function fs_dirname() ( # use () (...) as we modify the pwd function do_dirname() ( local path="$1" - if test -z "$path" -o "$path" = '.'; then + if [[ -z $path || $path == '.' ]]; then # handles [fs-dirname] and [fs-dirname .] cd .. pwd - elif test -d "$path"; then + elif [[ -d $path ]]; then # handles [fs-dirname ..] and [fs-dirname ./..] cd "$path" cd .. pwd - elif test -e "$(dirname "$path")"; then + elif [[ -e "$(dirname "$path")" ]]; then # handles files, in which case we just want the directory that contains the file # not the directory that contains the directory that contains the file cd "$(dirname "$path")" @@ -85,7 +85,7 @@ function fs_dirname() ( local basename dirname basename="$(basename "$path")" dirname="${path%"/$basename"}" - if test "$dirname" != "$path"; then + if [[ $dirname != "$path" ]]; then __print_lines "$dirname" else echo-error 'Unable to determine the dirname directory for the non-existent path:' $'\n' --code="$path" @@ -101,6 +101,6 @@ function fs_dirname() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_dirname "$@" fi diff --git a/commands/fs-extension b/commands/fs-extension index aad3dd400..80118cc06 100755 --- a/commands/fs-extension +++ b/commands/fs-extension @@ -19,7 +19,7 @@ function fs_extension() ( c EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function fs_extension() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,7 +44,7 @@ function fs_extension() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -58,6 +58,6 @@ function fs_extension() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_extension "$@" fi diff --git a/commands/fs-filename b/commands/fs-filename index 43ceb3c65..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 @@ -37,7 +40,7 @@ function fs_filename() ( fs-filename --first -- .dorothy/a.b.c # => .dorothy/a EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -45,7 +48,7 @@ function fs_filename() ( # process local item option_paths=() option_first='no' option_basename='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -68,7 +71,7 @@ function fs_filename() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -79,12 +82,12 @@ function fs_filename() ( for path in "${option_paths[@]}"; do dirname="$(dirname "$path")" filename="$(basename "$path")" - if test "$option_first" = 'yes'; then + if [[ $option_first == 'yes' ]]; then filename="${filename%%.*}" else filename="${filename%.*}" fi - if test "$option_basename" = 'yes' -o "$dirname" = '.'; then + if [[ $option_basename == 'yes' || $dirname == '.' ]]; then __print_lines "$filename" else __print_lines "$dirname/$filename" @@ -93,6 +96,6 @@ function fs_filename() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_filename "$@" fi diff --git a/commands/fs-join b/commands/fs-join index 59df2b1d4..548fceeaf 100755 --- a/commands/fs-join +++ b/commands/fs-join @@ -14,7 +14,7 @@ function fs_join() ( USAGE: fs-join [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function fs_join() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -62,7 +62,7 @@ function fs_join() ( next="${next#/}" fi # join - if test -n "$result" -a -n "$next"; then + if [[ -n $result && -n $next ]]; then result="$result/$next" else result="$result$next" @@ -72,6 +72,6 @@ function fs_join() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_join "$@" fi diff --git a/commands/fs-kilobytes b/commands/fs-kilobytes index fb637945b..826a14c83 100755 --- a/commands/fs-kilobytes +++ b/commands/fs-kilobytes @@ -24,7 +24,7 @@ function fs_kilobytes() ( USAGE: fs-kilobytes [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function fs_kilobytes() ( # options local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function fs_kilobytes() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -69,8 +69,8 @@ function fs_kilobytes() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then fs_kilobytes_test else fs_kilobytes "$@" diff --git a/commands/fs-megabytes b/commands/fs-megabytes index 68b8c6581..7fa35eb17 100755 --- a/commands/fs-megabytes +++ b/commands/fs-megabytes @@ -24,7 +24,7 @@ function fs_megabytes() ( USAGE: fs-megabytes [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function fs_megabytes() ( # options local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function fs_megabytes() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -69,8 +69,8 @@ function fs_megabytes() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then fs_megabytes_test else fs_megabytes "$@" diff --git a/commands/fs-own b/commands/fs-own index 02f245232..12b9827de 100755 --- a/commands/fs-own +++ b/commands/fs-own @@ -4,34 +4,69 @@ function fs_own_test() ( source "$DOROTHY/sources/bash.bash" echo-style --h1="TEST: $0" - local dir file - dir="$(fs-temp --directory)" - file="$(fs-temp --directory="$dir" --file --touch)" + local root dir file + root="$(fs-temp --directory)" + dir="$(fs-temp --root="$root" --directory='dir' --touch)" + file="$(fs-temp --root="$root" --file='file' --touch)" + subfile="$(fs-temp --root="$dir" --file='subfile' --touch)" + + fs-structure -- "$root" eval-tester --name='can own a file +quiet' \ -- fs-own --quiet -- "$file" - eval-tester --name='can own a durectory +quiet' \ + fs-structure -- "$root" + + eval-tester --name='can own a directory +quiet' \ -- fs-own --quiet -- "$dir" + fs-structure -- "$root" + eval-tester --name='can own a file -quiet' --ignore-stdout \ -- fs-own --no-quiet -- "$file" - eval-tester --name='can own a durectory -quiet' --ignore-stdout \ + fs-structure -- "$root" + + eval-tester --name='can own a directory -quiet' --ignore-stdout \ -- fs-own --no-quiet -- "$dir" + fs-structure -- "$root" + eval-tester --name='can own a file +verbose' --ignore-stdout --ignore-stderr \ -- fs-own --verbose -- "$file" - eval-tester --name='can own a durectory +verbose' --ignore-stdout --ignore-stderr \ + fs-structure -- "$root" + + eval-tester --name='can own a directory +verbose' --ignore-stdout --ignore-stderr \ -- fs-own --verbose -- "$dir" - eval-tester --name='can own a durectory +quiet +admin' --ignore-stderr \ + fs-structure -- "$root" + + eval-tester --name='can own a directory +quiet +admin' --ignore-stderr \ -- fs-own --quiet --admin -- "$dir" - eval-tester --name='can own a durectory -quiet +admin' --ignore-stdout --ignore-stderr \ + fs-structure -- "$root" + + eval-tester --name='can own a directory -quiet +admin' --ignore-stdout --ignore-stderr \ -- fs-own --no-quiet --admin -- "$dir" + fs-structure -- "$root" + + eval-tester --name='can own a directory +quiet +root' --ignore-stdout --ignore-stderr \ + -- fs-own --verbose --root -- "$dir" + + fs-structure -- "$root" || : + eval-helper --no-quiet --wrap -- is-accessible.bash -- "$dir" || : + eval-helper --no-quiet --wrap -- is-accessible -- "$dir" || : + eval-helper --no-quiet --wrap -- stat -L "$dir" || : + sudo-helper --wrap -- stat -L "$dir" || : + + fs-structure -- "$dir" || : + eval-helper --no-quiet --wrap -- is-accessible.bash -- "$subfile" || : + eval-helper --no-quiet --wrap -- is-accessible -- "$subfile" || : + eval-helper --no-quiet --wrap -- stat -L "$subfile" || : + sudo-helper --wrap -- stat -L "$subfile" || : + echo-style --g1="TEST: $0" return 0 ) @@ -54,7 +89,7 @@ function fs_own() ( if provided, will output the executed commands. --verbose - if provied, will use --no-quiet for this command, and use --verbose on executed chmod/chown commands. + if provided, will use --no-quiet for this command, and use --verbose on executed chmod/chown commands. --changes if provided, changes will be reported if the operating system supports it. @@ -88,9 +123,9 @@ function fs_own() ( if provided, only the exact path will be used, not any child paths. QUIRKS: - If [--permissions=...], [--directory-permissions=...], [--file-permissions=...] are all omitted, then the directory permissions will be set to [755] and the file permissions set to [644]. + If [--permissions=...], [--directory-permissions=...], [--file-permissions=...] are all omitted, then the permissions will be set to [a-xrw,ug+Xrw]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -107,13 +142,14 @@ function fs_own() ( local option_parents='no' local option_me='no' local option_admin='no' + local option_root='no' local option_permissions='' local option_directory_permissions='' # deprecated local option_file_permissions='' # deprecated local option_sudo='no' local option_optional='no' local option_recursive='yes' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -136,13 +172,16 @@ function fs_own() ( '--no-admin'* | '--admin'*) option_admin="$(get-flag-value --affirmative --fallback="$option_admin" -- "$item")" ;; + '--no-root'* | '--root'*) + option_root="$(get-flag-value --affirmative --fallback="$option_root" -- "$item")" + ;; '--no-sudo'* | '--sudo'*) option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" ;; '--no-optional'* | '--optional'*) option_optional="$(get-flag-value --affirmative --fallback="$option_optional" -- "$item")" ;; - '--no-recursive'* | '--recursive'*) + '--no-recursive'* | '--recursive'* | '--no-recurse'* | '--recurse'* | '--no-recursion'* | '--recursion'*) option_recursive="$(get-flag-value --affirmative --fallback="$option_recursive" -- "$item")" ;; '--permissions='*) option_permissions="${item#*=}" ;; @@ -163,8 +202,8 @@ function fs_own() ( done # check - if test "${#option_paths[@]}" -eq 0; then - if test "$option_optional" = 'yes'; then + if [[ ${#option_paths[@]} -eq 0 ]]; then + if [[ $option_optional == 'yes' ]]; then return 0 fi echo-error 'No s provided.' @@ -172,28 +211,32 @@ function fs_own() ( fi local path for path in "${option_paths[@]}"; do - if test -z "$path"; then + if [[ -z $path ]]; then echo-error 'Cannot claim ownership of an empty path:' $'\n' "$(echo-verbose -- "${option_paths[@]}")" return 22 # EINVAL 22 Invalid argument fi done # check for deprecation - if test -n "$option_directory_permissions" -o -n "$option_file_permissions"; then - help "[file|directory]-permissions are deprecated, you probably just wanted to do --permissions=X..., see https://superuser.com/a/91966/32418" + if [[ -n $option_directory_permissions || -n $option_file_permissions ]]; then + help '[file|directory]-permissions are deprecated, you probably just wanted to do --permissions=X..., see https://superuser.com/a/91966/32418' fi # adjustments: permissions - if test -z "$option_permissions"; then + if [[ -z $option_permissions ]]; then option_permissions='a-xrw,ug+Xrw' fi # adjustments: admin - if test "$option_me" = 'yes' -a "$option_admin" = 'yes'; then - help "--me and --admin cannot both be provided" - elif test "$option_me" = 'yes'; then + if [[ $option_me == 'yes' && $option_admin == 'yes' ]]; then + help '--me and --admin cannot both be provided' + elif [[ $option_me == 'yes' && $option_root == 'yes' ]]; then + help '--me and --root cannot both be provided' + elif [[ $option_admin == 'yes' && $option_root == 'yes' ]]; then + help '--admin and --root cannot both be provided' + elif [[ $option_me == 'yes' ]]; then option_user="$(whoami)" - elif test "$option_admin" = 'yes'; then + elif [[ $option_admin == 'yes' ]]; then if is-mac; then option_user="$(whoami)" option_group='admin' @@ -202,17 +245,27 @@ function fs_own() ( option_user='root' option_group='root' fi + elif [[ $option_root == 'yes' ]]; then + # option_user='0' + # option_group='0' + if is-mac; then + option_user='root' + option_group='wheel' + else + option_user='root' + option_group='root' + fi fi # adjustments: owner local owner='' - if test -n "$option_owner"; then + if [[ -n $option_owner ]]; then owner="$option_owner" - elif test -n "$option_user" -a -n "$option_group"; then + elif [[ -n $option_user && -n $option_group ]]; then owner="$option_user:$option_group" - elif test -n "$option_user"; then + elif [[ -n $option_user ]]; then owner="$option_user" - elif test -n "$option_group"; then + elif [[ -n $option_group ]]; then owner=":$option_group" fi @@ -220,21 +273,22 @@ function fs_own() ( # -f: Do not display a diagnostic message when chmod could not modify the mode for file, nor modify the exit status to reflect such (macos) # -f, --silent, --quiet: suppress most error messages (ubuntu) # -R/--recursive: self-explanatory + # -h: If the file is a symbolic link, change the mode of the link itself rather than the file that the link points to. local ch_args=() - if test "$option_recursive" = 'yes'; then + if [[ $option_recursive == 'yes' ]]; then if is-mac || is-alpine; then ch_args+=('-R') else ch_args+=('--recursive') fi fi - if test "$option_optional" = 'yes'; then + if [[ $option_optional == 'yes' ]]; then ch_args+=('-f') fi # adjustments: changes - if test -z "$option_changes"; then - if test "$option_quiet" = 'yes' || is-mac; then + if [[ -z $option_changes ]]; then + if [[ $option_quiet == 'yes' ]] || is-mac; then option_changes='no' else option_changes='yes' @@ -242,21 +296,21 @@ function fs_own() ( fi # adjustments: quiet - if test -z "$option_quiet"; then + if [[ -z $option_quiet ]]; then option_quiet='yes' fi # apply args - if test "$option_changes" = 'yes'; then + if [[ $option_changes == 'yes' ]]; then if is-mac || is-alpine; then echo-style --dim='Reporting permission changes is not provided by this Operating System.' >/dev/stderr else ch_args+=('--changes') fi fi - if test "$option_verbose" = 'yes'; then + if [[ $option_verbose == 'yes' ]]; then if is-mac; then - echo-style --dim='Verbose permission changes is not provided by this Operating System.' >/dev/stderr + ch_args+=('-v') else ch_args+=('--verbose') fi @@ -266,7 +320,7 @@ function fs_own() ( # Prepare # handle parents - if test "$option_parents" = 'yes'; then + if [[ $option_parents == 'yes' ]]; then local parent_paths all_parent_paths=() for path in "${option_paths[@]}"; do parent_paths=() @@ -275,7 +329,8 @@ function fs_own() ( done fs-own --no-recursive --no-parents \ --quiet="$option_quiet" --changes="$option_changes" \ - --me="$option_me" --admin="$option_admin" --sudo="$option_sudo" --user="$option_user" --group="$option_group" \ + --me="$option_me" --admin="$option_admin" --root="$option_root" \ + --sudo="$option_sudo" --user="$option_user" --group="$option_group" \ --optional="$option_optional" \ --permissions="$option_permissions" \ --directory-permissions="$option_directory_permissions" \ @@ -295,24 +350,26 @@ function fs_own() ( )" # sudo as the user/group - if test -n "$owner" -o "$option_sudo" = 'yes'; then + if [[ -n $owner || $option_sudo == 'yes' ]]; then sudo_as_user_args+=( 'sudo-helper' + '--inherit' "--reason=$sudo_reason" ) - if test -n "$option_user"; then + if [[ -n $option_user ]]; then sudo_as_user_args+=("--user=$option_user") fi - if test -n "$option_group"; then + if [[ -n $option_group ]]; then sudo_as_user_args+=("--group=$option_group") fi sudo_as_user_args+=('--') fi # sudo as the admin - if test -n "$owner" -o "$option_sudo" = 'yes'; then + if [[ -n $owner || $option_sudo == 'yes' ]]; then sudo_as_admin_args+=( 'sudo-helper' + '--inherit' "--reason=$sudo_reason" '--' ) @@ -324,24 +381,24 @@ function fs_own() ( local paths=() function is_available { local path="$1" cmd=() - if test -n "$owner"; then - if test "${#sudo_as_admin_args[@]}" -ne 0; then + if [[ -n $owner ]]; then + if [[ ${#sudo_as_admin_args[@]} -ne 0 ]]; then cmd+=("${sudo_as_admin_args[@]}") fi else - if test "${#sudo_as_user_args[@]}" -ne 0; then + if [[ ${#sudo_as_user_args[@]} -ne 0 ]]; then cmd+=("${sudo_as_user_args[@]}") fi fi # can't use is-present, because sudo doesn't have access, and it is too complicated to add access for it - cmd+=(test -e "$path" -o -L "$path") + cmd+=(is-present.bash -- "$path") "${cmd[@]}" # eval } function check_exists { local path="$1" available_status eval_capture --statusvar=available_status -- is_available "$path" - if test "$available_status" -ne 0; then - if test "$option_optional" != 'yes'; then + if [[ $available_status -ne 0 ]]; then + if [[ $option_optional != 'yes' ]]; then echo-error 'Cannot claim ownership a path that does exist or is inaccessible:' $'\n' --code="$path" return 2 # ENOENT 2 No such file or directory fi @@ -351,9 +408,9 @@ function fs_own() ( local cmd # chown - if test -n "$owner"; then + if [[ -n $owner ]]; then cmd=() - if test "${#sudo_as_admin_args[@]}" -ne 0; then + if [[ ${#sudo_as_admin_args[@]} -ne 0 ]]; then cmd+=("${sudo_as_admin_args[@]}") fi cmd+=(chown "${ch_args[@]}" "$owner" "${paths[@]}") @@ -361,12 +418,13 @@ function fs_own() ( fi # chmod - if test -n "$option_permissions"; then + if [[ -n $option_permissions ]]; then cmd=() # https://superuser.com/a/91966/32418 - if test "${#sudo_as_user_args[@]}" -ne 0; then + if [[ ${#sudo_as_user_args[@]} -ne 0 ]]; then cmd+=("${sudo_as_user_args[@]}") fi + # removing readable perms on a directory, while recursing, will cause permission denied failure cmd+=(chmod "${ch_args[@]}" "$option_permissions" "${paths[@]}") "${cmd[@]}" # eval fi @@ -377,21 +435,21 @@ function fs_own() ( for path in "${option_paths[@]}"; do eval_capture --statusvar=exists_status -- check_exists "$path" # check existence - if test "$exists_status" -eq 0; then + if [[ $exists_status -eq 0 ]]; then paths+=("$path") # it exists, update ownership for it fi done # perform the ownership - if test "${#paths[@]}" -ne 0; then + if [[ ${#paths[@]} -ne 0 ]]; then eval_capture --statusvar=own_status -- do_own # check ownership failures - if test "$own_status" -ne 0 -a "$option_optional" = 'no'; then + if [[ $own_status -ne 0 && $option_optional == 'no' ]]; then return "$own_status" fi fi # check if we had missing paths - if test "${#paths[@]}" -ne "${#option_paths[@]}"; then - if test "$option_optional" = 'yes'; then + if [[ ${#paths[@]} -ne ${#option_paths[@]} ]]; then + if [[ $option_optional == 'yes' ]]; then return 0 fi return 2 # ENOENT 2 No such file or directory @@ -399,8 +457,8 @@ function fs_own() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then fs_own_test else fs_own "$@" @@ -410,22 +468,22 @@ fi # # find -exec fails when --user --group is provided as it fails to attach to correct home # # so instead xargs needs to be used, but that fails because too many files or so # # so instead we do a loop, but that is very slow -# # and in the end, the only time people ever need this is when they actually inded to do +# # and in the end, the only time people ever need this is when they actually intend to do # # an uppercase X permission https://superuser.com/a/91966/32418 -# if test -n "$option_directory_permissions"; then -# test "$option_quiet" = 'no' && set -x || : +# if [[ -n "$option_directory_permissions" ]]; then +# [[ "$option_quiet" = 'no' ]] && set -x || : # "${sudo_as_user_args[@]}" find "$path" -type d | while read -r dir; do # "${sudo_as_user_args[@]}" \ # chmod "${ch_args[@]}" "$option_directory_permissions" "$dir" # done -# test "$option_quiet" = 'no' && set +x || : +# [[ "$option_quiet" = 'no' ]] && set +x || : # fi -# if test -n "$option_file_permissions"; then -# test "$option_quiet" = 'no' && set -x || : +# if [[ -n "$option_file_permissions" ]]; then +# [[ "$option_quiet" = 'no' ]] && set -x || : # "${sudo_as_user_args[@]}" find "$path" -type f | while read -r file; do # "${sudo_as_user_args[@]}" \ # chmod "${ch_args[@]}" "$option_file_permissions" "$file" # done -# test "$option_quiet" = 'no' && set +x || : +# [[ "$option_quiet" = 'no' ]] && set +x || : # fi diff --git a/commands/fs-parents b/commands/fs-parents index a9b1065c5..2466f08e4 100755 --- a/commands/fs-parents +++ b/commands/fs-parents @@ -16,28 +16,27 @@ function fs_parents() ( OPTIONS: --self - If specified, incude the in the output. + If specified, include the in the output. --root If specified, include the root directory in the output. - --sudo - If specified, use sudo on filesystem interactions. + --sudo= --user= --group= - If specified use this user and/or group for filesystem interactions. + Forwarded to [fs-absolute]. QUIRKS: Use [fs-realpath] if you want symlinks resolved. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_paths=() option_self='no' option_root='no' option_sudo='no' option_user='' option_group='' - while test "$#" -ne 0; do + local item option_paths=() option_self='no' option_root='no' option_sudo='' option_user='' option_group='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -64,7 +63,7 @@ function fs_parents() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -75,12 +74,12 @@ function fs_parents() ( for path in "${option_paths[@]}"; do paths=() path="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path")" - if test "$option_self" = 'yes'; then + if [[ $option_self == 'yes' ]]; then paths+=("$path") fi - while test "$path" != '/'; do + while [[ $path != '/' ]]; do temp="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path/..")" - if test "$temp" = "$path" || test "$temp" = '/' -a "$option_root" = 'no'; then + if [[ $temp == "$path" || ($temp == '/' && $option_root == 'no') ]]; then break fi paths+=("$temp") @@ -94,6 +93,6 @@ function fs_parents() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_parents "$@" fi diff --git a/commands/fs-realpath b/commands/fs-realpath index d760e1c08..cec2e2e3d 100755 --- a/commands/fs-realpath +++ b/commands/fs-realpath @@ -9,7 +9,7 @@ function fs_realpath() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Output the absolute (relative paths are expanded) and real (symlinks are resolved) respresentation of a path. + Output the absolute (relative paths are expanded) and real (symlinks are resolved) representation of a path. USAGE: fs-realpath [...options] [--] ... @@ -33,19 +33,24 @@ function fs_realpath() ( --relative-base= If provided, print absolute paths unless paths below DIR + --sudo= + --user= + --group= + Forwarded to [sudo-helper] and [is-symlink]. + 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. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # options - local item option_paths=() option_resolve='yes' option_validate='yes' option_relative='no' option_relative_to='' option_relative_base='' - while test "$#" -ne 0; do + local item option_paths=() option_resolve='yes' option_validate='yes' option_relative='no' option_relative_to='' option_relative_base='' option_sudo='' option_user='' option_group='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -63,6 +68,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 $# @@ -74,13 +84,17 @@ function fs_realpath() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # 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 @@ -89,7 +103,7 @@ function fs_realpath() ( if is-mac; then # usage: realpath [-q] [path ...] return 45 # ENOTSUP 45 Operation not supported - elif is-alpine && test "$(realpath "$(type -P 'realpath')" || :)" = "$(type -P busybox || :)"; then + elif is-alpine && [[ "$(realpath "$(type -P 'realpath')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: realpath FILE... return 45 # ENOTSUP 45 Operation not supported elif is-linux; then @@ -116,36 +130,37 @@ function fs_realpath() ( local args=() # grealpath always returns absolute paths, so do not support relative option - if test "$option_relative" = 'yes'; then + if [[ $option_relative == 'yes' ]]; then return 45 # ENOTSUP 45 Operation not supported fi # grealpath supports validation, and non-validation - if test "$option_validate" = 'yes'; then + if [[ $option_validate == 'yes' ]]; then args+=('--canonicalize-existing') else args+=('--canonicalize-missing') fi # grealpath supports resolution, and non-resolution - if test "$option_resolve" = 'no'; then + if [[ $option_resolve == 'no' ]]; then args+=('--no-symlinks') fi # grealpath supports relative-to and relative-dir - if test -n "$option_relative_to"; then + if [[ -n $option_relative_to ]]; then args+=("--relative-to=$option_relative_to") fi - if test -n "$option_relative_base"; then + if [[ -n $option_relative_base ]]; then args+=("--relative-to=$option_relative_base") fi # execute - "$gnu_realpath" "${args[@]}" "$path" + __sudo -- \ + "$gnu_realpath" "${args[@]}" "$path" } function do_gnu_readlink { # don't support unsupported args - if test -n "$option_relative_to" -o -n "$option_relative_to"; then + if [[ -n $option_relative_to || -n $option_relative_to ]]; then return 45 # ENOTSUP 45 Operation not supported fi @@ -156,7 +171,7 @@ function fs_realpath() ( if is-mac; then # usage: readlink [-fn] [file ...] return 45 # ENOTSUP 45 Operation not supported - elif is-alpine && test "$(readlink -f "$(type -P 'readlink')" || :)" = "$(type -P busybox || :)"; then + elif is-alpine && [[ "$(readlink -f "$(type -P 'readlink')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: readlink [-fnv] FILE return 45 # ENOTSUP 45 Operation not supported elif is-linux; then @@ -181,32 +196,32 @@ function fs_realpath() ( local path="$1" # greadlink always resolves symlinks - if test "$option_resolve" = 'no'; then + if [[ $option_resolve == 'no' ]]; then return 45 # ENOTSUP 45 Operation not supported fi # handle quirks - if test "$option_relative" = 'yes'; then - if test "$option_validate" = 'yes'; then + if [[ $option_relative == 'yes' ]]; then + if [[ $option_validate == 'yes' ]]; then # relative paths are not supported when validating return 45 # ENOTSUP 45 Operation not supported - elif test ! -L "$path"; then - # unless canoniclizing, then symlinks are required + elif [[ ! -L $path ]]; then + # unless canonicalizing, then symlinks are required return 45 # ENOTSUP 45 Operation not supported else - "$gnu_readlink" "$path" + __sudo -- "$gnu_readlink" "$path" fi else - if test "$option_validate" = 'yes'; then - "$gnu_readlink" --canonicalize-existing "$path" + if [[ $option_validate == 'yes' ]]; then + __sudo -- "$gnu_readlink" --canonicalize-existing "$path" else - "$gnu_readlink" --canonicalize-missing "$path" + __sudo -- "$gnu_readlink" --canonicalize-missing "$path" fi fi } function do_fallback_readlink { # don't support unsupported args - if test -n "$option_relative_to" -o -n "$option_relative_to"; then + if [[ -n $option_relative_to || -n $option_relative_to ]]; then return 45 # ENOTSUP 45 Operation not supported fi @@ -215,7 +230,7 @@ function fs_realpath() ( if is-mac; then # usage: readlink [-fn] [file ...] fallback_readlink='readlink' - elif is-alpine && test "$(readlink -f "$(type -P 'readlink')" || :)" = "$(type -P busybox || :)"; then + elif is-alpine && [[ "$(readlink -f "$(type -P 'readlink')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: readlink [-fnv] FILE fallback_readlink='readlink' else @@ -232,23 +247,23 @@ function fs_realpath() ( # NOTES # -f fetches the absolutely resolved path - if test "$option_resolve" = 'yes' -a "$option_relative" = 'yes' -a "$option_validate" = 'no'; then - if test -L "$path"; then - "$fallback_readlink" "$path" + if [[ $option_resolve == 'yes' && $option_relative == 'yes' && $option_validate == 'no' ]]; then + 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 test "$option_resolve" = 'yes' -a "$option_relative" = 'no' -a "$option_validate" = 'yes'; then + 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 } function do_fallback_realpath { # don't support unsupported args - if test -n "$option_relative_to" -o -n "$option_relative_to"; then + if [[ -n $option_relative_to || -n $option_relative_to ]]; then return 45 # ENOTSUP 45 Operation not supported fi @@ -257,7 +272,7 @@ function fs_realpath() ( if is-mac; then # usage: realpath [-q] [path ...] fallback_realpath='realpath' - elif is-alpine && test "$(realpath "$(type -P 'realpath')" || :)" = "$(type -P busybox || :)"; then + elif is-alpine && [[ "$(realpath "$(type -P 'realpath')" || :)" == "$(type -P busybox || :)" ]]; then # Usage: realpath FILE... fallback_realpath='realpath' else @@ -271,27 +286,27 @@ function fs_realpath() ( # realpath [-q] [path ...] # If -q is specified, warnings will not be printed when realpath(3) fails. - if test "$option_resolve" = 'yes' -a "$option_relative" = 'no' -a "$option_validate" = 'yes'; then + 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 } function __fish_capable { - __command_exists -- fish && test "$(version-compare "$(fish -c 'echo $FISH_VERSION')" 3.3.0)" -ge 0 + __command_exists -- fish && [[ "$(version-compare "$(fish -c 'echo $FISH_VERSION')" 3.3.0)" -ge 0 ]] return } function do_fish_realpath { # don't support unsupported args - if test -n "$option_relative_to" -o -n "$option_relative_to"; then + if [[ -n $option_relative_to || -n $option_relative_to ]]; then return 45 # ENOTSUP 45 Operation not supported fi local fish_status eval_capture --statusvar=fish_status -- __fish_capable - if test "$fish_status" -ne 0; then + if [[ $fish_status -ne 0 ]]; then return 45 # ENOTSUP 45 Operation not supported fi @@ -309,11 +324,11 @@ function fs_realpath() ( # exact compatibility unknown # if you are using an old version, remove fish and reinstall using [setup-util-fish] - if test "$option_relative" = 'no' -a "$option_validate" = 'no'; then - if test "$option_resolve" = 'no'; then - fish -c 'builtin realpath --no-symlinks "$argv[1]"' -- "$path" + if [[ $option_relative == 'no' && $option_validate == 'no' ]]; then + if [[ $option_resolve == 'no' ]]; then + __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 @@ -334,13 +349,13 @@ function fs_realpath() ( # cycle through the options, and continue if the option is not supported for method in "${methods[@]}"; do eval_capture --statusvar=method_status -- "$method" "$path" - if test "$method_status" -ne 45; then + if [[ $method_status -ne 45 ]]; then return "$method_status" fi done # if no option was supported, then install utilities and try again - if test "$FALLBACK" = 'no'; then + if [[ $FALLBACK == 'no' ]]; then # greadlink + macos-readlink support [--relative=yes] but require [--resolve=yes --validate=no] and for the symlink to exist echo-error 'fs-realpath depends on installed helper utilities, non of which support this combination of options:' $'\n' \ --code="fs-realpath --resolve=$option_resolve --validate=$option_validate --relative=$option_relative -- $(echo-escape-command -- "$path")" @@ -358,6 +373,6 @@ function fs_realpath() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_realpath "$@" fi diff --git a/commands/fs-rm b/commands/fs-rm index f698e93ff..9c56fdc9b 100755 --- a/commands/fs-rm +++ b/commands/fs-rm @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# @todo write tests, use fs-trim and is-readable for reference, make sure to test unreadable dirs, files, and unreadable nested contents of a readable dir + function fs_rm() ( source "$DOROTHY/sources/bash.bash" @@ -15,34 +17,56 @@ function fs_rm() ( fs-rm [...options] [--] ... OPTIONS: + --reason= + The reason for the removal. + --quiet If specified, only output errors or when user intervention is required. - --optional - If specified, doesn't fail if no s were provided. - --no-confirm - If specified, skip confirms. + If specified, skip confirmations. - --sudo - If specified, use sudo when removing the files. + --no-confirm-if-empty + If specified, skip confirmations if the file or directory is empty. - --trash - If specified, move the file to trash instead of deleting it immediately. + --optional + If specified, doesn't fail if no s were provided. + --sudo= --user= --group= - If specified run the removal commands as this and . + Forwarded to [sudo-helper]. + + --readable + Make the and its content readable. + + --trim + Trim redundant content and re-evaluate. + Requires to be readable. + + --trash + Remove the by moving it to trash, if the system supports it. + Does not require the and its content to be readable. + Cannot be used with --sudo, --user=, --group=. + + --delete + Remove the by deleting it immediately, without recovery. + If the is a directory, then the and its content must be readable. + + QUIRKS: + If --no-confirm is not provided, or if an error or complication is encountered, the user will be prompted for which action to take. + Delete will fail if it encounters a directory that is unreadable. + Removing a parent directory is forbidden, a the shell would crash. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_quiet='no' option_paths=() option_optional='no' option_confirm='yes' option_sudo='no' option_trash='no' option_user='' option_group='' - while test "$#" -ne 0; do + local item option_quiet='no' option_inputs=() option_reason='' option_optional='no' option_confirm='' option_confirm_if_empty='' option_sudo='' option_user='' option_group='' option_readable='' option_trim='' option_trash='' option_delete='' option_preferences=() + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -56,211 +80,428 @@ function fs_rm() ( '--no-optional'* | '--optional'*) option_optional="$(get-flag-value --affirmative --fallback="$option_optional" -- "$item")" ;; + '--no-confirm-if-empty'* | '--confirm-if-empty'*) # must be before --no-confirm as otherwise --no-confirm* will match it + option_confirm_if_empty="$(get-flag-value --affirmative --fallback="$option_confirm_if_empty" -- "$item")" + ;; '--no-confirm'* | '--confirm'*) option_confirm="$(get-flag-value --affirmative --fallback="$option_confirm" -- "$item")" ;; '--no-sudo'* | '--sudo'*) option_sudo="$(get-flag-value --affirmative --fallback="$option_sudo" -- "$item")" ;; + '--no-readable'* | '--readable'*) + option_readable="$(get-flag-value --affirmative --fallback="$option_readable" -- "$item")" + if [[ $option_readable == 'yes' ]]; then + option_preferences+=('readable') + fi + ;; + '--no-trim'* | '--trim'*) + option_trim="$(get-flag-value --affirmative --fallback="$option_trim" -- "$item")" + if [[ $option_trim == 'yes' ]]; then + option_preferences+=('trim') + fi + ;; '--no-trash'* | '--trash'*) option_trash="$(get-flag-value --affirmative --fallback="$option_trash" -- "$item")" + if [[ $option_trash == 'yes' ]]; then + option_preferences+=('trash') + fi + ;; + '--no-delete'* | '--delete'*) + option_delete="$(get-flag-value --affirmative --fallback="$option_delete" -- "$item")" + if [[ $option_delete == 'yes' ]]; then + option_preferences+=('delete') + fi ;; '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; - '--path='*) option_paths+=("${item#*=}") ;; + '--path='*) option_inputs+=("${item#*=}") ;; + '--reason='*) option_reason="${item#*=}" ;; '--') - option_paths+=("$@") + option_inputs+=("$@") shift $# break ;; '--'*) help "An unrecognised flag was provided: $item" ;; - *) option_paths+=("$item") ;; + *) option_inputs+=("$item") ;; esac done # check - if test "${#option_paths[@]}" -eq 0; then - if test "$option_optional" = 'yes'; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then + if [[ $option_optional == 'yes' ]]; then return 0 else help 'No s provided.' fi fi + # adjust reason + local styled_reason='' + if [[ -n $option_reason ]]; then + styled_reason="$( + echo-style --notice1="$option_reason" + )"$'\n' + fi + # ===================================== # Dependencies - if test "$option_trash" = 'yes'; then - setup-util-trash --quiet --optional --no-fallback - if __command_missing -- trash; then - echo-style --dim='Moving to trash is not available, falling back to immediate deletion for: ' --code="${option_paths[*]}" >/dev/stderr + # prep menu + local \ + readable_options=('readable' 'Make contents readable, then reevaluate') \ + trim_options=('trim' 'Trim redundant contents, then reevaluate') \ + trash_options=('trash' 'Move to trash') \ + delete_options=('delete' 'Delete immediately, without recovery') \ + again_options=('again' "I've done manual changes, reevaluate") \ + abort_options=('abort' 'Keep it, and abort the requested removal') + + # adjust options with warnings + if [[ $option_sudo == 'yes' || -n $option_user || -n $option_group ]]; then + if [[ $option_trash == 'yes' ]]; then + echo-style --dim='Moving to trash is not supported for sudo, falling back to immediate deletion for: ' --code="${option_inputs[*]}" >/dev/stderr option_trash='no' fi fi - # ===================================== - # Action - - function eval_wrapper { - while test "$1" = '--'; do - shift - done - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -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" \ - -- "$@" + # handle macos trash support, which is only properly supported with the builtin trash command in macOS 14.0 and up + # adjust options with warnings, and feature detection + local trash_bin + if [[ $option_trash != 'no' ]]; then + if [[ -z $option_trash && $option_delete != 'yes' ]]; then + # no trash/delete preference set, so provide it + setup-util-trash --quiet --optional --no-fallback 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 + if is-mac; then + if [[ -x '/usr/bin/trash' ]]; then + trash_bin='/usr/bin/trash' + else + trash_bin='' + fi + elif is-linux; then + trash_bin="$(type -P 'trash-put' 2>/dev/null || :)" fi - - # if preconfirmed, skip the prompt - if test "$option_confirm" = 'no'; then - return 0 + if [[ -z $trash_bin ]]; then + if [[ $option_trash == 'yes' ]]; then + echo-style --dim='Moving to trash is not available, falling back to immediate deletion for: ' --code="${option_inputs[*]}" >/dev/stderr + fi + option_trash='no' fi + 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 + # prep defaults if none + # do not do readable or trash or trim, as if we don't have permissions, we want to fail to let the user aware + # do not do trim, as it is redundant if we are empty + if [[ ${#option_preferences[@]} -eq 0 ]]; then + option_preferences=('delete') + fi - # note its structure and size - eval_wrapper -- ls -la "$path" + # ===================================== + # Action - # 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 test "$option_confirm" = 'no'; then + local CONCLUSION selves + mapfile -t selves < <(fs-parents --self --root -- .) + function do_rm { + local input="$1" path title='' body choices choice is defaults default_args preferences=("${option_preferences[@]}") temp had_failure='' is_readable is_empty can_readable can_trim can_trash can_delete default_for_noconfirm + + # is the input already removed? + if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input"; then + CONCLUSION="$( + echo-style --success='previously removed.' + )" return 0 fi - # we want to prompt - eval_wrapper -- ls -la "$path" - - # is a directory, so output extra information - if test -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 + # prevent deleting ourself which causes: shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory + path="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input")" + if is-needle --needle="$path" -- "${selves[@]}"; then + CONCLUSION="$( + echo-style --error='contains the CWD, denied.' + )" + echo-style --error1='Denied removing a lineage of the current working directory, change the working directory to elsewhere and try again: ' --code-error1="$path" >/dev/stderr + return 1 fi - # confirm removal - confirm --positive --ppid=$$ -- "$( - echo-style --warning='Confirm removal of non-empty' --bold=" $path " --warning="?" - )" - } + # it is remaining, so prompt on what to do + while :; do + CONCLUSION='' + is='' + choices=() + defaults=() + default_args=() + is_readable='' + is_directory='' + is_empty='' + can_readable='' + can_trim='' + can_trash='' + can_delete='' + default_for_noconfirm='' + + # if not quiet, dump the progress + if [[ -n $CONCLUSION && $option_quiet == 'no' ]]; then + echo-style --code="$path" " $CONCLUSION" >/dev/stderr + fi - local CONCLUSION='' - function do_rm { - local path="$1" confirm_trim_status confirm_removal_status + # if readable, then we can detect emptiness + if ! is-readable --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + is_readable='no' + is='non-readable' + else + is_readable='yes' + if is-broken-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + is_directory='no' + is='broken symlink' + elif 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"; then + is_empty='yes' + is_directory='no' + is='empty file' + elif is-empty-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + is_empty='yes' + is_directory='yes' + is='empty directory' + else + is='non-empty' + fi + else + is='symlink' + fi + fi + if [[ -z $is_directory ]]; then + if is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + is_directory='yes' + if [[ -z $is ]]; then + is='directory' + else + is+=' directory' + fi + else + is_directory='no' + # don't bother with commentary on files + fi + fi - # is the path missing - if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then - CONCLUSION="$( - echo-style --green="was previously removed." - )" - return 0 - fi - # path exists + # adjust options + if [[ $is_readable == 'no' ]]; then + can_readable='yes' + # no trim + # can trash + if [[ $option_trash != 'no' ]]; then + can_trash='yes' + fi + # no delete if directory + if ! [[ $option_delete == 'no' || $is_directory == 'yes' ]]; then + can_delete='yes' + fi + else + if [[ $option_readable == 'yes' || $had_failure == 'yes' ]]; then + can_readable='yes' + fi + if [[ $option_trim != 'no' ]]; then + can_trim='yes' + fi + if [[ $option_trash != 'no' ]]; then + can_trash='yes' + fi + if [[ $option_delete != 'no' ]]; then + can_delete='yes' + fi + fi - # delete empty directories - eval_capture --statusvar=confirm_trim_status -- do_confirm_trim "$path" - if test "$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 - CONCLUSION="$( - echo-style --green="was only empty directories, it has been removed." - )" - return 0 + # adjust menu choices and defaults + if [[ $can_readable == 'yes' ]]; then + choices+=("${readable_options[@]}") fi - fi - # there are leftovers - - # confirm and remove, or no confirm and remove - eval_capture --statusvar=confirm_removal_status -- do_confirm_removal "$path" - if test "$confirm_removal_status" -eq 0; then - if test "$option_trash" = 'yes'; then - eval_capture -- eval_wrapper -- trash "$path" - elif test "$option_quiet" = 'yes'; then - eval_capture -- eval_wrapper -- rm -rf "$path" + if [[ $can_delete == 'yes' ]]; then + choices+=("${delete_options[@]}") + fi + if [[ $can_trash == 'yes' ]]; then + choices+=("${trash_options[@]}") + fi + if [[ $can_trim == 'yes' ]]; then + choices+=("${trim_options[@]}") + fi + choices+=( + "${again_options[@]}" + "${abort_options[@]}" + ) + if [[ ${#preferences[@]} -ne 0 ]]; then + for item in "${preferences[@]}"; do + case "$item" in + 'readable') + if [[ $can_readable == 'yes' ]]; then + defaults+=('readable') + fi + ;; + 'trim') + if [[ $can_trim == 'yes' && $had_failure != 'yes' ]]; then + defaults+=('trim') + fi + ;; + 'trash') + if [[ $can_trash == 'yes' && $had_failure != 'yes' ]]; then + defaults+=('trash') + fi + ;; + 'delete') + if [[ $can_delete == 'yes' && $had_failure != 'yes' ]]; then + defaults+=('delete') + fi + ;; + esac + done + fi + if [[ ${#defaults[@]} -eq 0 ]]; then + if [[ $option_confirm != 'no' && $had_failure == 'yes' ]]; then + defaults+=('readable' 'trash') + fi + fi + if [[ ${#defaults[@]} -ne 0 ]]; then + if [[ $option_confirm == 'no' ]]; then + default_for_noconfirm="${defaults[0]}" + elif [[ $is_empty == 'yes' && $option_confirm_if_empty == 'no' ]]; then + default_for_noconfirm='delete' + fi + for item in "${defaults[@]}"; do + default_args+=("--default-exact=$item") + done + fi + # else allow the prompt + + # prompt + if [[ -n $default_for_noconfirm ]]; then + choice="$default_for_noconfirm" else - eval_capture -- eval_wrapper -- rm -rfv "$path" + if [[ -n $is ]]; then + title="$( + echo-style --="$styled_reason" --notice1="How to remove $is " --code-notice1="$path" + )" + else + title="$( + echo-style --="$styled_reason" --notice1='How to remove ' --code-notice1="$path" + )" + fi + if [[ $is_readable == 'no' && $is_directory == 'yes' ]]; then + # cannot ls/eza if directory is unreadable + body='' + else + body="$(echo-style --reset)$(fs-structure --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path")" + fi + choice="$( + choose --required "$title" "$body" --truncate-body "${default_args[@]}" --label -- "${choices[@]}" + )" fi - fi - # detect successful removal - 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." - )" - return 66 # ENOTEMPTY 66 Directory not empty - fi + # remove the predetermined choice from the preferences from next times defaults + # this is complicated as readable may have been provided to solve a nested readable issue, but not available yet due to is-readable showing it is readable, which is then changed to maybe on failure, allowing readable to become available for the default: this only applies to --confirm + if [[ ${#defaults[@]} -ne 0 && $choice == "${defaults[0]}" ]]; then + temp=() + for item in "${preferences[@]}"; do + if [[ $item != "$choice" ]]; then + temp+=("$item") + fi + done + preferences=("${temp[@]}") + fi - # success if removed - CONCLUSION="$( - echo-style --green="was non-empty, it was manually removed." - )" + # handle + # @todo maybe at one point store the output of the failures to show them in the next menu + # however, the ideal would be to have --inline for choose + had_failure='' + case "$choice" in + 'delete') + __wrap rm -rf "$path" || { + had_failure='yes' + } + ;; + 'trash') + __wrap "$trash_bin" "$path" || { + had_failure='yes' + } + ;; + 'trim') + fs-trim --quiet="$option_quiet" --confirm="$option_confirm" --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path" || { + had_failure='yes' + } + ;; + 'readable') + fs-own --quiet="$option_quiet" --permissions='+r' --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path" + ;; + 'abort') + if [[ -n $is ]]; then + CONCLUSION="$is " + fi + CONCLUSION+="$( + echo-style --error='failed to remove' + )" + echo-style --code="$path" " $CONCLUSION" >/dev/stderr + return 66 # ENOTEMPTY 66 Directory not empty + ;; + 'again' | '' | *) + continue + ;; + esac + + # check after trash/delete + if is-missing --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + if [[ -n $is ]]; then + CONCLUSION="$is " + fi + CONCLUSION+="$( + echo-style --success='removed' + )" + break + fi + done + 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 - if test "$option_quiet" = 'yes'; then - do_rm "$path" + local input="$1" title rm_status + if [[ $option_quiet == 'yes' ]]; then + do_rm "$input" return else title='fs-rm' - if test "$option_trash" = 'yes'; then + if [[ $option_trim == 'yes' ]]; then + title+=' --trim' + fi + if [[ $option_trash == 'yes' ]]; then title+=' --trash' fi - if test "$option_sudo" = 'yes'; then + if [[ $option_sudo == 'yes' ]]; then title+=' --sudo' fi - if test "$option_user" = 'yes'; then + if [[ -n $option_user ]]; then title+=" --user=$option_user" fi - if test "$option_group" = 'yes'; then + if [[ -n $option_group ]]; then title+=" --group=$option_group" fi - title+=" $path" - echo-style --h2="$title" - eval_capture --statusvar=rm_status -- do_rm "$path" - if test "$rm_status" -eq 0; then - echo-style --g2="$title" " $CONCLUSION" + title+=" $(echo-escape-command -- "$input")" + echo-style --h2="$title" >/dev/stderr + eval_capture --statusvar=rm_status -- do_rm "$input" + if [[ $rm_status -eq 0 ]]; then + echo-style --g2="$title" " $CONCLUSION" >/dev/stderr else - echo-style --e2="$title" " $CONCLUSION" + echo-style --e2="$title" " $CONCLUSION" >/dev/stderr return "$rm_status" fi fi } - local path - for path in "${option_paths[@]}"; do - act "$path" + local input + for input in "${option_inputs[@]}"; do + act "$input" done ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_rm "$@" fi diff --git a/commands/fs-size b/commands/fs-size deleted file mode 100755 index 5e24fa5b1..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 test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_quiet='no' option_paths=() - while test "$#" -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 test "${#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 test -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 test "$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 test "$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 test "$0" = "${BASH_SOURCE[0]}"; then - fs_size "$@" -fi diff --git a/commands/fs-speed b/commands/fs-speed index c3a327ddc..18594a4c9 100755 --- a/commands/fs-speed +++ b/commands/fs-speed @@ -22,7 +22,7 @@ function fs_speed() ( --group= If specified run the removal commands as this and . EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +30,7 @@ function fs_speed() ( # process local item option_paths=() option_user='' option_group='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function fs_speed() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -60,10 +60,10 @@ function fs_speed() ( # is broken: https://github.com/axboe/fio/blob/master/examples/fio-seq-write.fio function eval_wrapper { - while test "$1" = '--'; do + while [[ $1 == '--' ]]; do shift done - if test -n "$option_user" -o -n "$option_group"; then + if [[ -n $option_user || -n $option_group ]]; then sudo-helper --inherit --user="$option_user" --group="$option_group" -- "$@" else eval-helper --no-quiet -- "$@" @@ -87,7 +87,7 @@ function fs_speed() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_speed "$@" fi @@ -133,12 +133,12 @@ fi # --failure="$(echo-style --error='Failed calculate write.')" \ # -- dd if=/dev/urandom of="$scratchpad" bs="$bs" count="$count" 2>&1 | tee "$log" # bytes="$(echo-regexp -o --regexp='(\d+) bytes/sec' --replace='$1' <"$log")" -# if test -n "$bytes"; then +# if [[ -n "$bytes" ]]; then # megabytes="$(echo-math --precision=0 -- "$bytes / 1024 / 1024")" # echo-style 'Writes at ' --bold="$megabytes megabytes" ' a second.' # else # speed="$(echo-regexp -o --regexp='(\d+ [a-zA-Z]+)/s' --replace='$1' <"$log")" -# if test -n "$speed"; then +# if [[ -n "$speed" ]]; then # echo-style 'Writes at ' --bold="$speed" ' a second.' # fi # fi @@ -150,12 +150,12 @@ fi # --failure="$(echo-style --error='Failed calculate read.')" \ # -- dd if="$scratchpad" of=/dev/zero bs="$bs" 2>&1 | tee "$log" # bytes="$(echo-regexp -o --regexp='(\d+) bytes/sec' --replace='$1' <"$log")" -# if test -n "$bytes"; then +# if [[ -n "$bytes" ]]; then # megabytes="$(echo-math --precision=0 -- "$bytes / 1024 / 1024")" # echo-style 'Reads at ' --bold="$megabytes megabytes" ' a second.' # else # speed="$(echo-regexp -o --regexp='(\d+ [a-zA-Z]+)/s' --replace='$1' <"$log")" -# if test -n "$speed"; then +# if [[ -n "$speed" ]]; then # echo-style 'Reads at ' --bold="$speed" ' a second.' # fi # fi diff --git a/commands/fs-structure b/commands/fs-structure index f98e8563c..73d05bbfe 100755 --- a/commands/fs-structure +++ b/commands/fs-structure @@ -1,5 +1,32 @@ #!/usr/bin/env bash +# @todo support these: +# https://github.com/lsd-rs/lsd +# https://github.com/dduan/tre +# https://github.com/pls-rs/pls +# https://github.com/chaqchase/lla + +# don't need these, these are explorers, not listers: +# https://github.com/gokcehan/lf + +function fs_structure_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + set -x + fs-structure + fs-structure --no-eza + + fs-structure -- . + fs-structure --no-eza -- . + + fs-structure -- "$DOROTHY" + fs-structure --no-eza -- "$DOROTHY" + set +x + + echo-style --g1="TEST: $0" + return 0 +) function fs_structure() ( source "$DOROTHY/sources/bash.bash" @@ -15,18 +42,31 @@ function fs_structure() ( fs-structure [...options] [--] ... OPTIONS: - --sudo - If specified, use sudo when removing the files. + --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= + --user= + --group= + Forwarded to [sudo-helper], [is-missing], [fs-absolute], [is-empty-directory]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_paths=() option_sudo='no' - while test "$#" -ne 0; do + local item option_inputs=() option_sudo='' 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 case "$item" in @@ -34,63 +74,158 @@ 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 test "${#option_paths[@]}" -eq 0; then - help 'No s provided.' + if [[ ${#option_inputs[@]} -eq 0 ]]; then + option_inputs+=('.') fi # ===================================== # Action - # prepare - local cmd=() - if test "$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 - - # 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' '') + # 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, --all: show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories + # -A, --almost-all: Equivalent to –all; included for compatibility with ls -A. + # -M, --mounts: how mount details (Linux and Mac only) + # --absolute: display entries with their absolute path (on, follow, off) + # --total-size: show the size of a directory as the size of all files and directories inside (unix only) + # don't do absolute for now, as not sure if it is better with or without it + eza_cmd+=(eza -hlAM --total-size) + + # hide the permissions? + if [[ $option_perms == 'no' ]]; then + eza_cmd+=(--no-permissions --no-user) + else + eza_cmd+=(--group) + fi + + # hide the time? + if [[ $option_time == 'no' ]]; then + eza_cmd+=(--no-time) + fi + + # 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 - if test -d "$path"; then - cd "$path" - "${cmd[@]}" - else - cd "$(dirname "$path")" - basename="$(basename "$path")" - "${cmd[@]}" "$basename" | sd --fixed-strings "$basename" '' + # helpers + function __wrap { + sudo-helper --inherit --no-wrap --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$@" + } + function __list { + local path="$1" + if [[ ${#eza_cmd[@]} -ne 0 ]]; then + __wrap "${eza_cmd[@]}" "$path" + fi + if [[ ${#ls_cmd[@]} -ne 0 ]]; then + __wrap "${ls_cmd[@]}" "$path" + fi + } + + local input path + 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 + echo-style --error1='The path is missing: ' --code-error1="$input" >/dev/stderr + return 2 # ENOENT 2 No such file or directory + fi + + # now that we know it exists, get its path + path="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input")" + + # if it is empty, note it, as otherwise the output of eza and ls is unintuitive + if is-empty-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + echo-style --notice1='The directory is empty: ' --code-notice1="$path" + continue fi + + # it exists and isn't an empty directory, list its contents + __list "$path" done ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - fs_structure "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + fs_structure_test + else + fs_structure "$@" + fi fi diff --git a/commands/fs-temp b/commands/fs-temp index 8b4d77522..dd2c908ba 100755 --- a/commands/fs-temp +++ b/commands/fs-temp @@ -19,7 +19,7 @@ function fs_temp() ( --xdg If truthy, enforce XDG usage. If falsey, disable XDG usage. - If omitted, use system XDG prefernce. + If omitted, use system XDG preference. --root= | --inside= Use this as the root directory path. @@ -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. @@ -52,7 +53,7 @@ function fs_temp() ( This combination is to avoid your tooling complaining about existing files, while avoiding complaints about unable to write a new file to a non-existent path. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -67,8 +68,8 @@ function fs_temp() ( local option_prefix='' local option_suffix='' local option_extension='' - local option_touch='no' - while test "$#" -ne 0; do + local option_touch='' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -94,12 +95,12 @@ function fs_temp() ( done # if extension exists, ensure it starts with a . - if test -n "$option_extension" -a "${option_extension:0:1}" != '.'; then + if [[ -n $option_extension && ${option_extension:0:1} != '.' ]]; then option_extension=".${option_extension}" fi # ensure --file[=...] or --directory[=....] where provided - if test "${#option_files[@]}" -eq 0 -a "${#option_directories[@]}" -eq 0; then + if [[ ${#option_files[@]} -eq 0 && ${#option_directories[@]} -eq 0 ]]; then help "You must provide at least one --file[=] or --directory[=] option, so we know what we are generating." fi @@ -109,10 +110,10 @@ function fs_temp() ( local root directory file # generate the root path, that directories and the file will go inside - if test -n "$option_root"; then + if [[ -n $option_root ]]; then root="$option_root" else - if test "$option_xdg" = 'yes'; then + if [[ $option_xdg == 'yes' ]]; then root="$XDG_CACHE_HOME/dorothy" else root="$(mktemp -d)" @@ -121,9 +122,9 @@ function fs_temp() ( # add the directories to the root for directory in "${option_directories[@]}"; do - if test -z "$directory"; then + if [[ -z $directory ]]; then # generate a non-existent directory name - while true; do + while :; do directory="$option_prefix$(get-random-number)$option_suffix" if is-missing -- "$root/$directory"; then break @@ -134,24 +135,26 @@ 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 test "${#option_files[@]}" -eq 0; then + if [[ ${#option_files[@]} -eq 0 ]]; then __print_lines "$root" else # output as many filenames as requested for file in "${option_files[@]}"; do - if test -z "$file"; then + if [[ -z $file ]]; then # generate a non-existent filename - while true; do + while :; do file="$option_prefix$(get-random-number)$option_suffix$option_extension" if is-missing -- "$root/$file"; then break fi done fi - if test "$option_touch" = 'yes'; then + if [[ $option_touch == 'yes' ]]; then touch "$root/$file" fi __print_lines "$root/$file" @@ -160,6 +163,6 @@ function fs_temp() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then fs_temp "$@" fi diff --git a/commands/fs-trim b/commands/fs-trim new file mode 100755 index 000000000..34ab4154d --- /dev/null +++ b/commands/fs-trim @@ -0,0 +1,439 @@ +#!/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-does-not-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-test')" + # ensure anything left over from prior runs is removed + fs-rm --quiet --no-confirm -- "$root" + __mkdirp "$root" + # create a structure + 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 --no-confirm -- "$dir_target" + eval-tester --name='trim non-empty dir symlink should be no-op' \ + -- fs-trim --no-confirm -- "$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 empty file symlink should NOT remove its empty target and itself' \ + -- fs-trim --no-confirm -- "$file_symlink" + eval-tester --name='trim empty file symlink should NOT remove its empty target and itself (check)' \ + -- is-present -- "$file_target" "$file_symlink" + eval-tester --name='trim empty file symlink WITH --empty-files should remove its empty target and itself' --stdout="The following items were trimmed from $file_symlink"$'\n'"$file_target"$'\n'"$file_symlink" \ + -- env COLOR=no fs-trim --no-confirm --empty-files -- "$file_symlink" + eval-tester --name='trim empty file symlink WITH --empty-files 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 --no-confirm -- "$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' --stdout="The following items were trimmed from $root"$'\n'"$file_in_dir_target"$'\n'"$file_in_dir_symlink"$'\n'"$dir_target"$'\n'"$dir_symlink"$'\n'"$root" \ + -- env COLOR=no fs-trim --all --no-confirm -- "$root" + eval-tester --name='trim root with --all should trim everything (check)' \ + -- is-missing -- "$root" + + # recreate empty dirs and files + __mkdirp "$dir_target" + touch "$file_target" + touch "$file_in_dir_target" + symlink-helper --existing="$file_in_dir_target" --symlink="$file_in_dir_symlink" --quiet + rm -f "$file_in_dir_target" + eval-tester --name='trim root that has empty file, dir, and broken symlink, with --empty, should trim everything' --stdout="The following items were trimmed from $root"$'\n'"$file_target"$'\n'"$file_in_dir_symlink"$'\n'"$dir_target"$'\n'"$root" \ + -- env COLOR=no fs-trim --no-confirm --empty -- "$root" + eval-tester --name='trim root that has empty file, dir, and broken symlink, with --empty, should trim everything (check)' \ + -- is-missing -- "$root" + + # recreate empty dirs + __mkdirp "$dir_target" + eval-tester --name='trim root that has only empty dirs, should trim everything' --stdout="The following items were trimmed from $root"$'\n'"$dir_target"$'\n'"$root" \ + -- env COLOR=no fs-trim --no-confirm -- "$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" + + # prepare filenames + local cache_filenames=( + '.DS_Store' + '._.DS_Store' + 'Desktop.ini' + 'Thumbs.db' + '.log' + ) + local module_filenames=( + 'node_modules' + 'pnp' + 'package-lock.json' + 'yarn.lock' + '.pnp.js' + ) + local item cache_find=() module_find=() + for item in "${cache_filenames[@]}"; do + cache_find+=(-iname "$item" -or) + done + for item in "${module_filenames[@]}"; do + module_find+=(-iname "$item" -or) + done + cache_find=("${cache_find[@]:0:${#cache_find[@]}-1}") + module_find=("${module_find[@]:0:${#module_find[@]}-1}") + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Trim the s of commonly redundant files and directories. + + USAGE: + fs-trim [...options] [--] ... + + OPTIONS: + --quiet | --no-verbose + If quiet, do not output trimmed paths. + --confirm | --no-confirm + If enabled, confirm the actions. + If disabled, do not confirm the actions. + If empty, confirm only the default action. + + --sudo= + --user= + --group= + Forwarded to [sudo-helper] and various filesystem commands. + + --cache + If provided, paths of these case-insensitive filenames will be removed: ${cache_filenames[*]} + --module + If provided, paths of these case-insensitive filenames will be removed: ${module_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. + + --empty + Alias for --empty-files --broken-symlinks --empty-directories + + 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_quiet='' option_inputs=() option_sudo='' option_user='' option_group='' option_confirm='' option_all='' option_cache='' option_module='' option_empty_files='' option_broken_symlinks='' option_empty_directories='' + 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")" + ;; + '--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_cache='yes' + option_module='yes' + option_empty_files='yes' + option_broken_symlinks='yes' + option_empty_directories='yes' + fi + ;; + '--no-cache'* | '--cache'*) + option_cache="$(get-flag-value --affirmative --fallback="$option_cache" -- "$item")" + ;; + '--no-module'* | '--module'*) + option_module="$(get-flag-value --affirmative --fallback="$option_module" -- "$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")" + ;; + '--empty') + option_empty_files='yes' + option_broken_symlinks='yes' + option_empty_directories='yes' + ;; + '--') + 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.' # don't set to CWD, as trimming CWD is dangerous + fi + + # ===================================== + # Action + + local selection=() + if [[ $option_cache == 'yes' ]]; then + selection+=('cache') + fi + if [[ $option_module == 'yes' ]]; then + selection+=('module') + 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') + if [[ -z $option_confirm ]]; then + option_confirm='yes' + fi + fi + + # helpers + function __confirm { + local path="$1" title body + if [[ $option_confirm == 'yes' ]]; then + title="$( + echo-style --notice1='What items do you wish to trim for:' --newline \ + --code-notice1="$path" + )" + body="$(echo-style --reset)$(fs-structure --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path")" + mapfile -t selection < <( + choose "$title" "$body" --truncate-body --multiple --defaults-exact="$(__print_lines "${selection[@]}")" --label -- \ + cache "Caches: $(echo-style --newline --dim="${cache_filenames[*]}")" \ + module "Module Caches: $(echo-style --newline --dim="${module_filenames[*]}")" \ + files 'Empty files' \ + broken 'Broken symlinks' \ + directories 'Empty directories' + ) + fi + # bash v3 compat + if [[ ${#selection[@]} -eq 0 ]]; then + return 0 + fi + # still apply defaults in no-confirm mode + for item in "${selection[@]}"; do + case "$item" in + 'cache') option_cache='yes' ;; + 'module') option_module='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" -- "$@" + return + } + function do_find { + local path="$1" find_modifications + find_modifications="$(fs-temp --directory='fs-trim' --file)" + while is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; do + : >"$find_modifications" # clear + if [[ $option_cache == 'yes' ]]; then + __wrap find "$path" \( "${cache_find[@]}" \) -delete -print >>"$find_modifications" + fi + if [[ $option_module == 'yes' ]]; then + __wrap find "$path" \( "${module_find[@]}" \) -delete -print >>"$find_modifications" + fi + if [[ $option_empty_files == 'yes' ]]; then + __wrap find "$path" -type f -empty -delete -print >>"$find_modifications" + fi + if [[ $option_broken_symlinks == 'yes' ]]; then + __wrap find "$path" -type l -exec fs-trim-broken-symlinks.bash -- {} + >>"$find_modifications" + fi + if [[ $option_empty_directories == 'yes' ]]; then + __wrap find "$path" -type d -empty -delete -print >>"$find_modifications" + fi + if [[ ! -s $find_modifications ]]; then + # nothing was trimmed, find_modifications is empty + rm -f "$find_modifications" + break + else + cat "$find_modifications" + rm -f "$find_modifications" + fi + done + } + + # action + local input path target path_filename target_filename filenames selves trim_modifications + mapfile -t selves < <(fs-parents --self --root -- .) + trim_modifications="$(fs-temp --directory='fs-trim' --file)" + 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 + + # prevent deleting ourself which causes: shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory + path="$(fs-absolute --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$input")" + if is-needle --needle="$path" -- "${selves[@]}"; then + echo-style --error1='Denied trimming a lineage of the current working directory, change the working directory to elsewhere and try again: ' --code-error1="$path" >/dev/stderr + return 1 + fi + + # reset modifications + : >"$trim_modifications" + + # confirm the user for action if still ambiguous + __confirm "$path" + + # check we have something to do + if [[ $option_cache != 'yes' && $option_module != '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" -- "$path"; then + # it is a symlink + if is-broken-symlink --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + # it is broken, remove it if desired + if [[ $option_broken_symlinks == 'yes' ]]; then + __wrap rm -fv "$path" >>"$trim_modifications" + fi + continue + fi + + # get the target + target="$(fs-realpath -- "$path")" + + # check if it is a cache/module file + filenames=() + if [[ $option_cache == 'yes' ]]; then + filenames+=("${cache_filenames[@]}") + fi + if [[ $option_module == 'yes' ]]; then + filenames+=("${module_filenames[@]}") + fi + if [[ ${#filenames[@]} -ne 0 ]]; then + target_filename="$(fs-filename -- "$target")" + path_filename="$(fs-filename -- "$path")" + if is-needle --ignore-case --any --needle="$target_filename" --needle="$path_filename" -- "${filenames[@]}"; then + __wrap rm -fv "$target" "$path" >>"$trim_modifications" + 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" >>"$trim_modifications" + # 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 -fv "$path" >>"$trim_modifications" + fi + elif [[ $option_empty_files == 'yes' ]] && is-empty-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$target"; then + __wrap rm -fv "$target" "$path" >>"$trim_modifications" + fi + else + # check if it is a cache/module file + filenames=() + if [[ $option_cache == 'yes' ]]; then + filenames+=("${cache_filenames[@]}") + fi + if [[ $option_module == 'yes' ]]; then + filenames+=("${module_filenames[@]}") + fi + if [[ ${#filenames[@]} -ne 0 ]]; then + path_filename="$(fs-filename -- "$path")" + if is-needle --ignore-case --needle="$path_filename" -- "${filenames[@]}"; then + __wrap rm -fv "$path" >>"$trim_modifications" + continue + fi + fi + + # trim path + if is-directory --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + # it is a directory or symlink to a directory on the filesystem + do_find "$path" >>"$trim_modifications" + elif [[ $option_empty_files == 'yes' ]] && is-empty-file --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- "$path"; then + __wrap rm -fv "$path" >>"$trim_modifications" + fi + fi + + # output modifications + if [[ $option_quiet != 'yes' && -s $trim_modifications ]]; then + echo-style --notice1='The following items were trimmed from ' --code-notice1="$path" + # macos vs linux compat, linux wraps: https://github.com/bevry/dorothy/actions/runs/12093858728/job/33725316374#step:2:1270 + echo-regexp -gmn "^removed '(.+?)'\$" '$1' <"$trim_modifications" + fi + rm -f "$trim_modifications" + 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-broken-symlinks.bash b/commands/fs-trim-broken-symlinks.bash new file mode 100755 index 000000000..e8b278430 --- /dev/null +++ b/commands/fs-trim-broken-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-app b/commands/get-app index 6586e5786..19bcb60b2 100755 --- a/commands/get-app +++ b/commands/get-app @@ -3,7 +3,7 @@ function get_app() ( source "$DOROTHY/sources/bash.bash" - # @todo support linux systems, so [setup-util-1passsword] can detect successful install + # @todo support linux systems, so [setup-util-1password] can detect successful install local dirs=( "$HOME/Applications" '/Applications' @@ -33,7 +33,7 @@ function get_app() ( Searches the following paths for the application: $(__print_lines "${dirs[@]}") EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -41,7 +41,7 @@ function get_app() ( # process local item option_apps=() option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -61,7 +61,7 @@ function get_app() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test "${#option_apps[@]}" -eq 0; then + if [[ ${#option_apps[@]} -eq 0 ]]; then option_apps+=("$item") else help "An unrecognised argument was provided: $item" @@ -71,7 +71,7 @@ function get_app() ( done # check - if test "${#option_apps[@]}" -eq 0; then + if [[ ${#option_apps[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -80,7 +80,7 @@ function get_app() ( local name app dir path can_find was_found for name in "${option_apps[@]}"; do - if test -z "$name"; then + if [[ -z $name ]]; then return 1 fi if is-mac; then @@ -91,9 +91,9 @@ function get_app() ( was_found='no' for dir in "${dirs[@]}"; do path="$dir/$app" - if test -d "$path"; then + if [[ -d $path ]]; then was_found='yes' - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then __print_lines "$path" fi continue @@ -101,7 +101,7 @@ function get_app() ( done # check if found - if test "$was_found" = 'yes'; then + if [[ $was_found == 'yes' ]]; then continue fi # wasn't found @@ -113,8 +113,8 @@ function get_app() ( # only output if found local winget_status winget_stdout eval_capture --statusvar=winget_status --stdoutvar=winget_stdout -- winget.exe list --disable-interactivity "$name" - if test "$winget_status" -eq 0; then - if test "$option_quiet" != 'yes'; then + if [[ $winget_status -eq 0 ]]; then + if [[ $option_quiet != 'yes' ]]; then echo-last-line --stdin <<<"$winget_stdout" fi continue @@ -126,14 +126,14 @@ function get_app() ( local scoop_stdout scoop_stdout="$(scoop list "$name")" if grep --quiet --fixed-strings --regexp='----' <<<"$scoop_stdout"; then - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then echo-trim-padding -- "$scoop_stdout" | echo-last-line --stdin fi continue fi fi - if test "$can_find" = 'no'; then - if test "$option_quiet" != 'yes'; then + if [[ $can_find == 'no' ]]; then + if [[ $option_quiet != 'yes' ]]; then echo-error "Unable to detect application [$name] on WSL. Install Winget or Scoop." fi return 6 # ENXIO 6 Device not configured @@ -141,12 +141,12 @@ function get_app() ( # wasn't found return 1 elif is-headless; then - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then echo-style --notice="Skipping detection of application [$name] on headless system." >/dev/stderr fi return 19 # ENODEV 19 Operation not supported by device else - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then echo-error "Unable to detect application [$name] on Linux. Submit a PR to support it." fi return 19 # ENODEV 19 Operation not supported by device @@ -156,6 +156,6 @@ function get_app() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_app "$@" fi diff --git a/commands/get-arch b/commands/get-arch index 2899fad22..a765cf5e3 100755 --- a/commands/get-arch +++ b/commands/get-arch @@ -78,7 +78,7 @@ function get_arch() ( [0] if architecture was known [1] if architecture was unknown or could not be determined EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -86,7 +86,7 @@ function get_arch() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -101,7 +101,7 @@ function get_arch() ( local arch arch="$(uname -m)" # -i is only linux, -m is linux and apple, -p follows [arch ...] emulation - if test "$arch" = 'aarch64' -o "$arch" = 'arm64'; then + if [[ $arch == 'aarch64' || $arch == 'arm64' ]]; then __print_lines 'a64' # raspberry pi, apple silicon elif [[ $arch == x86_64* ]]; then if [[ "$(uname -v)" == *ARM64* ]]; then @@ -113,7 +113,7 @@ function get_arch() ( __print_lines 'x32' elif [[ $arch == arm* ]]; then __print_lines 'a32' - elif test "$arch" = 'riscv64'; then + elif [[ $arch == 'riscv64' ]]; then __print_lines 'r64' else return 1 @@ -121,6 +121,6 @@ function get_arch() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_arch "$@" fi diff --git a/commands/get-array b/commands/get-array index 78d3ffd79..b796a6dfe 100755 --- a/commands/get-array +++ b/commands/get-array @@ -23,7 +23,7 @@ function get_array() ( EXAMPLE: mapfile -t arr < <(get-array 10) EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function get_array() ( # process local item option_size='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function get_array() ( '--size='*) option_size="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_size"; then + if [[ -z $option_size ]]; then option_size="$item" else help "An unrecognised argument was provided: $item" @@ -49,7 +49,7 @@ function get_array() ( done # check - if test -z "$option_size"; then + if [[ -z $option_size ]]; then help ' is required' fi if ! is-integer -- "$option_size"; then @@ -66,6 +66,6 @@ function get_array() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_array "$@" fi diff --git a/commands/get-desktop-theme b/commands/get-desktop-theme index 7cedbeef2..f371da675 100755 --- a/commands/get-desktop-theme +++ b/commands/get-desktop-theme @@ -17,7 +17,7 @@ function get_desktop_theme() ( USAGE: get-desktop-theme EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -25,7 +25,7 @@ function get_desktop_theme() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -42,7 +42,7 @@ function get_desktop_theme() ( if is-mac; then # result="$(defaults read -globalDomain AppleInterfaceStyle || :)" <-- always reports Dark on macOS Ventura result="$(osascript -e 'tell application "System Events" to tell appearance preferences to get dark mode' || :)" # note that this prompts the user if they wish to allow terminal to access system events, so is undesirable - if test "$result" = 'true'; then + if [[ $result == 'true' ]]; then __print_lines 'dark' else __print_lines 'light' @@ -61,6 +61,6 @@ function get_desktop_theme() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_desktop_theme "$@" fi diff --git a/commands/get-devices b/commands/get-devices index f1d41a2d1..e494e533e 100755 --- a/commands/get-devices +++ b/commands/get-devices @@ -122,7 +122,7 @@ function get_devices() ( block Get device block size, e.g. [4096] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -159,7 +159,7 @@ function get_devices() ( count uuid ) - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -222,17 +222,17 @@ function get_devices() ( done # if select is provided, yet no results are provided, then default to node - if test -n "$option_select" -a "${#option_results[@]}" -eq 0; then + if [[ -n $option_select && ${#option_results[@]} -eq 0 ]]; then option_results=('node') fi # if no select is provided, yet results are provided, then ignore display - if test -z "$option_select" -a "${#option_results[@]}" -ne 0; then + if [[ -z $option_select && ${#option_results[@]} -ne 0 ]]; then option_details=() fi # if no missing, default to a generic message - if test -z "$option_missing"; then + if [[ -z $option_missing ]]; then option_missing='No matching devices found.' fi @@ -261,11 +261,11 @@ function get_devices() ( option="${!option}" local has="option_has_$name" has="${!has}" - if test -n "$option" -a "$option" != "$value"; then + if [[ -n $option && $option != "$value" ]]; then return 0 fi - if test -n "$has"; then - if test "$has" = 'yes' -a -z "$value" || test "$has" = 'no' -a -n "$value"; then + if [[ -n $has ]]; then + if [[ ($has == 'yes' && -z $value) || ($has == 'no' && -n $value) ]]; then return 0 fi fi @@ -273,9 +273,9 @@ function get_devices() ( } # debug - if test "$option_debug" = 'yes'; then + if [[ $option_debug == 'yes' ]]; then { - if test -n "$option_node"; then + if [[ -n $option_node ]]; then # model, serial, uuid, filesystem, size, block, label sudo-helper --wrap \ -- diskutil info "$option_node" || : @@ -336,10 +336,10 @@ function get_devices() ( local MOUNT_HAYSTACK='' MOUNT_RAID='' function get_all { MOUNT_HAYSTACK="$(mount)" - if test "$option_filesystem" = 'btrfs' || echo-regexp -q --regexp='btrfs' -- "$MOUNT_HAYSTACK"; then + if [[ $option_filesystem == 'btrfs' ]] || echo-regexp -q --regexp='btrfs' -- "$MOUNT_HAYSTACK"; then # if you attach a btrfs cluster to a new machine # it may not be completely discovered until the btrfs agent scans for the devices - # and btrfs supports mounting partial filesystems, which cause inumerable errors + # and btrfs supports mounting partial filesystems, which cause innumerable errors eval-helper --quiet -- btrfs-helper discover >"$terminal_device_file" MOUNT_HAYSTACK="$(mount)" fi @@ -356,7 +356,7 @@ function get_devices() ( # check node quickly eval_capture --statusvar=exclude_status -- check_exclude 'node' "$node" - if test "$exclude_status" -eq 0; then + if [[ $exclude_status -eq 0 ]]; then return 0 fi @@ -372,23 +372,23 @@ function get_devices() ( # IOContent is the type of device, not a UUID size="$(get_property 'Disk Size' "$temp")" # 5.0 TB (5034252828672 Bytes) (exactly 9832525056 512-Byte-Units) - if test -n "$size"; then + if [[ -n $size ]]; then size="${size%% (*}" # 5.0 TB fi block="$(get_property 'Device Block Size' "$temp")" # 512 Byte # fetch label last, as it is complicated, and overwrites temp label="$(get_property 'Volume Name' "$temp")" # volume device - if test -n "$label" -a "$label" != 'Not applicable (no file system)'; then + if [[ -n $label && $label != 'Not applicable (no file system)' ]]; then # if volume device, also has a filesystem filesystem="$(get_property 'Type [(]Bundle[)]' "$temp")" - elif test "$(get_property 'Content [(]IOContent[)]' "$temp")" = 'GUID_partition_scheme'; then + elif [[ "$(get_property 'Content [(]IOContent[)]' "$temp")" == 'GUID_partition_scheme' ]]; then # child raid device serial='' label='' temp="${node##*/}" # /dev/disk7 => disk7 temp="$(echo-regexp -o --regexp="${temp}s\d+" -- "$MOUNT_RAID")" - if test -n "$temp"; then + if [[ -n $temp ]]; then temp="$(sudo-helper -- diskutil info "$temp")" uuid="$(get_property 'Parent RAID Set UUID' "$temp")" label="$(get_property 'Parent Set Name' "$temp")" @@ -397,10 +397,10 @@ function get_devices() ( else # root device of volume label="$(get_property 'Set Name' "$temp")" - if test -z "$label"; then + if [[ -z $label ]]; then # probably child device of label temp="$(get_property 'APFS Physical Store' "$temp")" - if test -n "$temp"; then + if [[ -n $temp ]]; then temp="$(sudo-helper -- diskutil info "$temp")" uuid="$(get_property 'Disk [/] Partition UUID' "$temp")" label="$(get_property 'Set Name' "$temp")" @@ -413,7 +413,7 @@ function get_devices() ( fi # uuid - if test -n "$label" -a -n "$uuid"; then + if [[ -n $label && -n $uuid ]]; then # apfs count count_info="$(echo-regexp -sm --regexp="$uuid.+?====+" -- "$MOUNT_RAID")" count="$(echo-regexp -c --regexp='^[\d-] ' -- "$count_info")" @@ -422,13 +422,13 @@ function get_devices() ( # fetch mount mount="$(echo-regexp -o --regexp="$node on (.+?) \($filesystem" --replace='$1' -- "$MOUNT_HAYSTACK")" - if test -n "$filesystem" -a -z "$mount"; then + if [[ -n $filesystem && -z $mount ]]; then mount="$(echo-regexp -o --regexp="${node}s1 on (.+?) \($filesystem" --replace='$1' -- "$MOUNT_HAYSTACK")" fi else # fetch blkid properties temp="$(sudo-helper -- blkid "$node" || :)" # can fail if recently removed from a btrfs cluster - if test -n "$temp"; then + if [[ -n $temp ]]; then filesystem="$(get_key 'TYPE' "$temp")" label="$(get_key 'LABEL' "$temp")" uuid="$(get_key 'UUID' "$temp")" @@ -447,7 +447,7 @@ function get_devices() ( fi # fetch count - if test "$filesystem" = 'btrfs'; then + if [[ $filesystem == 'btrfs' ]]; then count_info="$(sudo-helper -- btrfs filesystem show "$node")" count="$(echo-regexp -o --regexp='Total devices ([0-9]+)' --replace='$1' -- "$count_info")" count_available="$(echo-regexp -c --regexp='devid' -- "$count_info")" @@ -457,19 +457,19 @@ function get_devices() ( local filter for filter in "${filters[@]}"; do eval_capture --statusvar=exclude_status -- check_exclude "$filter" "${!filter}" - if test "$exclude_status" -eq 0; then + if [[ $exclude_status -eq 0 ]]; then return 0 fi done # validate local result_status=0 - if test -n "$count"; then - if test "$count" != "$count_available" || echo-regexp -qi --regexp='(warning|error|missing)' -- "$count_info"; then + if [[ -n $count ]]; then + if [[ $count != "$count_available" ]] || echo-regexp -qi --regexp='(warning|error|missing)' -- "$count_info"; then echo-style --error="$node: $count_available/$count devices found" $'\n' "$(echo-lines --indent=$'\t' --stdin <<<"$count_info")" >/dev/stderr result_status=5 # EIO 5 Input/output error fi - if test -n "$option_count" -a "$option_label" = "$label" -a "$option_count" != "$count"; then + if [[ -n $option_count && $option_label == "$label" && $option_count != "$count" ]]; then echo-style --error="$node: $count_available/$count devices found, however we expected $option_count devices" $'\n' "$(echo-lines --indent=$'\t' --stdin <<<"$count_info")" >/dev/stderr result_status=5 # EIO 5 Input/output error fi @@ -477,8 +477,8 @@ function get_devices() ( # output local result - if test "${#option_results[@]}" -ne 0; then - if test "${#option_results[@]}" -eq 1; then + if [[ ${#option_results[@]} -ne 0 ]]; then + if [[ ${#option_results[@]} -eq 1 ]]; then printf '%s' "${!option_results[0]}" else for temp in "${option_results[@]}"; do @@ -487,8 +487,8 @@ function get_devices() ( fi printf '\n' fi - if test "${#option_details[@]}" -ne 0; then - if test "${#option_details[@]}" -eq 1; then + if [[ ${#option_details[@]} -ne 0 ]]; then + if [[ ${#option_details[@]} -eq 1 ]]; then printf '%s=[%s]' "${option_details[0]}" "${!option_details[0]}" else for temp in "${option_details[@]}"; do @@ -506,7 +506,7 @@ function get_devices() ( local status result echo-style --bold='Getting devices...' >"$terminal_device_file" eval_capture --statusvar=status --stdoutvar=result -- get_all - if test "$status" -ne 0; then + if [[ $status -ne 0 ]]; then return "$status" fi # erase [Getting devices...] message @@ -522,7 +522,7 @@ function get_devices() ( fi mapfile -t items <<<"$result" for item in "${items[@]}"; do - if test -z "$item"; then + if [[ -z $item ]]; then continue fi # reset @@ -530,17 +530,17 @@ function get_devices() ( # read IFS=$'\t' read -r node size model <<<"$item" # trim trailing whitespace from model - if test -n "$model"; then + if [[ -n $model ]]; then model="$(echo-trim-each-line -- "$model")" fi printf "$message\n" "$node" "$size" "$model" >"$terminal_device_file" # process eval_capture --statusvar=status --stdoutvar=result -- get_one "$node" "$size" "$model" # handle - if test -n "$result"; then + if [[ -n $result ]]; then results+=("$result") fi - if test "$status" -ne 0; then + if [[ $status -ne 0 ]]; then result_status="$status" else # erase [Getting device:] message @@ -549,23 +549,23 @@ function get_devices() ( done # check for failure - if test "$result_status" -ne 0; then - if test -z "$option_select"; then + if [[ $result_status -ne 0 ]]; then + if [[ -z $option_select ]]; then __print_lines "${results[@]}" fi return "$result_status" fi # check for missing failure - if test "${#results[@]}" -eq 0; then - if test "$option_quiet" != 'yes'; then + if [[ ${#results[@]} -eq 0 ]]; then + if [[ $option_quiet != 'yes' ]]; then echo-style --error="$option_missing" >/dev/stderr fi return 6 # ENXIO 6 Device not configured fi # all good, select one - if test -n "$option_select"; then + if [[ -n $option_select ]]; then # expand results into results and details mapfile -t results < <(__print_lines "${results[@]}") choose --required --confirm --label \ @@ -577,6 +577,6 @@ function get_devices() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_devices "$@" fi diff --git a/commands/get-file b/commands/get-file index 6149fb48c..b4d8a8748 100755 --- a/commands/get-file +++ b/commands/get-file @@ -19,12 +19,12 @@ function get_file() ( function help { cat <<-EOF >/dev/stderr ABOUT: - Get file type indentification of a . + Get file type identification of a . USAGE: fs-bytes [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function get_file() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function get_file() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -68,8 +68,8 @@ function get_file() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_file_test else get_file "$@" diff --git a/commands/get-filesystem b/commands/get-filesystem index 510575bbd..6141e8cc7 100755 --- a/commands/get-filesystem +++ b/commands/get-filesystem @@ -27,7 +27,7 @@ function get_filesystem() ( Attempts automation detection of appropriate type. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -61,7 +61,7 @@ function get_filesystem() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -73,7 +73,7 @@ function get_filesystem() ( '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -b "$item"; then + if [[ -b $item ]]; then get_filesystem_from_device "$item" elif is-present -- "$item"; then get_filesystem_from_target "$item" @@ -84,13 +84,13 @@ function get_filesystem() ( esac done - if test "$got" = 'no'; then + if [[ $got == 'no' ]]; then echo-error 'No filesystem was found. Make sure you specify a location. E.g. ' --code='get-filesystem --target=/' return 1 fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_filesystem "$@" fi diff --git a/commands/get-first-commit b/commands/get-first-commit index 79bf6068e..227af8ba3 100755 --- a/commands/get-first-commit +++ b/commands/get-first-commit @@ -21,7 +21,7 @@ function get_first_commit() ( The repository slug or url to get the first commit of. Defaults to using the current working directory. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -29,7 +29,7 @@ function get_first_commit() ( # process local item option_repo='' option_open='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -40,7 +40,7 @@ function get_first_commit() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_repo"; then + if [[ -z $option_repo ]]; then option_repo="$item" else help "An unrecognised argument was provided: $item" @@ -50,7 +50,7 @@ function get_first_commit() ( done # open - if test -z "$option_open"; then + if [[ -z $option_open ]]; then if is-headful; then option_open='yes' else @@ -63,7 +63,7 @@ function get_first_commit() ( # helper function act { - if test "$option_open" = 'no'; then + if [[ $option_open == 'no' ]]; then git-helper first-commit-entry else local url @@ -73,7 +73,7 @@ function get_first_commit() ( } local tempdir - if test -n "$option_repo"; then + if [[ -n $option_repo ]]; then # dependencies setup-util-gh --quiet @@ -89,6 +89,6 @@ function get_first_commit() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_first_commit "$@" fi diff --git a/commands/get-flag-value b/commands/get-flag-value index d8638c54a..ac34319c7 100755 --- a/commands/get-flag-value +++ b/commands/get-flag-value @@ -33,7 +33,7 @@ function get_flag_value_test() ( eval-tester --status=91 -- \ get-flag-value var --non-affirmative -- --other - # unamed, not found + # unnamed, not found eval-tester --status=91 -- \ get-flag-value -- @@ -72,7 +72,7 @@ function get_flag_value_test() ( eval-tester --stdout=one -- \ get-flag-value -- --flag=one --flag=two - # fallback, unamed + # fallback, unnamed eval-tester --stdout=missing -- \ get-flag-value var --fallback=missing -- '' @@ -182,11 +182,14 @@ function get_flag_value() ( If not provided, it is inferred from the first . --multi - Output the value for each occurence of the flag, instead of just the first. + Output the value for each occurrence of the flag, instead of just the first. --fallback= | --missing= When the flag is missing, use as the value. + --fallback-on-empty + When the flag is empty, use the fallback value. + --affirmative Parse the flag as an affirmative value, e.g. [yes] or [no] @@ -196,21 +199,22 @@ function get_flag_value() ( QUIRKS: It does not support [--flag value], only [--[no-]flag[=value]]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process - local item option_name='' option_multi=no option_fallback='' option_boolean='no' option_invert='no' - while test "$#" -ne 0; do + local item option_name='' option_multi=no option_fallback='' option_boolean='no' option_invert='no' option_fallback_on_empty='no' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--multi') option_multi='yes' ;; '--fallback='* | '--missing='*) option_fallback="${item#*=}" ;; + '--fallback-on-empty') option_fallback_on_empty='yes' ;; '--affirmative') option_boolean='yes' ;; '--non-affirmative') option_boolean='yes' @@ -219,7 +223,7 @@ function get_flag_value() ( '--') break ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_name"; then + if [[ -z $option_name ]]; then option_name="$item" else help "An unrecognised argument was provided: $item" @@ -231,7 +235,7 @@ function get_flag_value() ( # ===================================== # Action - local found='no' name inverted index value + local name inverted index value values=() had_nonempty_value='no' for item in "$@"; do # prepare name="$option_name" @@ -239,37 +243,36 @@ function get_flag_value() ( index=0 # check flag status - if test "${item:0:2}" != '--'; then + if [[ ${item:0:2} != '--' ]]; then # not a flag continue fi index=2 # check inversion - if test "${item:index:3}" == 'no-'; then + if [[ ${item:index:3} == 'no-' ]]; then # is inverted inverted='yes' index=5 fi # if we are looking for a specific flag, check it is so - if test -n "$name" -a "${item:index:${#name}}" != "$name"; then + if [[ -n $name && ${item:index:${#name}} != "$name" ]]; then # not our specific flag continue fi - found='yes' # fallback the name - if test -z "$name"; then + if [[ -z $name ]]; then name="${item:index}" name="${name%%=*}" fi # get the value value="${item:index+${#name}}" - if test -z "$value"; then + if [[ -z $value ]]; then value='yes' - elif test "${value:0:1}" = '='; then + elif [[ ${value:0:1} == '=' ]]; then # is a proper value, trim = value="${value:1}" else @@ -278,8 +281,8 @@ function get_flag_value() ( fi # convert the value if inverted, affirmative, or non-affirmative - if test "$option_boolean" = 'yes'; then - if test "$option_invert" = 'no'; then + if [[ $option_boolean == 'yes' ]]; then + if [[ $option_invert == 'no' ]]; then case "$value" in 'yes' | 'y' | 'true' | 'Y' | 'YES' | 'TRUE') value='yes' ;; 'no' | 'n' | 'false' | 'N' | 'NO' | 'FALSE') value='no' ;; @@ -291,36 +294,41 @@ function get_flag_value() ( esac fi fi - if test "$inverted" = 'yes'; then - if test "$value" = 'yes'; then + if [[ $inverted == 'yes' ]]; then + if [[ $value == 'yes' ]]; then value='no' - elif test "$value" = 'no'; then + elif [[ $value == 'no' ]]; then value='yes' fi fi # output - __print_lines "$value" + values+=("$value") + if [[ -n $value ]]; then + had_nonempty_value='yes' + fi # if single, one result is all we want - if test "$option_multi" = 'no'; then + if [[ $option_multi == 'no' ]]; then break fi done # handle missing case - if test "$found" = 'no'; then - if test -n "$option_fallback"; then + if [[ ($had_nonempty_value == 'no' && $option_fallback_on_empty == 'yes') || ${#values[@]} -eq 0 ]]; then + if [[ -n $option_fallback ]]; then __print_lines "$option_fallback" else return 91 # ENOMSG 91 No message of desired type fi + else + __print_lines "${values[@]}" fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_flag_value_test else get_flag_value "$@" diff --git a/commands/get-font b/commands/get-font index 69ad0d6f6..01f81506c 100755 --- a/commands/get-font +++ b/commands/get-font @@ -3,7 +3,7 @@ function get_font() ( source "$DOROTHY/sources/bash.bash" - # @todo support linux systems, so [setup-util-1passsword] can detect successful install + # @todo support linux systems, so [setup-util-1password] can detect successful install local dirs=() if is-mac; then dirs+=( @@ -28,7 +28,7 @@ function get_font() ( OPTIONS: | --font= - The font naem to get the full path of. Can be a glob pattern. + The font name to get the full path of. Can be a glob pattern. --dirs Only output the font directories that will be searched. @@ -40,7 +40,7 @@ function get_font() ( Searches the following paths for the font: $(__print_lines "${dirs[@]}") EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,7 +48,7 @@ function get_font() ( # process local item option_fonts=() option_dirs='no' option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -70,7 +70,7 @@ function get_font() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test "${#option_fonts[@]}" -eq 0; then + if [[ ${#option_fonts[@]} -eq 0 ]]; then option_fonts+=("$item") else help "An unrecognised argument was provided: $item" @@ -84,31 +84,31 @@ function get_font() ( # ensure the most desired font directory exists, and that undesired directories are trimmed # aka enforce xdg fonts directory on linux so that flatpak can detect the fonts - if test -d "$HOME/.fonts"; then - if test ! -d "${dirs[0]}"; then + if [[ -d "$HOME/.fonts" ]]; then + if [[ ! -d ${dirs[0]} ]]; then mv -v "$HOME/.fonts" "${dirs[0]}" >/dev/stderr else __mkdirp "${dirs[0]}" mv -v "$HOME/.fonts/"* "${dirs[0]}" >/dev/stderr - fs-rm --no-confirm --quiet -- "$HOME/.fonts" + fs-rm --quiet --no-confirm -- "$HOME/.fonts" fi else __mkdirp "${dirs[0]}" fi # if only dirs - if test "$option_dirs" = 'yes'; then + if [[ $option_dirs == 'yes' ]]; then __print_lines "${dirs[@]}" return 0 fi # get the font paths local font paths path - if test "${#option_fonts[@]}" -eq 0; then + if [[ ${#option_fonts[@]} -eq 0 ]]; then help 'No s provided.' fi for font in "${option_fonts[@]}"; do - if test -z "$font"; then + if [[ -z $font ]]; then return 1 fi # if no extension or glob, add it @@ -124,9 +124,9 @@ function get_font() ( expand-path -- "$dir/$font" ) for path in "${paths[@]}"; do - if test -f "$path"; then + if [[ -f $path ]]; then was_found='yes' - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then __print_lines "$path" fi fi @@ -134,7 +134,7 @@ function get_font() ( done # check if found - if test "$was_found" = 'yes'; then + if [[ $was_found == 'yes' ]]; then continue fi # wasn't found @@ -144,6 +144,6 @@ function get_font() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_font "$@" fi diff --git a/commands/get-github-release b/commands/get-github-release index 8ee9d0a2d..a6b974e86 100755 --- a/commands/get-github-release +++ b/commands/get-github-release @@ -217,7 +217,7 @@ function get_github_release() ( Enable using curl and awk to fetch the release. > If no specific techniques are specified, then autodetection is used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -225,7 +225,7 @@ function get_github_release() ( # process, @todo rewrite with option_ prefix local item slug='' alias='' tag='' id='' release='' action='' techniques=() technique='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -252,7 +252,7 @@ function get_github_release() ( '--diy') techniques+=('diy') ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$slug"; then + if [[ -z $slug ]]; then slug="$item" else help "An unrecognised argument was provided: $item" @@ -274,21 +274,21 @@ function get_github_release() ( # GITHUB_API_URL # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables # default to the default - if test -z "${GITHUB_API_URL-}"; then + if [[ -z ${GITHUB_API_URL-} ]]; then GITHUB_API_URL='https://api.github.com' # encounters rate limits when testing, even with authorization fi # if default, and if our CI, then use our caching proxy (however it currently doesn't return the same http error codes so ignore) - # if test "$GITHUB_API_URL" = 'https://api.github.com' -a "${GITHUB_ACTIONS-}" -a "${GITHUB_REPOSITORY-}" = 'bevry/dorothy'; then + # if [[ "$GITHUB_API_URL" = 'https://api.github.com' && -n "${GITHUB_ACTIONS-}" && "${GITHUB_REPOSITORY-}" = 'bevry/dorothy' ]]; then # GITHUB_API_URL='https://bevry.me/api/github' # caches results, to avoid rate limits # fi # GITHUB_HOSTNAME - if test -z "${GITHUB_HOSTNAME-}"; then + if [[ -z ${GITHUB_HOSTNAME-} ]]; then GITHUB_HOSTNAME='github.com' fi # GITHUB_TOKEN - if test -z "${GITHUB_TOKEN-}"; then + if [[ -z ${GITHUB_TOKEN-} ]]; then GITHUB_TOKEN='' if __command_exists -- gh; then @@ -318,7 +318,7 @@ function get_github_release() ( json="$(fetch_json_of_rate_limit)" # limit="$(extract_value_from_json_property_from_stdin 'limit' <<<"$json")" remaining="$(extract_value_from_json_property_from_stdin 'remaining' <<<"$json")" - if test "$remaining" -gt 5; then # we need at least a few requests to do anything, so 5 seems a reasonable limit + if [[ $remaining -gt 5 ]]; then # we need at least a few requests to do anything, so 5 seems a reasonable limit return 0 fi now="$(date-helper --unix)" @@ -328,50 +328,50 @@ function get_github_release() ( } # rate-limit action is here, to avoid the slug check for everything else - if test "$action" = 'rate-limit'; then + if [[ $action == 'rate-limit' ]]; then fetch_json_of_rate_limit return fi # enforcements - if test -z "$slug"; then + if [[ -z $slug ]]; then help 'A GitHub repository slug is required.' fi - if test -n "$release"; then + if [[ -n $release ]]; then # convert release into its modern format - if test "$release" = 'latest'; then + if [[ $release == 'latest' ]]; then echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --latest" - if test -n "$alias"; then + if [[ -n $alias ]]; then help "[--release=latest] and [--alias=$alias] are mutually exclusive, you should only use: --alias=$alias" fi alias='latest' elif is-integer -- "$release"; then echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --id=$release" - if test -n "$id"; then + if [[ -n $id ]]; then help "[--release=$release] and [--id=$id] are mutually exclusive, you should only use: --id=$id" fi id="$release" else echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --tag=$release" - if test -n "$tag"; then + if [[ -n $tag ]]; then help "[--release=$release] and [--tag=$tag] are mutually exclusive, you should only use: --tag=$tag" fi tag="$release" fi fi - if test -n "$alias" -a "$alias" != 'latest'; then + if [[ -n $alias && $alias != 'latest' ]]; then help ' can only be ' --code='latest' fi - if test -z "$alias" -a -z "$tag" -a -z "$id"; then + if [[ -z $alias && -z $tag && -z $id ]]; then # set default alias='latest' fi # defaults - if test -z "$action"; then + if [[ -z $action ]]; then action='tag' fi - if test "${#techniques[@]}" -eq 0; then + if [[ ${#techniques[@]} -eq 0 ]]; then if __command_exists -- gh; then techniques+=('gh') fi @@ -381,12 +381,12 @@ function get_github_release() ( techniques+=('diy') else for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then if __command_missing -- gh; then echo-error 'The GitHub CLI is required for the gh technique.' return 1 fi - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then if __command_missing -- jq; then echo-error 'The jq utility is required for the jq technique.' return 1 @@ -535,9 +535,9 @@ function get_github_release() ( wait_on_rate_limit # continue with the action - if test "$action" = 'releases'; then + if [[ $action == 'releases' ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_table_of_releases return else @@ -546,176 +546,176 @@ function get_github_release() ( fi done help "Unable to fetch the releases [slug = $slug, id = $id, alias = $alias, tag = $tag]" - elif test "$action" = 'release'; then + elif [[ $action == 'release' ]]; then # used to debug the other actions - if test -n "$id"; then + if [[ -n $id ]]; then fetch_json "$RELEASE_API_URL_FROM_ID" return fi - if test -n "$alias"; then + if [[ -n $alias ]]; then fetch_json "$RELEASE_API_URL_FROM_ALIAS" return fi - if test -n "$tag"; then + if [[ -n $tag ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_json_of_release_from_tag_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_json_of_release_from_tag_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_json_of_release_from_tag_via_diy return fi done fi help "Unable to fetch the release json [slug = $slug, id = $id, alias = $alias, tag = $tag]" - elif test "$action" = 'api-url'; then + elif [[ $action == 'api-url' ]]; then # used to debug the other actions - if test -n "$id"; then + if [[ -n $id ]]; then __print_lines "$RELEASE_API_URL_FROM_ID" return fi - if test -n "$alias"; then + if [[ -n $alias ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_api_url_of_release_from_alias_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_api_url_of_release_from_alias_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_api_url_of_release_from_alias_via_diy return fi done fi - if test -n "$tag"; then + if [[ -n $tag ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_api_url_of_release_from_tag_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_api_url_of_release_from_tag_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_api_url_of_release_from_tag_via_diy return fi done fi help "Unable to fetch the api url [slug = $slug, id = $id, alias = $alias, tag = $tag]" - elif test "$action" = 'tag'; then + elif [[ $action == 'tag' ]]; then # used by setup-util-elvish to fetch the latest tag name to then do a download from its website - if test -n "$tag"; then + if [[ -n $tag ]]; then __print_lines "$tag" return fi - if test -n "$id"; then + if [[ -n $id ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'jq'; then + if [[ $technique == 'jq' ]]; then fetch_tag_of_release_from_id_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_tag_of_release_from_id_via_diy return fi done fi - if test -n "$alias"; then + if [[ -n $alias ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_tag_of_release_from_alias_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_tag_of_release_from_alias_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_tag_of_release_from_alias_via_diy return fi done fi help "Unable to fetch the tag, likely because only a id with provided with gh [slug = $slug, id = $id, alias = $alias, tag = $tag]" - elif test "$action" = 'assets'; then + elif [[ $action == 'assets' ]]; then # used by setup-util-* to grab the appropriate asset for the current platform # id is first here, as id will incur less api calls - if test -n "$id"; then + if [[ -n $id ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'jq'; then + if [[ $technique == 'jq' ]]; then fetch_assets_of_release_from_id_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_assets_of_release_from_id_via_diy return fi done fi - if test -n "$alias"; then + if [[ -n $alias ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_assets_of_release_from_alias_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_assets_of_release_from_alias_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_assets_of_release_from_alias_via_diy return fi done fi - if test -n "$tag"; then + if [[ -n $tag ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_assets_of_release_from_tag_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_assets_of_release_from_tag_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_assets_of_release_from_tag_via_diy return fi done fi help "Unable to fetch the assets, likely because only a id was provided with gh [slug = $slug, id = $id, alias = $alias, tag = $tag]" - elif test "$action" = 'tar'; then - if test -n "$id"; then + elif [[ $action == 'tar' ]]; then + if [[ -n $id ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'jq'; then + if [[ $technique == 'jq' ]]; then fetch_tar_of_release_from_id_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_tar_of_release_from_id_via_diy return fi done fi - if test -n "$alias"; then + if [[ -n $alias ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_tar_of_release_from_alias_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_tar_of_release_from_alias_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_tar_of_release_from_alias_via_diy return fi done fi - if test -n "$tag"; then + if [[ -n $tag ]]; then for technique in "${techniques[@]}"; do - if test "$technique" = 'gh'; then + if [[ $technique == 'gh' ]]; then fetch_tar_of_release_from_tag_via_gh return - elif test "$technique" = 'jq'; then + elif [[ $technique == 'jq' ]]; then fetch_tar_of_release_from_tag_via_jq return - elif test "$technique" = 'diy'; then + elif [[ $technique == 'diy' ]]; then fetch_tar_of_release_from_tag_via_diy return fi @@ -729,8 +729,8 @@ function get_github_release() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_github_release_test else get_github_release "$@" diff --git a/commands/get-group-id b/commands/get-group-id index f401622a9..639cac216 100755 --- a/commands/get-group-id +++ b/commands/get-group-id @@ -15,7 +15,7 @@ function get_group_id() ( get-group-id get-group-id --group= EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function get_group_id() ( # process local item option_group='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -31,7 +31,7 @@ function get_group_id() ( '--group='*) option_group="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_group"; then + if [[ -z $option_group ]]; then option_group="$item" else help "An unrecognised argument was provided: $item" @@ -41,7 +41,7 @@ function get_group_id() ( done # check - if test -z "$option_group"; then + if [[ -z $option_group ]]; then help "No was provided" # ^ there is no fallback for this, as a user may be part of several groups, so the question is which one, e.g. [id -g] [id -G] [groups] # using [id -g] will result in: @@ -62,6 +62,6 @@ function get_group_id() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_group_id "$@" fi diff --git a/commands/get-hostname b/commands/get-hostname index 62c27bdc1..40892f9b8 100755 --- a/commands/get-hostname +++ b/commands/get-hostname @@ -15,7 +15,7 @@ function get_hostname() ( USAGE: get-hostname EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function get_hostname() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -40,6 +40,6 @@ function get_hostname() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_hostname "$@" fi diff --git a/commands/get-installer b/commands/get-installer index 0401514b3..d3f147fff 100755 --- a/commands/get-installer +++ b/commands/get-installer @@ -303,14 +303,14 @@ function get_installer() ( aliases['zrun']='setup-util-moreutils' # testing - if test "$*" = '--test'; then + if [[ $* == '--test' ]]; then echo-style --h1="TEST: $0" # test that the installers for the aliases actually exist local key value='' result=0 for key in "${!aliases[@]}"; do value="${aliases[$key]}" - if test "setup-util-$key" = "$value"; then + if [[ "setup-util-$key" == "$value" ]]; then echo-style --code="$key" ' => ' --code="$value" ' ❌ ' --error='no need for the alias' result=1 elif __command_missing -- "$value"; then @@ -322,7 +322,7 @@ function get_installer() ( done # return result - if test "$result" -eq 0; then + if [[ $result -eq 0 ]]; then echo-style --g1="TEST: $0" else echo-style --e1="TEST: $0" @@ -359,7 +359,7 @@ function get_installer() ( If invoke, forwarded to installer. Otherwise, used to suppress missing installer error. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -367,7 +367,7 @@ function get_installer() ( # process local item option_quiet='' option_utils=() option_invoke='no' option_first_success='no' option_first_failure='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -394,7 +394,7 @@ function get_installer() ( done # check - if test "${#option_utils[@]}" -eq 0; then + if [[ ${#option_utils[@]} -eq 0 ]]; then help 'No s specified.' fi @@ -410,7 +410,7 @@ function get_installer() ( # trim tap prefix # clementtsang/bottom/bottom => bottom util="${util##*/}" - # if util has a space character, then it must have flags or superflous spaces, trim them + # if util has a space character, then it must have flags or superfluous spaces, trim them if [[ $util == *' '* ]]; then # --classic code --classic => code # (^| ) matches either start of the line, or a space character @@ -421,48 +421,49 @@ function get_installer() ( fi # skip if empty - if test -z "$util"; then + if [[ -z $util ]]; then continue fi # find it installer="${aliases["$util"]-}" - if test -z "$installer"; then + if [[ -z $installer ]]; then installer="setup-util-$util" fi installer="$(echo-if-command-exists -- "$installer" || :)" # handle it - if test -z "$installer"; then + if [[ -z $installer ]]; then any_failure='yes' - if test "$option_quiet" != 'yes'; then + if [[ $option_quiet != 'yes' ]]; then echo-style --stderr --notice1='No installer found for: ' --code-notice1="$util" fi - if test "$option_first_failure" = 'yes'; then + if [[ $option_first_failure == 'yes' ]]; then return 1 fi - elif test "$option_invoke" = 'no'; then + elif [[ $option_invoke == 'no' ]]; then __print_lines "$installer" - if test "$option_first_success" = 'yes'; then + if [[ $option_first_success == 'yes' ]]; then return 0 fi elif "$installer" --quiet="$option_quiet"; then - if test "$option_first_success" = 'yes'; then + if [[ $option_first_success == 'yes' ]]; then return 0 fi else any_failure='yes' - if test "$option_first_failure" = 'yes'; then + if [[ $option_first_failure == 'yes' ]]; then return 1 fi fi done # check for failures - test "$any_failure" = 'no' + [[ $any_failure == 'no' ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_installer "$@" fi diff --git a/commands/get-json-api-url.awk b/commands/get-json-api-url.awk index 9721fabde..76a86b6f0 100755 --- a/commands/get-json-api-url.awk +++ b/commands/get-json-api-url.awk @@ -1,5 +1,5 @@ { - last_innner = "" + last_inner = "" url = "" while ( match($0, /"[^"]*"/) ) { inner = substr($0, RSTART + 1, RLENGTH - 2) diff --git a/commands/get-local-to-remote b/commands/get-local-to-remote index c056cc8e3..1756431eb 100755 --- a/commands/get-local-to-remote +++ b/commands/get-local-to-remote @@ -18,7 +18,7 @@ function get_local_to_remote() ( USAGE: get-local-to-remote EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,14 +26,14 @@ function get_local_to_remote() ( # process local item option_target='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_target"; then + if [[ -z $option_target ]]; then option_target="$item" else help "An unrecognised argument was provided: $item" @@ -43,7 +43,7 @@ function get_local_to_remote() ( done # check - if test -z "$option_target"; then + if [[ -z $option_target ]]; then help " is required" fi option_target="$(fs-realpath -- "$option_target")" @@ -53,20 +53,20 @@ function get_local_to_remote() ( local remotes result volume path server directory result mapfile -t remotes < <(get-volumes --remote | echo-trim-empty-lines --stdin) - if test "${#remotes[@]}" -eq 0; then + if [[ ${#remotes[@]} -eq 0 ]]; then help 'There were no remote volumes to convert ' --code="$option_target" fi result='' for remote in "${remotes[@]}"; do # skip empty ones - if test -z "$remote"; then + if [[ -z $remote ]]; then continue fi mapfile -t parts < <(echo-split $'\t' -- "$remote") volume="${parts[0]}" path="$(echo-regexp --regexp="^$volume(.+)" --replace='$1' -- "$option_target")" - if test -n "$path"; then + if [[ -n $path ]]; then server="${parts[1]}" directory="${parts[2]}" result="$server:$directory$path" @@ -74,7 +74,7 @@ function get_local_to_remote() ( fi done - if test -n "$result"; then + if [[ -n $result ]]; then local example_result example_result="$(echo-regexp --regexp='(.+)@(.+?)([.].+)?:(.+)' --replace='a-login-user@$2:/mnt$4' -- "$result")" echo-style 'The local to remote conversion for ' --code="$option_target" ' will probably be something like these, but not these, as the username will be a local login username, and the mount point will be the local mount point rather than the remote mount point, as such, these are only suggestions to guide what the correct combination could be:' --newline \ @@ -86,6 +86,6 @@ function get_local_to_remote() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_local_to_remote "$@" fi diff --git a/commands/get-macos-release-name b/commands/get-macos-release-name index 02ce14321..38d2710e5 100755 --- a/commands/get-macos-release-name +++ b/commands/get-macos-release-name @@ -14,7 +14,7 @@ function get_macos_release_name() ( USAGE: get-macos-release-name EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_macos_release_name() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -53,6 +53,6 @@ function get_macos_release_name() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_macos_release_name "$@" fi diff --git a/commands/get-min b/commands/get-min index 4cb2b6830..1db45d340 100755 --- a/commands/get-min +++ b/commands/get-min @@ -14,7 +14,7 @@ function get_min() ( USAGE: get-min [--] ... EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_min() ( # process local item option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,6 +44,6 @@ function get_min() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_min "$@" fi diff --git a/commands/get-opensuse-release b/commands/get-opensuse-release index 0f927fdda..d8b208452 100755 --- a/commands/get-opensuse-release +++ b/commands/get-opensuse-release @@ -17,7 +17,7 @@ function get_opensuse_release() ( QUIRKS: Returns [1] if not openSUSE, or if the check failed. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -25,7 +25,7 @@ function get_opensuse_release() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -38,16 +38,17 @@ function get_opensuse_release() ( # ===================================== # Action - if is-opensuse && test -f /etc/os-release; then + if is-opensuse && [[ -f /etc/os-release ]]; then # config-helper /etc/os-release -- \ # --field=NAME --field=VERSION | "$bin_gsed_or_sed" -e 's/"//g' -e 's/ /_/g' | echo-join '_' --stdin echo-regexp -fongm '^\s*(NAME|VERSION)="*(.+?)"*$' '$2' If provided, will compare the version against EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -29,16 +29,16 @@ function get_python_version() ( # process local item option_bin='' option_compare='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_bin"; then + if [[ -z $option_bin ]]; then option_bin="$item" - elif test -z "$option_compare"; then + elif [[ -z $option_compare ]]; then option_compare="$item" else help "An unrecognised argument was provided: $item" @@ -48,7 +48,7 @@ function get_python_version() ( done # ensure - if test -z "$option_bin"; then + if [[ -z $option_bin ]]; then option_bin='python' fi @@ -58,7 +58,7 @@ function get_python_version() ( # python outputs the version to stderr, so we have to redirect stderr to stdout for it to be captured version="$("$option_bin" --version 2>&1)" - if test -n "$option_compare"; then + if [[ -n $option_compare ]]; then if [[ $version == "Python $option_compare"* ]]; then return 0 else @@ -70,6 +70,6 @@ function get_python_version() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_python_version "$@" fi diff --git a/commands/get-random-number b/commands/get-random-number index 755d343a6..50ad085a2 100755 --- a/commands/get-random-number +++ b/commands/get-random-number @@ -14,7 +14,7 @@ function get_random_number() ( USAGE: get-random-number EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_random_number() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -36,7 +36,7 @@ function get_random_number() ( # Action # if our shell provides the RANDOM special variable use that, otherwise install fish and use its technique - if test -n "$RANDOM"; then + if [[ -n $RANDOM ]]; then __print_lines "$RANDOM" else setup-util-fish --quiet @@ -45,6 +45,6 @@ function get_random_number() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_random_number "$@" fi diff --git a/commands/get-size b/commands/get-size index da2036cc1..aabf2bee8 100755 --- a/commands/get-size +++ b/commands/get-size @@ -21,7 +21,7 @@ function get_size() ( If the path is a file or directory and dust is installed (can be installed via setup-util-dust), dust will be used to fetch size information, otherwise du will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -29,7 +29,7 @@ function get_size() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function get_size() ( # ===================================== # Action - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then if is-mac; then # size of the drives sudo-helper --wrap \ @@ -102,7 +102,7 @@ function get_size() ( sudo-helper --wrap \ -- btrfs balance status "$path" || : # if still running, exit code 1 - elif test "$path" = /; then + elif [[ $path == '/' ]]; then # root filesystem eval-helper --no-quiet \ -- df -h / @@ -113,7 +113,7 @@ function get_size() ( # -s, --apparent-size Use file length instead of blocks # -d, --depth Depth to show - elif test -f "$path"; then + elif [[ -f $path ]]; then # path is a file # -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G) eval-helper --no-quiet \ @@ -153,6 +153,6 @@ function get_size() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_size "$@" fi diff --git a/commands/get-terminal-alternative-support b/commands/get-terminal-alternative-support index 2656ced44..0e0643ec1 100755 --- a/commands/get-terminal-alternative-support +++ b/commands/get-terminal-alternative-support @@ -18,7 +18,7 @@ function get_terminal_alternative_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_alternative_support() ( # process local item option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function get_terminal_alternative_support() ( is-ci } - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then if __check; then return 1 fi @@ -61,6 +61,6 @@ function get_terminal_alternative_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_alternative_support "$@" fi diff --git a/commands/get-terminal-color-support b/commands/get-terminal-color-support index 7e7789062..3d38f4ae9 100755 --- a/commands/get-terminal-color-support +++ b/commands/get-terminal-color-support @@ -30,11 +30,16 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -42,7 +47,7 @@ function get_terminal_color_support() ( # process local item option_fallback='' option_quiet='' option_env='yes' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -64,7 +69,7 @@ function get_terminal_color_support() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_fallback"; then + if [[ -z $option_fallback ]]; then option_fallback="$item" else help "An unrecognised argument was provided: $item" @@ -80,7 +85,7 @@ function get_terminal_color_support() ( local color='' status # process arguments - if test "${#option_args[@]}" -ne 0; then + if [[ ${#option_args[@]} -ne 0 ]]; then for item in "${option_args[@]}"; do case "$item" in '--no-colors'* | '--colors'*) @@ -95,7 +100,7 @@ function get_terminal_color_support() ( fi # handle quiet and verbose modes - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then # quiet function process_status { : @@ -103,9 +108,9 @@ function get_terminal_color_support() ( else # verbose, output instead function process_status { - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then __print_lines 'yes' - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then __print_lines 'no' status=0 fi @@ -114,50 +119,50 @@ function get_terminal_color_support() ( fi # process arguments against env - if test -n "$color"; then + if [[ -n $color ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$color" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test -n "${COLOR-}"; then + if [[ -n ${COLOR-} ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$COLOR" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test -n "${NO_COLOR-}"; then + if [[ -n ${NO_COLOR-} ]]; then eval_capture --statusvar=status -- __is_non_affirmative -- "$NO_COLOR" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test -n "${NOCOLOR-}"; then + if [[ -n ${NOCOLOR-} ]]; then eval_capture --statusvar=status -- __is_non_affirmative -- "$NOCOLOR" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test -n "${CRON-}" -o -n "${CRONITOR_EXEC-}"; then + if [[ -n ${CRON-} || -n ${CRONITOR_EXEC-} ]]; then # cron strips nearly all env vars, these must be defined manually in [crontab -e] status=1 process_status return "$status" fi - if test -n "${TERM-}"; then + if [[ -n ${TERM-} ]]; then # cron strips TERM, however bash resets it to TERM=dumb # https://unix.stackexchange.com/a/411097 - if test "$TERM" = 'xterm-256color'; then + if [[ $TERM == 'xterm-256color' ]]; then # Visual Studio Code's integrated terminal reports TERM=xterm-256color status=0 process_status return "$status" - elif test "$TERM" = 'dumb'; then - if test -n "${GITHUB_ACTIONS-}"; then + elif [[ $TERM == 'dumb' ]]; then + if [[ -n ${GITHUB_ACTIONS-} ]]; then : # continue to fallback elif is-ci; then # if there are other CIs that support colors, they should be added to the prior check @@ -173,9 +178,9 @@ function get_terminal_color_support() ( fi # fallback - if test -n "$option_fallback"; then + if [[ -n $option_fallback ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$option_fallback" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi @@ -188,6 +193,6 @@ function get_terminal_color_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_color_support "$@" fi diff --git a/commands/get-terminal-cursor-line-and-column b/commands/get-terminal-cursor-line-and-column index 0c1243a7a..c187821d6 100755 --- a/commands/get-terminal-cursor-line-and-column +++ b/commands/get-terminal-cursor-line-and-column @@ -22,7 +22,7 @@ function get_terminal_cursor_line_and_column() ( [0] can detect terminal lines and columns [19] cannot detect terminal lines and columns EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +30,7 @@ function get_terminal_cursor_line_and_column() ( # process local item option_device_file='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -41,7 +41,7 @@ function get_terminal_cursor_line_and_column() ( esac done - if test -z "$option_device_file"; then + if [[ -z $option_device_file ]]; then option_device_file="$(get-terminal-device-file)" fi @@ -55,7 +55,7 @@ function get_terminal_cursor_line_and_column() ( # the read will complete immediately upon a response thanks to [-d R] which completes reading when the R is read, which is the final character of the response local _ line column IFS='[;' read -t 2 -srd R -p $'\e[6n' _ line column <"$option_device_file" - if test -n "${line-}" -a -n "${column-}"; then + if [[ -n ${line-} && -n ${column-} ]]; then __print_lines "$line" "$column" else return 19 # ENODEV 19 Operation not supported by device @@ -63,6 +63,6 @@ function get_terminal_cursor_line_and_column() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_cursor_line_and_column "$@" fi diff --git a/commands/get-terminal-device-file b/commands/get-terminal-device-file index 745f0d54c..e46eaba32 100755 --- a/commands/get-terminal-device-file +++ b/commands/get-terminal-device-file @@ -14,7 +14,7 @@ function get_terminal_device_file() ( USAGE: get-terminal-device-file [...options] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_terminal_device_file() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,6 +43,6 @@ function get_terminal_device_file() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_device_file "$@" fi diff --git a/commands/get-terminal-lines-and-columns b/commands/get-terminal-lines-and-columns index f2c294f31..2473a77c4 100755 --- a/commands/get-terminal-lines-and-columns +++ b/commands/get-terminal-lines-and-columns @@ -18,7 +18,7 @@ function get_terminal_lines_and_columns() ( [0] can detect terminal lines and columns [19] cannot detect terminal lines and columns EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_lines_and_columns() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,6 +43,6 @@ function get_terminal_lines_and_columns() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_lines_and_columns "$@" fi diff --git a/commands/get-terminal-lines-and-columns.bash b/commands/get-terminal-lines-and-columns.bash index 150dd515f..43adbcd17 100755 --- a/commands/get-terminal-lines-and-columns.bash +++ b/commands/get-terminal-lines-and-columns.bash @@ -1,6 +1,6 @@ #!/usr/bin/env bash -if test -n "${BASH_SUBSHELL-}" && test "$BASH_SUBSHELL" -ne 0; then +if [[ -n ${BASH_SUBSHELL-} && $BASH_SUBSHELL -ne 0 ]]; then # checkwinsize does not work inside subshells, so if running in a subshell then invoke to avoid subshell get-terminal-lines-and-columns.bash "$@" exit @@ -8,7 +8,7 @@ else # checkwinsize: If set, Bash checks the window size after each external (non-builtin) command and, if necessary, updates the values of LINES and COLUMNS. This option is enabled by default. shopt -s checkwinsize (:) # noop subshell which updates LINES and COLUMNS - if test -n "${LINES-}" -a -n "${COLUMNS-}"; then + if [[ -n ${LINES-} && -n ${COLUMNS-} ]]; then printf "%s\n" "$LINES" "$COLUMNS" else exit 19 # ENODEV 19 Operation not supported by device diff --git a/commands/get-terminal-position-support b/commands/get-terminal-position-support index 2c4dc1382..0aa71e7d9 100755 --- a/commands/get-terminal-position-support +++ b/commands/get-terminal-position-support @@ -18,7 +18,7 @@ function get_terminal_position_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_position_support() ( # process local item option_quiet='' option_device_file='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -43,7 +43,7 @@ function get_terminal_position_support() ( esac done - if test -z "$option_device_file"; then + if [[ -z $option_device_file ]]; then option_device_file="$(get-terminal-device-file)" fi @@ -58,11 +58,11 @@ function get_terminal_position_support() ( function __check { local _ line column IFS='[;' read -t 2 -srd R -p $'\e[6n' _ line column 2>/dev/null <"$option_device_file" || : - test -n "${line-}" -a -n "${column-}" + [[ -n ${line-} && -n ${column-} ]] return } - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then if __check; then return 0 fi @@ -75,6 +75,6 @@ function get_terminal_position_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_position_support "$@" fi diff --git a/commands/get-terminal-quiet-support b/commands/get-terminal-quiet-support index 61c201f3a..170499471 100755 --- a/commands/get-terminal-quiet-support +++ b/commands/get-terminal-quiet-support @@ -37,7 +37,7 @@ function get_terminal_quiet_support() ( [1] if disabled [91] if not determined (no fallback). EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -45,7 +45,7 @@ function get_terminal_quiet_support() ( # process local item option_fallback='' option_quiet='' option_env='yes' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -67,7 +67,7 @@ function get_terminal_quiet_support() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_fallback"; then + if [[ -z $option_fallback ]]; then option_fallback="$item" else help "An unrecognised argument was provided: $item" @@ -83,7 +83,7 @@ function get_terminal_quiet_support() ( local status verbose='' quiet='' # process arguments - if test "${#option_args[@]}" -ne 0; then + if [[ ${#option_args[@]} -ne 0 ]]; then for item in "${option_args[@]}"; do case "$item" in '--no-verbose'* | '--verbose'*) @@ -98,7 +98,7 @@ function get_terminal_quiet_support() ( fi # handle quiet and verbose modes - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then # quiet function process_status { : @@ -106,9 +106,9 @@ function get_terminal_quiet_support() ( else # verbose, output instead function process_status { - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then __print_lines 'yes' - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then __print_lines 'no' status=0 fi @@ -117,39 +117,39 @@ function get_terminal_quiet_support() ( fi # process arguments against env - if test -n "$verbose"; then + if [[ -n $verbose ]]; then eval_capture --statusvar=status -- __is_non_affirmative -- "$verbose" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test -n "$quiet"; then + if [[ -n $quiet ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$quiet" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test "$option_env" = 'yes' -a -n "${VERBOSE-}"; then + if [[ $option_env == 'yes' && -n ${VERBOSE-} ]]; then eval_capture --statusvar=status -- __is_non_affirmative -- "$VERBOSE" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi - if test "$option_env" = 'yes' -a -n "${QUIET-}"; then + if [[ $option_env == 'yes' && -n ${QUIET-} ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$QUIET" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi fi # fallback - if test -n "$option_fallback"; then + if [[ -n $option_fallback ]]; then eval_capture --statusvar=status -- __is_affirmative -- "$option_fallback" - if test "$status" -eq 0 -o "$status" -eq 1; then + if [[ $status -eq 0 || $status -eq 1 ]]; then process_status return "$status" fi @@ -162,6 +162,6 @@ function get_terminal_quiet_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_quiet_support "$@" fi diff --git a/commands/get-terminal-reactivity-support b/commands/get-terminal-reactivity-support index b7794c378..f9b71c286 100755 --- a/commands/get-terminal-reactivity-support +++ b/commands/get-terminal-reactivity-support @@ -18,7 +18,7 @@ function get_terminal_reactivity_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_reactivity_support() ( # process local item option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,9 +47,10 @@ function get_terminal_reactivity_support() ( function __check { [[ -t 0 ]] # this fails on GitHub Actions, if it passes on a CI, then we should add: && ! is-ci + return } - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then __check return elif __check; then @@ -60,6 +61,6 @@ function get_terminal_reactivity_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_reactivity_support "$@" fi diff --git a/commands/get-terminal-size-support b/commands/get-terminal-size-support index 9fc4ae454..5c9f8129a 100755 --- a/commands/get-terminal-size-support +++ b/commands/get-terminal-size-support @@ -18,7 +18,7 @@ function get_terminal_size_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_size_support() ( # process local item option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,7 +45,7 @@ function get_terminal_size_support() ( # ===================================== # Action - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then get-terminal-size-support.bash return elif get-terminal-size-support.bash; then @@ -56,6 +56,6 @@ function get_terminal_size_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_size_support "$@" fi diff --git a/commands/get-terminal-size-support.bash b/commands/get-terminal-size-support.bash index 23243dde6..0443130b0 100755 --- a/commands/get-terminal-size-support.bash +++ b/commands/get-terminal-size-support.bash @@ -1,6 +1,6 @@ #!/usr/bin/env bash -if test -n "${BASH_SUBSHELL-}" && test "$BASH_SUBSHELL" -ne 0; then +if [[ -n ${BASH_SUBSHELL-} && $BASH_SUBSHELL -ne 0 ]]; then # checkwinsize does not work inside subshells, so if running in a subshell then invoke to avoid subshell get-terminal-lines-and-columns.bash "$@" exit @@ -8,7 +8,7 @@ else # checkwinsize: If set, Bash checks the window size after each external (non-builtin) command and, if necessary, updates the values of LINES and COLUMNS. This option is enabled by default. shopt -s checkwinsize (:) # noop subshell which updates LINES and COLUMNS - if test -n "${LINES-}" -a -n "${COLUMNS-}"; then + if [[ -n ${LINES-} && -n ${COLUMNS-} ]]; then exit 0 else exit 19 # ENODEV 19 Operation not supported by device diff --git a/commands/get-terminal-theme b/commands/get-terminal-theme index 6e259caba..fe5cca35e 100755 --- a/commands/get-terminal-theme +++ b/commands/get-terminal-theme @@ -29,7 +29,7 @@ function get_terminal_theme() ( If a theme cannot be determined, use this instead. Defaults to [dark] EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -37,7 +37,7 @@ function get_terminal_theme() ( # process local item option_fallback='dark' option_clear_cache='no' option_refresh_cache='no' option_ignore_cache='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -52,7 +52,7 @@ function get_terminal_theme() ( esac done - if test "$option_fallback" != 'dark' -a "$option_fallback" != 'light' -a -n "$option_fallback"; then + if [[ $option_fallback != 'dark' && $option_fallback != 'light' && -n $option_fallback ]]; then help "Invalid fallback theme [$option_fallback] must either be empty, light, or dark" fi @@ -63,16 +63,16 @@ function get_terminal_theme() ( local cache_file # if clearing cache, only clear cache - if test "$option_ignore_cache" != 'yes'; then + if [[ $option_ignore_cache != 'yes' ]]; then cache_file="$(fs-temp --file='terminal-theme')" - if test "$option_clear_cache" = 'yes' -o "$option_refresh_cache" = 'yes'; then - if test -f "$cache_file"; then + if [[ $option_clear_cache == 'yes' || $option_refresh_cache == 'yes' ]]; then + if [[ -f $cache_file ]]; then rm "$cache_file" fi - if test "$option_clear_cache" = 'yes'; then + if [[ $option_clear_cache == 'yes' ]]; then return 0 fi - elif test -f "$cache_file"; then + elif [[ -f $cache_file ]]; then cat "$cache_file" return 0 fi @@ -87,7 +87,7 @@ function get_terminal_theme() ( # methods local theme='' function get_theme_via_fallback { - if test -n "$option_fallback"; then + if [[ -n $option_fallback ]]; then theme="$option_fallback" fi } @@ -103,7 +103,7 @@ function get_terminal_theme() ( fi } function get_theme_via_colorfgbg_env_var { - if test -n "${COLORFGBG-}"; then + if [[ -n ${COLORFGBG-} ]]; then # COLORFGBG contains segments of ANSI colors: # 12;8 β€” Foreground color code 12 and background color code 8. # 1;15;10 β€” Foreground color code 1, background color code 15, and alternate background color code 10. @@ -112,9 +112,9 @@ function get_terminal_theme() ( # default;default β€” Foreground color code default and background color code default. # ANSI colors are 0-15, in which 7 and 15 are light grey, and 9-15 are light colors. local bg="$COLORFGBG" - if test -n "$bg"; then + if [[ -n $bg ]]; then bg="${bg##*;}" # trim everything prior to the last ; - if test "$bg" = 'default' || test "$bg" -le 6 -o "$bg" -eq 8; then + if [[ $bg == 'default' ]] || (is-integer -- "$bg" && [[ $bg -le 6 || $bg -eq 8 ]]); then theme='light' else theme='dark' @@ -125,7 +125,7 @@ function get_terminal_theme() ( function get_theme_via_ansi_tty_query { # If the background color is `ba1a2b3c` the read will return `1a74/2b98/3cb6` local _ color='' r g b l - # stdin+stderr must be readable+writable for the read to work, but we can't check silently, as failures are noisey, and silencing the failures causes them to close: https://gist.github.com/balupton/6eee015345c663d7d7baf83d8e20ce1f so just note in this comment + # stdin+stderr must be readable+writable for the read to work, but we can't check silently, as failures are noisy, and silencing the failures causes them to close: https://gist.github.com/balupton/6eee015345c663d7d7baf83d8e20ce1f so just note in this comment # as terminal theme is really only important for TTY use cases, detecting TTY support solves vscode unable to ssh session into a machine if get-terminal-tty-support --quiet; then # /dev/tty as -s flag prevents output): @@ -137,7 +137,7 @@ function get_terminal_theme() ( # # use a timeout of 1 seconds, as 0.01 is too slow for vscode tunnels, which causes the respond to be to tty and not captured, causing a false guess to them... as [get-terminal-theme] now has a cache, the larger timeout is not a big deal IFS=: read -s -t 1 -d $'\a' -p $'\e]11;?\a' _ color 26 @@ -173,8 +173,8 @@ function get_terminal_theme() ( ) for method in "${methods[@]}"; do "$method" - if test -n "$theme"; then - if test "$option_ignore_cache" != 'yes'; then + if [[ -n $theme ]]; then + if [[ $option_ignore_cache != 'yes' ]]; then tee "$cache_file" <<<"$theme" fi return 0 @@ -186,6 +186,6 @@ function get_terminal_theme() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_theme "$@" fi diff --git a/commands/get-terminal-title-support b/commands/get-terminal-title-support index 241313024..33c43f234 100755 --- a/commands/get-terminal-title-support +++ b/commands/get-terminal-title-support @@ -18,7 +18,7 @@ function get_terminal_title_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_title_support() ( # process local item option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function get_terminal_title_support() ( is-ci || is-vscode } - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then if __check; then return 1 fi @@ -61,6 +61,6 @@ function get_terminal_title_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_title_support "$@" fi diff --git a/commands/get-terminal-tty-support b/commands/get-terminal-tty-support index 8162afa7f..de78b64dd 100755 --- a/commands/get-terminal-tty-support +++ b/commands/get-terminal-tty-support @@ -18,7 +18,7 @@ function get_terminal_tty_support() ( --quiet Instead outputting [yes] or [no], return an exit status of [0] if so, otherwise [1]. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then printf '%s\n' '' 'ERROR:' "$@" # don't get echo-style, as echo-style uses this fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function get_terminal_tty_support() ( # process local item option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function get_terminal_tty_support() ( (: /dev/tty) &>/dev/null } - if test "$option_quiet" = 'yes'; then + if [[ $option_quiet == 'yes' ]]; then __check return elif __check; then @@ -60,6 +60,6 @@ function get_terminal_tty_support() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_terminal_tty_support "$@" fi diff --git a/commands/get-ubuntu-release b/commands/get-ubuntu-release index c0d51c29d..d1c6a5db0 100755 --- a/commands/get-ubuntu-release +++ b/commands/get-ubuntu-release @@ -14,7 +14,7 @@ function get_ubuntu_release() ( USAGE: get-ubuntu-release EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_ubuntu_release() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,6 +39,6 @@ function get_ubuntu_release() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_ubuntu_release "$@" fi diff --git a/commands/get-ubuntu-release-name b/commands/get-ubuntu-release-name index 9a097f0bf..b8d5c79a4 100755 --- a/commands/get-ubuntu-release-name +++ b/commands/get-ubuntu-release-name @@ -14,7 +14,7 @@ function get_ubuntu_release_name() ( USAGE: get-ubuntu-release-name EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function get_ubuntu_release_name() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,6 +39,6 @@ function get_ubuntu_release_name() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_ubuntu_release_name "$@" fi diff --git a/commands/get-url-domain b/commands/get-url-domain index 0284004f5..71f4ba09d 100755 --- a/commands/get-url-domain +++ b/commands/get-url-domain @@ -54,7 +54,7 @@ function get_url_domain() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -74,8 +74,8 @@ function get_url_domain() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_url_domain_test else get_url_domain "$@" diff --git a/commands/get-url-protocol b/commands/get-url-protocol index 23734aac7..9cdc2f630 100755 --- a/commands/get-url-protocol +++ b/commands/get-url-protocol @@ -54,7 +54,7 @@ function get_url_protocol() ( # exit status: 0 EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -75,8 +75,8 @@ function get_url_protocol() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then get_url_protocol_test else get_url_protocol "$@" diff --git a/commands/get-url-upgrade b/commands/get-url-upgrade index 9e7ea8053..f5303a5b6 100755 --- a/commands/get-url-upgrade +++ b/commands/get-url-upgrade @@ -14,7 +14,7 @@ function get_url_upgrade() ( USAGE: get-url-upgrade EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,14 +22,14 @@ function get_url_upgrade() ( # process local item option_url='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url="$item" else help "An unrecognised argument was provided: $item" @@ -39,7 +39,7 @@ function get_url_upgrade() ( done # check - if test -z "$option_url"; then + if [[ -z $option_url ]]; then help " is required" fi @@ -49,26 +49,26 @@ function get_url_upgrade() ( function do_url_upgrade { local url="$1" protocol result - # remove superflous trailing slash + # remove superfluous trailing slash url="$(echo-regexp '([.][a-z]+)[/]$' '$1' -- "$url")" # test the original url protocol="$(get-url-protocol "$url" || :)" - if test -z "$protocol"; then + if [[ -z $protocol ]]; then # try http, which tries https first, otherwise fail __print_lines "[$url] missing protocol, will try variations" >/dev/stderr do_url_upgrade "http://$url" return - elif test "$protocol" = 'https'; then + elif [[ $protocol == 'https' ]]; then # test original url works if ! fetch --ok "$url"; then __print_lines "[$url] failing, will try variations" >/dev/stderr fi - elif test "$protocol" = 'http'; then + elif [[ $protocol == 'http' ]]; then # try upgrade to https result="${url//http\:/https\:}" result="$(do_url_upgrade "$result")" - if test -n "$result"; then + if [[ -n $result ]]; then __print_lines "$result" return 0 fi @@ -80,9 +80,9 @@ function get_url_upgrade() ( # try removing www result="${url//www./}" - if test "$result" != "$url"; then + if [[ $result != "$url" ]]; then result="$(do_url_upgrade "$result")" - if test -n "$result"; then + if [[ -n $result ]]; then __print_lines "$result" return 0 fi @@ -94,9 +94,9 @@ function get_url_upgrade() ( # try removing login items if echo-regexp -qi 'reg=|registration|verify|create|signup|signing|reset|token|join|register|logout|password|forgot|invitation|invite' -- "$url"; then result="$(get-url-domain "$url")" - if test "$result" != "$url"; then + if [[ $result != "$url" ]]; then result="$(do_url_upgrade "$result")" - if test -n "$result"; then + if [[ -n $result ]]; then __print_lines "$result" return 0 fi @@ -110,9 +110,9 @@ function get_url_upgrade() ( # try removing path if ? is not present if [[ $url != *'?'* ]]; then result="$(get-url-domain "$url")" - if test "$result" != "$url"; then + if [[ $result != "$url" ]]; then result="$(do_url_upgrade "$result")" - if test -n "$result"; then + if [[ -n $result ]]; then __print_lines "$result" return 0 fi @@ -122,9 +122,9 @@ function get_url_upgrade() ( # try removing path altogether result="$(get-url-domain "$url")" - if test "$result" != "$url"; then + if [[ $result != "$url" ]]; then result="$(do_url_upgrade"$result")" - if test -n "$result"; then + if [[ -n $result ]]; then __print_lines "$result" return 0 fi @@ -149,6 +149,6 @@ function get_url_upgrade() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_url_upgrade "$@" fi diff --git a/commands/get-user-id b/commands/get-user-id index 6870e61cf..2cb73bbb6 100755 --- a/commands/get-user-id +++ b/commands/get-user-id @@ -19,7 +19,7 @@ function get_user_id() ( QUIRKS: If no username is provided, the current user via [whoami] will be used. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function get_user_id() ( # process local item option_user='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -35,7 +35,7 @@ function get_user_id() ( '--user='* | '--username='*) option_user="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_user"; then + if [[ -z $option_user ]]; then option_user="$item" else help "An unrecognised argument was provided: $item" @@ -45,7 +45,7 @@ function get_user_id() ( done # fallback username to current user - if test -z "$option_user"; then + if [[ -z $option_user ]]; then option_user="$(whoami)" fi @@ -56,6 +56,6 @@ function get_user_id() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_user_id "$@" fi diff --git a/commands/get-volumes b/commands/get-volumes index c2d653552..c2105a887 100755 --- a/commands/get-volumes +++ b/commands/get-volumes @@ -19,7 +19,7 @@ function get_volumes() ( --remote Only output remote volumes, as a TSV of volume, server, directory. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -27,7 +27,7 @@ function get_volumes() ( # process local item option_remote='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -46,16 +46,16 @@ function get_volumes() ( local mount volumes=() mount="$(mount)" mapfile -t volumes < <(echo-regexp -ong --regexp='.+ on (.+) \(.+' --replace='$1' -- "$mount") - if test "${#volumes[@]}" -eq 0; then + if [[ ${#volumes[@]} -eq 0 ]]; then echo-style --error='No volumes found.' >/dev/stderr return 1 fi local volume source server directory - if test "$option_remote" = 'yes'; then + if [[ $option_remote == 'yes' ]]; then for volume in "${volumes[@]}"; do source="$(echo-regexp -o --regexp='//(.+) on '"$volume"' .+' --replace='$1' -- "$mount")" - if test -n "$source"; then + if [[ -n $source ]]; then server="$(echo-regexp -o --regexp='(.+)/.+' --replace='$1' -- "$source")" directory="$(echo-regexp -o --regexp='.+(/.+)' --replace='$1' -- "$source")" printf '%s\t%s\t%s\n' "$volume" "$server" "$directory" @@ -67,6 +67,6 @@ function get_volumes() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then get_volumes "$@" fi diff --git a/commands/git-helper b/commands/git-helper index a417b1393..abb4c7fbc 100755 --- a/commands/git-helper +++ b/commands/git-helper @@ -59,7 +59,7 @@ function git_helper() ( If no is specified, the preferred protocol will be selected. review - Open the git repositorty in your desired git reviewer, supported: + Open the git repository in your desired git reviewer, supported: Gitfox.app/gf Tower.app/tower @@ -73,13 +73,16 @@ function git_helper() ( current exit code 0 updated exit code 0 + umt | upstream-modification-times + Update the modification times of cloned files to that of their commits, rather than the time of cloning. + verify Verify the contains an initialised git repository. wipe Wipe the current working directory to the state of the last commit. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -87,7 +90,7 @@ function git_helper() ( # process local item action='' option_path='' option_verify='no' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -103,7 +106,7 @@ function git_helper() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else option_args+=("$item" "$@") @@ -115,15 +118,15 @@ function git_helper() ( done # ensure path, and verify - if test -z "$option_path"; then + if [[ -z $option_path ]]; then option_path="$(pwd)" - elif test ! -d "$option_path"; then + elif [[ ! -d $option_path ]]; then echo-style --error1='Unable to use git-helper on a non-existent directory: ' --code-error1="$option_path" >/dev/stderr return 2 # ENOENT 2 No such file or directory fi # check - if test -z "$action"; then + if [[ -z $action ]]; then help "No was provided." fi @@ -138,7 +141,7 @@ function git_helper() ( load_dorothy_config 'git.bash' # ensure - if test -z "$GIT_DEFAULT_BRANCH"; then + if [[ -z $GIT_DEFAULT_BRANCH ]]; then # try local first, then global GIT_DEFAULT_BRANCH="$( git config --local init.defaultBranch || git config --global init.defaultBranch || : @@ -153,15 +156,15 @@ function git_helper() ( # verify repository local git_status git_output eval_capture --statusvar=git_status --outputvar=git_output -- git status - if test "$git_status" -eq 128; then + if [[ $git_status -eq 128 ]]; then echo-style --error1='The directory is not a git repository: ' --code-error1="$option_path" >/dev/stderr - if test -n "$git_output"; then + if [[ -n $git_output ]]; then __print_lines "$git_output" >/dev/stderr fi return "$git_status" # makes more sense to continue inheriting the git exit status 128 than converting it to EFTYPE 79 Inappropriate file type or format - elif test "$git_status" -ne 0; then + elif [[ $git_status -ne 0 ]]; then echo-style --error1='Encountered an unknown git exit status ' --code-error1="$git_status" --error1=' when fetching the git status of the directory:' --code-error1="$option_path" >/dev/stderr - if test -n "$git_output"; then + if [[ -n $git_output ]]; then __print_lines "$git_output" >/dev/stderr fi return "$git_status" @@ -171,7 +174,7 @@ function git_helper() ( return 0 } function load_protocols { - if test "${#protocols[@]}" -eq 0; then + if [[ ${#protocols[@]} -eq 0 ]]; then if ssh-helper test &>/dev/null; then protocols+=('ssh') fi @@ -180,7 +183,7 @@ function git_helper() ( } function get_protocol { local protocol="${1:-"$GIT_PROTOCOL"}" - if test -n "$protocol"; then + if [[ -n $protocol ]]; then __print_lines "$protocol" else load_protocols @@ -193,18 +196,18 @@ function git_helper() ( # verify path, assumed verified for other actions local remote_status remote_output eval_capture --statusvar=remote_status --outputvar=remote_output -- git remote - if test "$remote_status" -eq 128; then + if [[ $remote_status -eq 128 ]]; then echo-style --error1='The directory is not a git repository: ' --code-error1="$option_path" >/dev/stderr - if test -n "$remote_output"; then + if [[ -n $remote_output ]]; then __print_lines "$remote_output" >/dev/stderr fi return "$status" # makes more sense to continue inheriting the git exit status 128 than converting it to EFTYPE 79 Inappropriate file type or format - elif test -z "$remote_output"; then + elif [[ -z $remote_output ]]; then echo-style --error1='The directory is a git repository without any remotes: ' --code-error1="$option_path" >/dev/stderr return 91 # ENOMSG 91 No message of desired type - elif test "$remote_status" -ne 0; then + elif [[ $remote_status -ne 0 ]]; then echo-style --error1='Encountered an unknown git exit status ' --code-error1="$remote_status" --error1=' when fetching the remotes of the directory:' --code-error1="$option_path" >/dev/stderr - if test -n "$remote_output"; then + if [[ -n $remote_output ]]; then __print_lines "$remote_output" >/dev/stderr fi return "$remote_status" @@ -214,7 +217,7 @@ function git_helper() ( } function get_remote_name { local remote="${1-}" question="${2:-"Which remote to use?"}" remotes - if test -n "$remote"; then + if [[ -n $remote ]]; then __print_lines "$remote" else # assume verified @@ -239,7 +242,7 @@ function git_helper() ( # ensure it changed, as git rules may have prevented it applied_url="$(get_remote_url "$remote_name")" - if test "$applied_url" != "$remote_url"; then + if [[ $applied_url != "$remote_url" ]]; then # it did not change, failure condition # output details @@ -253,7 +256,7 @@ function git_helper() ( # and check to see if it was git config rewrite rules to blame local global_lines='' local_lines='' global_lines="$(git config --global --list | grep --fixed-strings --regexp='.insteadof=')" - if test -n "$global_lines"; then + if [[ -n $global_lines ]]; then cat <<-EOF you have replacement rules in your global git config, remove them if you want this to work: @@ -261,14 +264,14 @@ function git_helper() ( EOF fi local_lines="$(git config --list | grep --fixed-strings --regexp='.insteadof=')" - if test -n "$local_lines"; then + if [[ -n $local_lines ]]; then cat <<-EOF you have replacement rules in your local git config, remove them if you want this to work: $local_lines EOF fi - if test -n "$local_lines" -o -n "$local_lines"; then + if [[ -n $local_lines || -n $local_lines ]]; then cat <<-EOF try run the following and try again: @@ -277,15 +280,17 @@ function git_helper() ( echo-regexp -gomn '^(.+)=.*$' 'git config --unset $1' -- "$local_lines" fi - # nore the failure + # note the failure return 1 fi } function __does_remote_branch_exist { git rev-parse --verify --quiet "$1" &>/dev/null + return } function __does_local_branch_exist { - test -n "$(git branch --list "$1")" + [[ -n "$(git branch --list "$1")" ]] + return } # ===================================== @@ -293,18 +298,22 @@ function git_helper() ( function get_authors { git shortlog --summary --email | cut -f2- + return } function get_github_slug { git remote -v | echo-regexp -o --regexp='.+?github[.]com[:/](.+?)[.]git.+' --replace='$1' + return } function get_first_commit_entry { git log --reverse --oneline | echo-first-line || : # || : to avoid exit code 141 + return } function get_first_commit_hash { get_first_commit_entry | echo-regexp -o --regexp='(.+?) .+' --replace='$1' + return } function get_first_commit_url { @@ -317,18 +326,18 @@ function git_helper() ( function author_update { # process local item old_email='' new_email='' new_name='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$old_email"; then + if [[ -z $old_email ]]; then old_email="$item" - elif test -z "$new_email"; then + elif [[ -z $new_email ]]; then new_email="$item" - elif test -z "$new_name"; then + elif [[ -z $new_name ]]; then new_name="$item" else help "An unrecognised argument was provided: $item" @@ -382,11 +391,12 @@ function git_helper() ( function get_current_branch { git rev-parse --abbrev-ref HEAD 2>/dev/null || : + return } function get_default_branch { # remote - if test -n "$GIT_DEFAULT_BRANCH" && __does_remote_branch_exist "$GIT_DEFAULT_BRANCH"; then + if [[ -n $GIT_DEFAULT_BRANCH ]] && __does_remote_branch_exist "$GIT_DEFAULT_BRANCH"; then __print_lines "$GIT_DEFAULT_BRANCH" return 0 elif __does_remote_branch_exist main; then @@ -398,7 +408,7 @@ function git_helper() ( fi # local - if test -n "$GIT_DEFAULT_BRANCH" && __does_local_branch_exist "$GIT_DEFAULT_BRANCH"; then + if [[ -n $GIT_DEFAULT_BRANCH ]] && __does_local_branch_exist "$GIT_DEFAULT_BRANCH"; then __print_lines "$GIT_DEFAULT_BRANCH" return 0 elif __does_local_branch_exist main; then @@ -410,9 +420,9 @@ function git_helper() ( fi # no branches? - if test -z "$(git branch -a)"; then + if [[ -z "$(git branch -a)" ]]; then # use default - if test -n "$GIT_DEFAULT_BRANCH"; then + if [[ -n $GIT_DEFAULT_BRANCH ]]; then git checkout -b "$GIT_DEFAULT_BRANCH" &>/dev/null __print_lines "$GIT_DEFAULT_BRANCH" return 0 @@ -439,7 +449,8 @@ function git_helper() ( } function is_shallow_clone { - test "$(git rev-parse --is-shallow-repository || :)" != 'false' + [[ "$(git rev-parse --is-shallow-repository || :)" != 'false' ]] + return } function update_protocol { @@ -466,7 +477,7 @@ function git_helper() ( EOF # apply difference if necessary - if test "$proposed_url" != "$original_url"; then + if [[ $proposed_url != "$original_url" ]]; then set_remote_url "$remote" "$proposed_url" # this will set a non-existent remote fi # @todo there is an [else] edge case here where the remote does not exist, in which the below will fail @@ -479,7 +490,7 @@ function git_helper() ( if __test_remote_name "$remote"; then return 0 else - if test "$protocol" = 'ssh'; then + if [[ $protocol == 'ssh' ]]; then if confirm --positive --ppid=$$ -- "Protocol [$protocol] failed, try HTTPS?"; then set_remote_url "$remote" "$https_url" test_with_fallback 'https' @@ -512,7 +523,7 @@ function git_helper() ( # fetch domain domain="${url%:*}" - if test -z "$domain" -o "$domain" = "$url"; then + if [[ -z $domain || $domain == "$url" ]]; then domain="${url%/*}" # trims repo domain="${domain%/*}" # trims user fi @@ -520,13 +531,13 @@ function git_helper() ( # fetch path path="${url#*:}" - if test -z "$path" -o "$path" = "$url"; then + if [[ -z $path || $path == "$url" ]]; then path="${url#*/}" fi - if test "$protocol" = 'https'; then + if [[ $protocol == 'https' ]]; then __print_lines "https://$domain/$path.git" - elif test "$protocol" = 'ssh'; then + elif [[ $protocol == 'ssh' ]]; then # ssh://git@github.com/balupton/dotfiles.git # and # git@github.com:balupton/dotfiles.git @@ -559,7 +570,7 @@ function git_helper() ( function git_update { local item option_remote_name='' option_remote_url='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -567,9 +578,9 @@ function git_helper() ( '--remote='* | '--remote-name='*) option_remote_name="${item#*=}" ;; '--remote-url='*) option_remote_url="${item#*=}" ;; *) - if test -z "$option_remote_name"; then + if [[ -z $option_remote_name ]]; then option_remote_name="$item" - elif test -z "$option_remote_url"; then + elif [[ -z $option_remote_url ]]; then option_args+=("$item" "$@") shift "$#" break @@ -588,7 +599,7 @@ function git_helper() ( # avoid merge conflicts git config pull.ff only # add/update the remote url if specified - if test -n "$option_remote_url"; then + if [[ -n $option_remote_url ]]; then set_remote_url "$option_remote_name" "$option_remote_url" fi # now that we know the remote name should exist, ensure it is the preferred protocol @@ -604,9 +615,9 @@ function git_helper() ( # @todo style this with eval-helper pending/success/failure local update_status eval_capture --statusvar=update_status --outputpipe=/dev/stderr -- do_update - if test "$update_status" -ne 0; then + if [[ $update_status -ne 0 ]]; then # update failed - echo-style --error1='git failed with exit status: ' --code-erro1="$update_status" >/dev/stderr + echo-style --error1='git failed with exit status: ' --code-error1="$update_status" >/dev/stderr __print_lines 'failure' return 1 elif grep --quiet --fixed-strings --regexp='Already' <"$PULL_OUTPUT_FILE"; then @@ -618,6 +629,25 @@ function git_helper() ( fi } + function git_umt { + # https://serverfault.com/a/1031956 + # git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0n1 -I_ git --no-pager log -1 --date=iso-local --format="%ad _" -- _ + # git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0n1 git --no-pager log -1 --date=iso-local --name-only -z --format='format:%ad' | perl -npe "INIT {\$/ = \"\\0\"} s@^(.*? .*?) .*?\n(.*)\$@\$date=\$1; \$name=\$2; \$name =~ s/'/'\"'\"'/sg; \"TZ=UTC touch -m --date '\$date' '\$name';\n\"@se" | bash + # git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0n1 -I_ git --no-pager log -1 --date=iso-local --format='%ad _' -- _ | echo-wait | echo-regexp -gonm '^(.+?) \+0000 (.+)$' "TZ=UTC touch -md '\$1' '\$2'" | bash + # ^ these all produce: xargs: warning: options --max-args and --replace/-I/-i are mutually exclusive, ignoring previous --max-args value + # this solves it: + # git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0 -I_ git --no-pager log -1 --date=iso-local --format='%ad _' -- _ | echo-wait | echo-regexp -gonm '^(.+?) \+0000 (.+)$' "TZ=UTC touch -md '\$1' '\$2'" | bash + # however this is far easier, more understandable, just as quick, and supports paths with % and quotes: + local subfiles=() subfile timestamp + mapfile -t subfiles < <(git ls-tree -r --name-only HEAD) + for subfile in "${subfiles[@]}"; do + timestamp="$(TZ=UTC git --no-pager log -1 --date=iso-local --format='%ad' -- "$subfile")" + # trim trailing +0000 + timestamp="${timestamp% +0000}" + TZ=UTC touch -md "$timestamp" "$subfile" + done + } + function git_wipe { if confirm --positive --ppid=$$ -- "Are you sure you want to wipe [$(pwd)] to the last commit state, reverting all uncommitted files and changes?"; then git reset --hard @@ -632,7 +662,7 @@ function git_helper() ( cd "$option_path" # verify? - if test "$option_verify" = 'yes'; then + if [[ $option_verify == 'yes' ]]; then verify_repository fi @@ -653,6 +683,7 @@ function git_helper() ( 'review') git_review "${option_args[@]}" ;; 'unstage') git_unstage "${option_args[@]}" ;; 'update') git_update "${option_args[@]}" ;; + 'umt' | 'upstream-modification-times') git_umt "${option_args[@]}" ;; 'verify') verify_repository "${option_args[@]}" ;; 'wipe') git_wipe "${option_args[@]}" ;; *) help "Unknown ." ;; @@ -660,6 +691,6 @@ function git_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then git_helper "$@" fi diff --git a/commands/github-download b/commands/github-download index e17a68309..22edd90b5 100755 --- a/commands/github-download +++ b/commands/github-download @@ -57,7 +57,7 @@ function github_download() ( --directory= Place downloaded file(s) inside . - If ommitted, the current working directory will be used. + If omitted, the current working directory will be used. --file= If only a single file was downloaded, rename it to . @@ -77,7 +77,7 @@ function github_download() ( To get the release identifier of a tag that hasn't been promoted to a release: fetch 'https://bevry.me/api/github/repos/jqlang/jq/releases' | jq -r '.[] | {tag_name, id}' EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -85,7 +85,7 @@ function github_download() ( # process, @todo rewrite with option_ local item option_quiet='yes' slug='' branch='' commit='' reference='' alias='' tag='' id='' release='' pathname='' asset_regexp='' archive_format='' archive_glob='' directory='' file='' filepath='' dry='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -126,81 +126,81 @@ function github_download() ( done # enforcements - if test -z "$slug"; then + if [[ -z $slug ]]; then help 'A GitHub repository slug is required.' fi - if test -n "$release"; then + if [[ -n $release ]]; then # convert release into its modern format - if test "$release" = 'latest'; then + if [[ $release == 'latest' ]]; then echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --latest" - if test -n "$alias"; then + if [[ -n $alias ]]; then help "[--release=latest] and [--alias=$alias] are mutually exclusive, you should only use: --alias=$alias" fi alias='latest' elif is-integer -- "$release"; then echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --id=$release" - if test -n "$id"; then + if [[ -n $id ]]; then help "[--release=$release] and [--id=$id] are mutually exclusive, you should only use: --id=$id" fi id="$release" else echo-style --tty --notice1='deprecation warning: ' --code-notice1="--release=$release β†’ --tag=$release" - if test -n "$tag"; then + if [[ -n $tag ]]; then help "[--release=$release] and [--tag=$tag] are mutually exclusive, you should only use: --tag=$tag" fi tag="$release" fi fi - if test -n "$alias" -a "$alias" != 'latest'; then + if [[ -n $alias && $alias != 'latest' ]]; then help ' can only be ' --code='latest' fi - if test -z "$release"; then + if [[ -z $release ]]; then # convert release properties into release, to make figuring out what we are doing easier - if test -n "$alias"; then + if [[ -n $alias ]]; then release="$alias" - elif test -n "$tag"; then + elif [[ -n $tag ]]; then release="$tag" - elif test -n "$id"; then + elif [[ -n $id ]]; then release="$id" fi fi - if test -z "$reference"; then - # convert references into references, as we actually juse use them as a reference - if test -n "$branch"; then + if [[ -z $reference ]]; then + # convert references into references, as we actually just use them as a reference + if [[ -n $branch ]]; then reference="$branch" - elif test -n "$tag"; then + elif [[ -n $tag ]]; then reference="$tag" - elif test -n "$commit"; then + elif [[ -n $commit ]]; then reference="$commit" fi fi # check for incompatibilities - if test -n "$release"; then - # don't check pathname, as tha tcan work for tag - if test -n "$branch" -o -n "$commit"; then + if [[ -n $release ]]; then + # don't check pathname, as that can work for tag + if [[ -n $branch || -n $commit ]]; then help "[--reference/head/branch/commit] and [--release/latest/alias/id] are mutually exclusive" fi fi - if test -n "$reference"; then + if [[ -n $reference ]]; then # don't check asset-regexp, as that can work for tag - if test -n "$alias" -o -n "$id"; then + if [[ -n $alias || -n $id ]]; then help "[--release/latest/alias/id] and [--reference/head/branch/commit] are mutually exclusive" fi fi - if test -n "$asset_regexp" -a "$pathname"; then + if [[ -n $asset_regexp && -n $pathname ]]; then help "[--asset-regexp] and [--pathname] are mutually exclusive, did you intend to use: --archive-glob=$pathname" - elif test -n "$asset_regexp"; then + elif [[ -n $asset_regexp ]]; then reference='' - elif test -n "$pathname"; then + elif [[ -n $pathname ]]; then release='' fi - if test -z "$reference" -a -z "$release"; then + if [[ -z $reference && -z $release ]]; then # set defaults - if test -n "$asset_regexp"; then + if [[ -n $asset_regexp ]]; then release='latest' alias='latest' - elif test -n "$pathname"; then + elif [[ -n $pathname ]]; then reference='HEAD' else release='latest' @@ -209,22 +209,22 @@ function github_download() ( fi # ensure directory, filename, filepath - if test -n "$filepath"; then + if [[ -n $filepath ]]; then # filepath is a directory + file combination filepath="$(fs-absolute -- "$filepath")" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -n "$directory" -a -n "$file"; then + elif [[ -n $directory && -n $file ]]; then # directory + file filepath="$(fs-absolute -- "$directory/$file")" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -z "$directory" -a -n "$file"; then + elif [[ -z $directory && -n $file ]]; then # file, without directory filepath="$(pwd)/$file" directory="$(dirname "$filepath")" file="$(basename "$filepath")" - elif test -n "$directory" -a -z "$file"; then + elif [[ -n $directory && -z $file ]]; then # directory, without file directory="$(fs-absolute -- "$directory")" filepath='' # it is for dir+file combos only @@ -239,7 +239,7 @@ function github_download() ( function get_assets { # we don't care for all assets, just the release in it's entirety - if test -z "$asset_regexp"; then + if [[ -z $asset_regexp ]]; then get-github-release --slug="$slug" --tar --alias="$alias" --tag="$tag" --id="$id" return fi @@ -247,7 +247,7 @@ function github_download() ( # get the assets [name, url] for the release local assets=() mapfile -t assets < <(get-github-release --slug="$slug" --assets --alias="$alias" --tag="$tag" --id="$id") - if test "${#assets[@]}" -eq 0; then + if [[ ${#assets[@]} -eq 0 ]]; then echo-error 'No release assets were found for repository ' --code="$slug" return 1 fi @@ -264,10 +264,10 @@ function github_download() ( if grep --quiet --extended-regexp --regexp='[.](asc|mini|sbom|sha256sum|sha256|sha|sig|zsync)$' <<<"$name"; then # ignore signature assets continue - elif test -z "$asset_regexp"; then + elif [[ -z $asset_regexp ]]; then # no asset filter, add them all matches+=("$url") - elif test "$asset_regexp" = "$name"; then + elif [[ $asset_regexp == "$name" ]]; then # exact match, use only that matches=("$url") break @@ -278,7 +278,7 @@ function github_download() ( done # filtering worked - if test "${#matches[@]}" -eq 0; then + if [[ ${#matches[@]} -eq 0 ]]; then { echo-style --error1='No assets matched the filter: ' --code-error1="$asset_regexp" echo-verbose -- "${assets[@]}" @@ -291,7 +291,7 @@ function github_download() ( } function get_ref_pathname_url { # determine branch - if test -z "$reference"; then + if [[ -z $reference ]]; then if fetch --ok "https://raw.githubusercontent.com/$slug/master/$pathname"; then reference='master' elif fetch --ok "https://raw.githubusercontent.com/$slug/main/$pathname"; then @@ -306,14 +306,14 @@ function github_download() ( } function download_reference { local url glob - if test -n "$pathname"; then + if [[ -n $pathname ]]; then url="$(get_ref_pathname_url)" glob="$archive_glob" else url="https://github.com/$slug/archive/$reference.tar.gz" glob="*-$reference/${archive_glob:-"*"}" fi - if test "$dry" = 'yes'; then + if [[ $dry == 'yes' ]]; then __print_lines "$url" return 0 fi @@ -328,12 +328,12 @@ function github_download() ( function download_release { local asset assets mapfile -t assets < <(get_assets) - if test "${#assets[@]}" -eq 0; then + if [[ ${#assets[@]} -eq 0 ]]; then echo-error 'No download assets were found for repository ' --code="$slug" return 1 fi for asset in "${assets[@]}"; do - if test "$dry" = 'yes'; then + if [[ $dry == 'yes' ]]; then __print_lines "$asset" continue fi @@ -350,9 +350,9 @@ function github_download() ( # ===================================== # Action - if test -n "$reference"; then + if [[ -n $reference ]]; then download_reference # prefer tag via reference, as is quicker than via release - elif test -n "$release"; then + elif [[ -n $release ]]; then download_release else help 'Invalid combination of options.' @@ -360,6 +360,6 @@ function github_download() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then github_download "$@" fi diff --git a/commands/gocryptfs-helper b/commands/gocryptfs-helper index 3e013fdc0..63e7415d9 100755 --- a/commands/gocryptfs-helper +++ b/commands/gocryptfs-helper @@ -35,7 +35,7 @@ function gocryptfs_helper() ( mount -- [--owner=] [--user=] [--group=] Mounts the vault at the mount-point. - unmount -- + unmount -- Unmounts the mount-point. upgrade -- @@ -47,7 +47,7 @@ function gocryptfs_helper() ( verify -- [--user=] [--group=] Verifies that the path is a gocryptfs vault. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -55,7 +55,7 @@ function gocryptfs_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -67,7 +67,7 @@ function gocryptfs_helper() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$action"; then + if [[ -z $action ]]; then action="$item" else help "An unrecognised argument was provided: $item" @@ -113,15 +113,15 @@ function gocryptfs_helper() ( local vault="${1-}" eval-helper --no-quiet --wrap \ -- gocryptfs --speed # dumps version info - if test -n "$vault"; then + if [[ -n $vault ]]; then eval-helper --no-quiet --wrap \ - -- gocryptfs --info "$vault" # dumsps vault info + -- gocryptfs --info "$vault" # dumps vault info fi } function act_verify { # process local item vault='' owner='' user='' group='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -130,7 +130,7 @@ function gocryptfs_helper() ( '--group='*) group="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$vault"; then + if [[ -z $vault ]]; then vault="$item" else help "An unrecognised argument was provided: $item" @@ -139,22 +139,8 @@ function gocryptfs_helper() ( esac done - local cmd=() cmd_status - if test -n "$user" -o -n "$group"; then - cmd+=( - 'sudo-helper' - "--user=$user" - "--group=$group" - '--' - ) - fi - cmd+=( - 'test' - '-f' - "$vault/gocryptfs.conf" - ) - eval_capture --statusvar=cmd_status -- "${cmd[@]}" - if test "$cmd_status" -eq 0; then + # check + if is-file --user="$user" --group="$group" -- "$vault/gocryptfs.conf"; then echo-style --code-good1="$vault" --good1=' is a gocryptfs vault' else echo-style --code-error1="$vault" --error1=' is not a gocryptfs vault' >/dev/stderr @@ -176,7 +162,7 @@ function gocryptfs_helper() ( )" # compare the features - if test "$old_vault_features" = "$new_vault_features"; then + if [[ $old_vault_features == "$new_vault_features" ]]; then echo-style --success="Vaults have the same features." result=0 else @@ -197,7 +183,7 @@ function gocryptfs_helper() ( function act_mount { # process local item mount_source='' mount_target='' owner='' user='' group='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -207,11 +193,11 @@ function gocryptfs_helper() ( '--group='*) group="${item#*=}" ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$mount_source"; then + if [[ -z $mount_source ]]; then mount_source="$item" - elif test -z "$mount_target"; then + elif [[ -z $mount_target ]]; then mount_target="$item" - elif test -z "$owner"; then + elif [[ -z $owner ]]; then owner="$item" else help "An unrecognised argument was provided: $item" @@ -221,13 +207,13 @@ function gocryptfs_helper() ( done # check - if test -z "$mount_source" -o -z "$mount_target"; then + if [[ -z $mount_source || -z $mount_target ]]; then help "No s provided." fi # construct command local mount_cmd=() ls_cmd=() - if test -n "$owner" -o -n "$user" -o -n "$group"; then + if [[ -n $owner || -n $user || -n $group ]]; then # https://github.com/rfjakob/gocryptfs/issues/638#issuecomment-1009578054 mount_cmd+=( 'sudo-helper' @@ -248,7 +234,7 @@ function gocryptfs_helper() ( '--noprealloc' '--allow_other' ) - if test -n "$owner"; then + if [[ -n $owner ]]; then mount_cmd+=( '--force_owner' "$owner" @@ -271,7 +257,7 @@ function gocryptfs_helper() ( -- "${mount_cmd[@]}" eval-helper --no-quiet --wrap \ -- "${ls_cmd[@]}" - if test -n "$owner" -o -n "$user" -o -n "$group"; then + if [[ -n $owner || -n $user || -n $group ]]; then eval-helper --no-quiet --wrap \ -- fs-own --owner="$owner" --user="$user" --group="$group" \ -- "$mount_target" @@ -296,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" @@ -304,7 +290,7 @@ function gocryptfs_helper() ( { echo-style --error="Something already existed at [$new_vault]..." echo-style --notice='Leaving for you to figure out.' - eval-helper --no-quiet --wrap -- ls -la "$new_vault" + fs-structure -- "$new_vault" } >/dev/stderr return 1 fi @@ -326,32 +312,32 @@ function gocryptfs_helper() ( --label --default="$algorithm" -- aessiv 'AES-SIV (RFC5297) - Best for AES Hardware Acceleration' xchacha 'XChaCha20-Poly1305 β€” Best for Raspberry Pi' )" - # prepare temporary mountpoints + # prepare temporary mount points temp_dir="$(fs-temp --directory='gocryptfs-helper' --directory)" new_vault="$(fs-temp --root="$temp_dir" --directory='new_vault')" old_plain="$(fs-temp --root="$temp_dir" --directory='old_plain')" new_plain="$(fs-temp --root="$temp_dir" --directory='new_plain')" # ensure clean exit - if test "$0" != "${BASH_SOURCE[0]}"; then + if [[ $0 != "${BASH_SOURCE[0]}" ]]; then echo-error '[gocryptfs-helper migrate] requires being run as a command, not a function, such that cleanup always occurs, even on failures.' return 1 # EPERM 1 Operation not permitted fi function on_gocryptfs_finish { mount-helper --unmount --target="$old_plain" || : mount-helper --unmount --target="$new_plain" || : - fs-rm --quiet -- "$temp_dir" || : + fs-rm --quiet --no-confirm-if-empty -- "$temp_dir" || : } trap on_gocryptfs_finish EXIT # create __print_line eval_capture --statusvar=verify_status -- act_verify "$new_vault" - if test "$verify_status" -eq 0; then + if [[ $verify_status -eq 0 ]]; then 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" @@ -360,7 +346,7 @@ function gocryptfs_helper() ( echo-style \ --error="Something already existed at [$new_vault] which was not a known vault structure..." --newline \ --notice='Leaving for you to figure out.' - eval-helper --no-quiet --wrap -- ls -la "$new_vault" + fs-structure -- "$new_vault" } >/dev/stderr return 1 fi @@ -368,7 +354,7 @@ function gocryptfs_helper() ( # verify there is a difference eval_capture --statusvar=compare_status -- act_compare "$old_vault" "$new_vault" - if test "$compare_status" -eq 0; then + if [[ $compare_status -eq 0 ]]; then echo-style --error='Vault migration is nonsensical, as both vaults have the same features.' >/dev/stderr return 1 fi @@ -385,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 \ @@ -411,7 +397,7 @@ function gocryptfs_helper() ( # ===================================== # Act - if test "$(type -t "act_$action")" = 'function'; then + if [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" "${option_args[@]}" else echo-style --stderr --error1="Action not yet implemented: " --code-error1="$action" @@ -420,6 +406,6 @@ function gocryptfs_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then gocryptfs_helper "$@" fi diff --git a/commands/gpg-helper b/commands/gpg-helper index 738771b93..24dcd2ac3 100755 --- a/commands/gpg-helper +++ b/commands/gpg-helper @@ -106,7 +106,7 @@ function gpg_helper() ( Encrypt a file using a passphrase instead. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -114,7 +114,7 @@ function gpg_helper() ( # process local item action='' option_args=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -190,7 +190,7 @@ function gpg_helper() ( } function get_date { local date="${1-}" - if test -n "$date"; then + if [[ -n $date ]]; then __print_lines "$date" else date-helper --gpg --next-year @@ -218,14 +218,14 @@ function gpg_helper() ( # configure if confirm --positive --ppid=$$ -- 'Apply sensible defaults?'; then # http://github.com/isaacs/github/issues/675 - # ^ no longer necessary, and intereferes with gpg edit key + # ^ no longer necessary, and interferes with gpg edit key config-helper --file="$GNUPGHOME/gpg.conf" -- \ --find='no-tty' --replace='' # pinentry local agent_file agent_program agent_program="$(type -P pinentry)" - if test -n "$agent_program"; then + if [[ -n $agent_program ]]; then agent_file="$GNUPGHOME/gpg-agent.conf" touch "$agent_file" config-helper --file="$agent_file" -- \ @@ -251,7 +251,7 @@ function gpg_helper() ( function act_list { local mode="${1-}" mode="$(get_mode "$mode")" - if test "$mode" = 'private'; then + if [[ $mode == 'private' ]]; then gpg --keyid-format LONG -K else gpg --keyid-format LONG -k @@ -262,7 +262,7 @@ function gpg_helper() ( function act_get { local mode="${1-}" key="${2-}" flag type keys=() mode="$(get_mode "$mode")" - if test "$mode" = 'private'; then + if [[ $mode == 'private' ]]; then flag="K" type='sec' else @@ -306,7 +306,7 @@ function gpg_helper() ( local mode="${1-}" key="${2-}" mode="$(get_mode "$mode")" key="$(act_get "$mode" "$key")" - if test "$mode" = 'private'; then + if [[ $mode == 'private' ]]; then gpg --armor --export-options backup --export-secret-keys "$key" else gpg --armor --export-options backup --export "$key" @@ -319,7 +319,7 @@ function gpg_helper() ( mode="$(get_mode "$mode")" key="$(act_get "$mode" "$key")" if confirm --bool --ppid=$$ -- "Confirm you wish to delete [$mode] key [$key]?"; then - if test "$mode" = 'private'; then + if [[ $mode == 'private' ]]; then gpg --delete-secret-keys "$key" else gpg --delete-keys "$key" @@ -385,10 +385,10 @@ function gpg_helper() ( file="${1-}" them="${2-}" you="${3-}" # "$(act_get private "${3-}")" - if test -z "$file" -o -z "$them"; then + if [[ -z $file || -z $them ]]; then help "encrypt requires file and their user id" fi - if test -n "$you"; then + if [[ -n $you ]]; then gpg -ase -r "$them" -u "$you" "$file" else gpg -ase -r "$them" "$file" @@ -398,7 +398,7 @@ function gpg_helper() ( # decrypt function act_decrypt { local file="${1-}" - if test -z "$file"; then + if [[ -z $file ]]; then help "decrypt requires file" fi gpg -d "$file" @@ -407,7 +407,7 @@ function gpg_helper() ( # symmetric function act_symmetric { local file="${1-}" - if test -z "$file"; then + if [[ -z $file ]]; then help "symmetric encryption requires file" fi gpg -c "$file" @@ -416,7 +416,7 @@ function gpg_helper() ( # ===================================== # Act - if test "$(type -t "act_$action")" = 'function'; then + if [[ "$(type -t "act_$action")" == 'function' ]]; then "act_$action" "${option_args[@]}" else echo-style --stderr --error1="Action not yet implemented: " --code-error1="$action" @@ -425,6 +425,6 @@ function gpg_helper() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then gpg_helper "$@" fi diff --git a/commands/gravatar b/commands/gravatar index b0e5c60e3..e58493ffa 100755 --- a/commands/gravatar +++ b/commands/gravatar @@ -18,7 +18,7 @@ function gravatar() ( --open Open the gravatar in your web browser. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function gravatar() ( # process local item option_email='' option_open='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -36,7 +36,7 @@ function gravatar() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_email"; then + if [[ -z $option_email ]]; then option_email="$item" else help "An unrecognised argument was provided: $item" @@ -59,12 +59,12 @@ function gravatar() ( hash="$(echo-checksum --algorithm=md5 -- "$option_email")" url="https://www.gravatar.com/avatar/${hash}?s=2048" __print_lines "$url" - if test "$option_open" = 'yes'; then + if [[ $option_open == 'yes' ]]; then open "$url" fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then gravatar "$@" fi diff --git a/commands/grub-helper b/commands/grub-helper index fcae1d5b2..718ed077d 100755 --- a/commands/grub-helper +++ b/commands/grub-helper @@ -24,7 +24,7 @@ function grub_helper() ( USAGE: grub-helper EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function grub_helper() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -56,19 +56,19 @@ function grub_helper() ( sudo-helper -- update-grub elif __command_exists -- grub-mkconfig:; then # https://itsfoss.com/update-grub/ - # this can have errors, so perfer update-grub + # this can have errors, so prefer update-grub sudo-helper -- grub-mkconfig -o /boot/grub/grub.cfg elif is-linux; then # fedora uses grubby it seems, as grub2-common doesn't provide update-grub on fedora echo-style --error="Your Linux distribution is currently unsupported." >/dev/stderr return 46 # EPFNOSUPPORT 46 Protocol family not supported else - echo-style --error="This command is only intended for Linux distrubtions that use GRUB." >/dev/stderr + echo-style --error="This command is only intended for Linux distributions that use GRUB." >/dev/stderr return 45 # ENOTSUP 45 Operation not supported fi ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then grub_helper "$@" fi diff --git a/commands/is-abort b/commands/is-abort index 0c9a2cb58..b360edbde 100755 --- a/commands/is-abort +++ b/commands/is-abort @@ -28,10 +28,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -39,7 +39,7 @@ function is_abort() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -68,6 +68,6 @@ function is_abort() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_abort "$@" fi diff --git a/commands/is-accessible b/commands/is-accessible new file mode 100755 index 000000000..dc0beb6af --- /dev/null +++ b/commands/is-accessible @@ -0,0 +1,263 @@ +#!/usr/bin/env bash + +function is_accessible_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-accessible' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 0 "$root/missing-dir/missing-file" + 0 "$root/missing-file" + + 0 "$root/targets/empty-dir" + 0 "$root/targets/empty-file" + 0 "$root/targets/filled-dir/empty-subfile" + 0 "$root/targets/filled-dir/filled-subdir" + 0 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/filled-dir/filled-subfile" + 0 "$root/targets/filled-file" + 0 "$root/targets/unaccessible-empty-dir" + 0 "$root/targets/unaccessible-empty-file" + 0 "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + 0 "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-empty-dir" + 0 "$root/targets/unexecutable-empty-file" + 0 "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/targets/unexecutable-filled-file" + 0 "$root/targets/unreadable-empty-dir" + 0 "$root/targets/unreadable-empty-file" + 0 "$root/targets/unreadable-filled-dir" + 0 "$root/targets/unreadable-filled-dir/empty-subfile" + 0 "$root/targets/unreadable-filled-dir/filled-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subfile" + 0 "$root/targets/unreadable-filled-file" + 0 "$root/targets/unwritable-empty-dir" + 0 "$root/targets/unwritable-empty-file" + 0 "$root/targets/unwritable-filled-dir" + 0 "$root/targets/unwritable-filled-dir/empty-subfile" + 0 "$root/targets/unwritable-filled-dir/filled-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subfile" + 0 "$root/targets/unwritable-filled-file" + + 0 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/filled-dir--filled-subfile" + 0 "$root/symlinks/filled-file" + 0 "$root/symlinks/unaccessible-empty-dir" + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 0 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-file" + 0 "$root/symlinks/unreadable-empty-dir" + 0 "$root/symlinks/unreadable-empty-file" + 0 "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 0 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subfile" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 0 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/filled-dir--filled-subfile" + 0 "$root/symlinks/filled-file" + 0 "$root/symlinks/unaccessible-empty-dir" + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 0 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-file" + 0 "$root/symlinks/unreadable-empty-dir" + 0 "$root/symlinks/unreadable-empty-file" + 0 "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 0 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + echo-style --g1="TEST: $0" + return 0 +) +function is_accessible() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are accessible, regardless of whether they exist or not. + + USAGE: + is-accessible [...options] [--] ... + + OPTIONS: + --sudo= + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were accessible + [13] if a was not accessible + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='' 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-accessible.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_accessible_test + else + is_accessible "$@" + fi +fi diff --git a/commands/is-accessible.bash b/commands/is-accessible.bash new file mode 100755 index 000000000..6e73b09f8 --- /dev/null +++ b/commands/is-accessible.bash @@ -0,0 +1,24 @@ +#!/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 + path="$1" + shift + + # check accessibility, regardless of existence, by checking the stderr and discarding stdout of stat -L which -L checks the source and target of symlinks + # LINUX + # stat: cannot statx '/home/runner/.cache/dorothy/5350/dir/subfile': Permission denied + # MACOS + # stat: /Users/balupton/.cache/dorothy/12776/dir/subfile: stat: Permission denied + if stat -L "$path" 2>&1 >/dev/null | grep --quiet --regexp=': Permission denied$'; then + exit 13 # EACCES 13 Permission denied + fi +done diff --git a/commands/is-admin b/commands/is-admin index 98dc333d5..dd256804a 100755 --- a/commands/is-admin +++ b/commands/is-admin @@ -20,10 +20,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function is_admin() ( # process local item option_users=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,7 +49,7 @@ function is_admin() ( done # ensure user - if test "${#option_users[@]}" -eq 0; then + if [[ ${#option_users[@]} -eq 0 ]]; then option_users+=("$(whoami)") fi @@ -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 @@ -72,6 +72,6 @@ function is_admin() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_admin "$@" fi diff --git a/commands/is-affirmative b/commands/is-affirmative index 624e07da1..7aaa50440 100755 --- a/commands/is-affirmative +++ b/commands/is-affirmative @@ -22,11 +22,11 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" || return fi return 22 # EINVAL 22 Invalid argument @@ -34,7 +34,7 @@ function __is_affirmative() ( # process local item option_inputs=() option_ignore_empty='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -62,7 +62,7 @@ function __is_affirmative() ( 'Y' | 'y' | 'YES' | 'yes' | 'TRUE' | 'true') had_affirmative='yes' ;; 'N' | 'n' | 'NO' | 'no' | 'FALSE' | 'false') return 1 ;; '') - if test "$option_ignore_empty" = 'yes'; then + if [[ $option_ignore_empty == 'yes' ]]; then continue else return 91 # ENOMSG 91 No message of desired type @@ -74,7 +74,7 @@ function __is_affirmative() ( esac done - if test "$had_affirmative" = 'no'; then + if [[ $had_affirmative == 'no' ]]; then return 91 # ENOMSG 91 No message of desired type else return 0 @@ -82,6 +82,6 @@ function __is_affirmative() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then __is_affirmative "$@" fi diff --git a/commands/is-alpine b/commands/is-alpine index a48f01855..092a28e87 100755 --- a/commands/is-alpine +++ b/commands/is-alpine @@ -18,7 +18,7 @@ function is_alpine() ( [0] if the system is Alpine Linux [1] if the system is not Alpine Linux EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_alpine() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function is_alpine() ( # ===================================== # Action - if test -f /etc/os-release && grep --quiet --ignore-case --regexp='ID=alpine' /etc/os-release 2>/dev/null; then + if [[ -f /etc/os-release ]] && grep --quiet --ignore-case --regexp='ID=alpine' /etc/os-release 2>/dev/null; then return 0 else return 1 @@ -47,6 +47,6 @@ function is_alpine() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_alpine "$@" fi diff --git a/commands/is-apk b/commands/is-apk index b4f9cb236..126d28783 100755 --- a/commands/is-apk +++ b/commands/is-apk @@ -18,7 +18,7 @@ function is_apk() ( [0] if the system has apk [1] if the system does not have apk EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_apk() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_apk() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_apk "$@" fi diff --git a/commands/is-appimage b/commands/is-appimage index 7eeaa3473..dbc51db0f 100755 --- a/commands/is-appimage +++ b/commands/is-appimage @@ -18,7 +18,7 @@ function is_appimage() ( [0] if the system has AppImage [1] if the system does not have AppImage EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_appimage() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_appimage() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_appimage "$@" fi diff --git a/commands/is-apple-silicon b/commands/is-apple-silicon index d0065ec94..2535a80bf 100755 --- a/commands/is-apple-silicon +++ b/commands/is-apple-silicon @@ -18,7 +18,7 @@ function is_apple_silicon() ( [0] if the system is Apple Silicon [1] if the system is not Apple Silicon EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_apple_silicon() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function is_apple_silicon() ( # ===================================== # Action - if is-mac && test "$(uname -p)" = 'arm' -o "$(uname -m)" = 'arm64'; then + if is-mac && [[ "$(uname -p)" == 'arm' || "$(uname -m)" == 'arm64' ]]; then return 0 else return 1 @@ -47,6 +47,6 @@ function is_apple_silicon() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_apple_silicon "$@" fi diff --git a/commands/is-apt b/commands/is-apt index 48c799b0b..61a6ce2d6 100755 --- a/commands/is-apt +++ b/commands/is-apt @@ -20,7 +20,7 @@ function is_apt() ( [0] if the system has apt [1] if the system does not have apt EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -28,7 +28,7 @@ function is_apt() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -49,6 +49,6 @@ function is_apt() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_apt "$@" fi diff --git a/commands/is-arch b/commands/is-arch index 2986c64bb..f05de4af1 100755 --- a/commands/is-arch +++ b/commands/is-arch @@ -18,7 +18,7 @@ function is_arch() ( [0] if the system is Arch Linux [1] if the system is not Arch Linux EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_arch() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,14 +39,11 @@ function is_arch() ( # ===================================== # Action - if test -f /etc/arch-release; then - return 0 - else - return 1 - fi + [[ -f '/etc/arch-release' ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_arch "$@" fi diff --git a/commands/is-bash-version-outdated b/commands/is-bash-version-outdated index f6e178e1d..acb25308f 100755 --- a/commands/is-bash-version-outdated +++ b/commands/is-bash-version-outdated @@ -18,7 +18,7 @@ function is_bash_version_outdated_test() ( echo-style --bold='current bash version' ' = ' --invert="$BASH_VERSION_CURRENT" echo-style --bold='latest known bash version' ' = ' --invert="$BASH_VERSION_LATEST" echo-style --bold='latest available bash version' ' = ' --invert="$bash_version_latest_available" - if test "$BASH_VERSION_LATEST" != "$bash_version_latest_available"; then + if [[ $BASH_VERSION_LATEST != "$bash_version_latest_available" ]]; then echo-style --e2='latest known bash version is the latest available bash version' return 1 fi @@ -52,7 +52,7 @@ function is_bash_version_outdated() ( QUIRKS: This checks whether the current version is supported by Dorothy, not whether the current version is the latest version. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -60,7 +60,7 @@ function is_bash_version_outdated() ( # process local item option_quiet='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -79,21 +79,18 @@ function is_bash_version_outdated() ( # ===================================== # Action - if test "$option_quiet" = 'no'; then + if [[ $option_quiet == 'no' ]]; then echo-style --bold='current bash version' ' = ' --invert="$BASH_VERSION_CURRENT" echo-style --bold='latest known bash version' ' = ' --invert="$BASH_VERSION_LATEST" echo-style --bold='version outdated' ' = ' --invert="$IS_BASH_VERSION_OUTDATED" fi - if test "$IS_BASH_VERSION_OUTDATED" = 'yes'; then - return 0 - else - return 1 - fi + [[ $IS_BASH_VERSION_OUTDATED == 'yes' ]] + return ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_bash_version_outdated_test else is_bash_version_outdated "$@" diff --git a/commands/is-brew b/commands/is-brew index d6a4c6a8f..85903b5b3 100755 --- a/commands/is-brew +++ b/commands/is-brew @@ -5,7 +5,7 @@ function is_brew_test() ( echo-style --h1="TEST: $0" local expected_status - if test -n "${HOMEBREW_PREFIX-}" -a -x "${HOMEBREW_PREFIX-}/bin/brew"; then + if [[ -n ${HOMEBREW_PREFIX-} && -x "${HOMEBREW_PREFIX-}/bin/brew" ]]; then expected_status=0 else expected_status=1 @@ -34,7 +34,7 @@ function is_brew() ( [0] if the system has Homebrew installed. [1] if the system does not have Homebrew installed. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -42,7 +42,7 @@ function is_brew() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -61,8 +61,8 @@ function is_brew() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_brew_test else is_brew "$@" diff --git a/commands/is-broken-symlink b/commands/is-broken-symlink new file mode 100755 index 000000000..e9352aef6 --- /dev/null +++ b/commands/is-broken-symlink @@ -0,0 +1,269 @@ +#!/usr/bin/env bash + +function is_broken_symlink_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-broken-symlink' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 17 "$root/targets/empty-dir" + 17 "$root/targets/empty-file" + 17 "$root/targets/filled-dir/empty-subfile" + 17 "$root/targets/filled-dir/filled-subdir" + 17 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/filled-dir/filled-subfile" + 17 "$root/targets/filled-file" + 17 "$root/targets/unaccessible-empty-dir" + 17 "$root/targets/unaccessible-empty-file" + 17 "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + 17 "$root/targets/unaccessible-filled-file" + 17 "$root/targets/unexecutable-empty-dir" + 17 "$root/targets/unexecutable-empty-file" + 17 "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 17 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 17 "$root/targets/unexecutable-filled-file" + 17 "$root/targets/unreadable-empty-dir" + 17 "$root/targets/unreadable-empty-file" + 17 "$root/targets/unreadable-filled-dir" + 17 "$root/targets/unreadable-filled-dir/empty-subfile" + 17 "$root/targets/unreadable-filled-dir/filled-subdir" + 17 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unreadable-filled-dir/filled-subfile" + 17 "$root/targets/unreadable-filled-file" + 17 "$root/targets/unwritable-empty-dir" + 17 "$root/targets/unwritable-empty-file" + 17 "$root/targets/unwritable-filled-dir" + 17 "$root/targets/unwritable-filled-dir/empty-subfile" + 17 "$root/targets/unwritable-filled-dir/filled-subdir" + 17 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unwritable-filled-dir/filled-subfile" + 17 "$root/targets/unwritable-filled-file" + + 79 "$root/symlinks/empty-dir" + 79 "$root/symlinks/empty-file" + 79 "$root/symlinks/filled-dir--empty-subfile" + 79 "$root/symlinks/filled-dir--filled-subdir" + 79 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/filled-dir--filled-subfile" + 79 "$root/symlinks/filled-file" + 79 "$root/symlinks/unaccessible-empty-dir" + 79 "$root/symlinks/unaccessible-empty-file" + 79 "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 79 "$root/symlinks/unaccessible-filled-file" + 79 "$root/symlinks/unexecutable-empty-dir" + 79 "$root/symlinks/unexecutable-empty-file" + 79 "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 79 "$root/symlinks/unexecutable-filled-file" + 79 "$root/symlinks/unreadable-empty-dir" + 79 "$root/symlinks/unreadable-empty-file" + 79 "$root/symlinks/unreadable-filled-dir" + 79 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 79 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 79 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 79 "$root/symlinks/unreadable-filled-file" + 79 "$root/symlinks/unwritable-empty-dir" + 79 "$root/symlinks/unwritable-empty-file" + 79 "$root/symlinks/unwritable-filled-dir" + 79 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 79 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 79 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 79 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + 17 "$root/targets/unaccessible-filled-dir/empty-subfile" + 17 "$root/targets/unaccessible-filled-dir/filled-subdir" + 17 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unaccessible-filled-dir/filled-subfile" + 17 "$root/targets/unexecutable-filled-dir/empty-subfile" + 17 "$root/targets/unexecutable-filled-dir/filled-subdir" + 17 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unexecutable-filled-dir/filled-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 17 "$root/targets/unaccessible-filled-dir/empty-subfile" + 17 "$root/targets/unaccessible-filled-dir/filled-subdir" + 17 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unaccessible-filled-dir/filled-subfile" + 17 "$root/targets/unexecutable-filled-dir/empty-subfile" + 17 "$root/targets/unexecutable-filled-dir/filled-subdir" + 17 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 17 "$root/targets/unexecutable-filled-dir/filled-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 0 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/filled-dir--filled-subfile" + 0 "$root/symlinks/filled-file" + 0 "$root/symlinks/unaccessible-empty-dir" + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 0 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-file" + 0 "$root/symlinks/unreadable-empty-dir" + 0 "$root/symlinks/unreadable-empty-file" + 0 "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 0 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + 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. + Companion to [is-symlink], [is-not-symlink]. + + USAGE: + is-broken-symlink [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were broken symlinks + [2] if a was not found + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [17] if a was found, but was not a symlink + [79] if a was found, but was not a broken 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='13' 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-on-empty --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..068be0e1d --- /dev/null +++ b/commands/is-broken-symlink.bash @@ -0,0 +1,37 @@ +#!/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 + path="$1" + shift + + # Check for accessible symlink + if [[ -L $path ]]; then + # Check for accessible target + if [[ -e $path ]]; then + # Accessible symlink and target, not a broken symlink + exit 79 # EFTYPE 79 Inappropriate file type or format + else + # Discern accessibility of symlink target + is-accessible.bash -- "$path" || exit $? + # Target was accessible but did not exist, thus it is a broken symlink, which is what we want + continue + fi + elif [[ -e $path ]]; then + # Accessible existing non-symlink file or directory + exit 17 # EEXIST 17 File exists + else + # Discern accessibility or non-existence + is-accessible.bash -- "$path" || exit $? + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-btrfs b/commands/is-btrfs index f6518ee33..1f22a1093 100755 --- a/commands/is-btrfs +++ b/commands/is-btrfs @@ -18,7 +18,7 @@ function is_btrfs() ( [0] if all s are btrfs filesystems. [1] if any s are not btrfs filesystems. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_btrfs() ( # process local item option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -42,7 +42,7 @@ function is_btrfs() ( done # check - if test "${#option_paths[@]}" -eq 0; then + if [[ ${#option_paths[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -52,12 +52,12 @@ function is_btrfs() ( local path filesystem for path in "${option_paths[@]}"; do filesystem="$(get-filesystem "$path")" - test "$filesystem" = 'btrfs' + [[ $filesystem == 'btrfs' ]] || return # explicit return with [[ required for bash v3 done return 0 ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_btrfs "$@" fi diff --git a/commands/is-ci b/commands/is-ci index f0d95b412..c402678be 100755 --- a/commands/is-ci +++ b/commands/is-ci @@ -18,7 +18,7 @@ function is_ci() ( [0] if the environment is a CI environment. [1] if the environment is not a CI environment. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_ci() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -48,6 +48,6 @@ function is_ci() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_ci "$@" fi diff --git a/commands/is-digit b/commands/is-digit index e36ce3fa4..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,10 +52,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -62,7 +63,7 @@ function is_digit() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -78,7 +79,7 @@ function is_digit() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -93,8 +94,8 @@ function is_digit() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_digit_test else is_digit "$@" diff --git a/commands/is-directory b/commands/is-directory new file mode 100755 index 000000000..d37bd9861 --- /dev/null +++ b/commands/is-directory @@ -0,0 +1,269 @@ +#!/usr/bin/env bash + +function is_directory_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-directory' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 0 "$root/targets/empty-dir" + 20 "$root/targets/empty-file" + 20 "$root/targets/filled-dir/empty-subfile" + 0 "$root/targets/filled-dir/filled-subdir" + 0 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/filled-dir/filled-subfile" + 20 "$root/targets/filled-file" + 0 "$root/targets/unaccessible-empty-dir" + 20 "$root/targets/unaccessible-empty-file" + 0 "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-empty-dir" + 20 "$root/targets/unexecutable-empty-file" + 0 "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-file" + 0 "$root/targets/unreadable-empty-dir" + 20 "$root/targets/unreadable-empty-file" + 0 "$root/targets/unreadable-filled-dir" + 20 "$root/targets/unreadable-filled-dir/empty-subfile" + 0 "$root/targets/unreadable-filled-dir/filled-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unreadable-filled-dir/filled-subfile" + 20 "$root/targets/unreadable-filled-file" + 0 "$root/targets/unwritable-empty-dir" + 20 "$root/targets/unwritable-empty-file" + 0 "$root/targets/unwritable-filled-dir" + 20 "$root/targets/unwritable-filled-dir/empty-subfile" + 0 "$root/targets/unwritable-filled-dir/filled-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unwritable-filled-dir/filled-subfile" + 20 "$root/targets/unwritable-filled-file" + + 0 "$root/symlinks/empty-dir" + 20 "$root/symlinks/empty-file" + 20 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/filled-dir--filled-subfile" + 20 "$root/symlinks/filled-file" + 0 "$root/symlinks/unaccessible-empty-dir" + 20 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 20 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-file" + 0 "$root/symlinks/unreadable-empty-dir" + 20 "$root/symlinks/unreadable-empty-file" + 0 "$root/symlinks/unreadable-filled-dir" + 20 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 20 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 20 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 20 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 20 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + 20 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 20 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 9 "$root/symlinks/empty-dir" + 9 "$root/symlinks/empty-file" + 9 "$root/symlinks/filled-dir--empty-subfile" + 9 "$root/symlinks/filled-dir--filled-subdir" + 9 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/filled-dir--filled-subfile" + 9 "$root/symlinks/filled-file" + 9 "$root/symlinks/unaccessible-empty-dir" + 9 "$root/symlinks/unaccessible-empty-file" + 9 "$root/symlinks/unaccessible-filled-dir" + 9 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 9 "$root/symlinks/unaccessible-filled-file" + 9 "$root/symlinks/unexecutable-empty-dir" + 9 "$root/symlinks/unexecutable-empty-file" + 9 "$root/symlinks/unexecutable-filled-dir" + 9 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 9 "$root/symlinks/unexecutable-filled-file" + 9 "$root/symlinks/unreadable-empty-dir" + 9 "$root/symlinks/unreadable-empty-file" + 9 "$root/symlinks/unreadable-filled-dir" + 9 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 9 "$root/symlinks/unreadable-filled-file" + 9 "$root/symlinks/unwritable-empty-dir" + 9 "$root/symlinks/unwritable-empty-file" + 9 "$root/symlinks/unwritable-filled-dir" + 9 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 9 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + echo-style --g1="TEST: $0" + return 0 +) +function is_directory() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are a directory, or an unbroken symlink to a directory. + Companion to [is-not-directory], [echo-if-directory], [is-empty-directory]. + + USAGE: + is-directory [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a directory, or an unbroken symlink to a directory + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [20] if a was found but not a directory nor an unbroken symlink to 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='13' 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-on-empty --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-directory.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_directory_test + else + is_directory "$@" + fi +fi diff --git a/commands/is-directory.bash b/commands/is-directory.bash new file mode 100755 index 000000000..019d3ee73 --- /dev/null +++ b/commands/is-directory.bash @@ -0,0 +1,32 @@ +#!/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 + path="$1" + shift + + if [[ -d $path ]]; then + # accessible and exists, is an unbroken symlink to a directory, or a directory + continue + elif [[ -e $path ]]; then + # accessible and exists, but not an unbroken symlink to a directory, nor a directory + exit 20 # NOTDIR 20 Not a directory + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-dnf b/commands/is-dnf index e07feddbc..46f50f85e 100755 --- a/commands/is-dnf +++ b/commands/is-dnf @@ -18,7 +18,7 @@ function is_dnf() ( [0] if the system has dnf [1] if the system does not have dnf EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_dnf() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_dnf() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_dnf "$@" fi diff --git a/commands/is-dns-working b/commands/is-dns-working index 26dae7d54..508f9dd8b 100755 --- a/commands/is-dns-working +++ b/commands/is-dns-working @@ -24,7 +24,7 @@ function is_dns_working() ( --quiet Toggle verbosity by having this empty (the default), enabled (quiet mode), and disabled (verbose mode). EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function is_dns_working() ( # process local item option_url='' option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,7 +45,7 @@ function is_dns_working() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url="$item" else help "An unrecognised argument was provided: $item" @@ -55,7 +55,7 @@ function is_dns_working() ( done # ensure url - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url='cloudflare.com' fi @@ -79,6 +79,6 @@ function is_dns_working() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_dns_working "$@" fi diff --git a/commands/is-empty-directory b/commands/is-empty-directory new file mode 100755 index 000000000..d2f329d86 --- /dev/null +++ b/commands/is-empty-directory @@ -0,0 +1,269 @@ +#!/usr/bin/env bash + +function is_empty_directory_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-empty-directory' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 0 "$root/targets/empty-dir" + 20 "$root/targets/empty-file" + 20 "$root/targets/filled-dir/empty-subfile" + 66 "$root/targets/filled-dir/filled-subdir" + 0 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/filled-dir/filled-subfile" + 20 "$root/targets/filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-empty-dir" + 20 "$root/targets/unaccessible-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 66 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-empty-dir" + 20 "$root/targets/unexecutable-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 66 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unreadable-empty-dir" + 20 "$root/targets/unreadable-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/targets/unreadable-filled-dir" + 20 "$root/targets/unreadable-filled-dir/empty-subfile" + 66 "$root/targets/unreadable-filled-dir/filled-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unreadable-filled-dir/filled-subfile" + 20 "$root/targets/unreadable-filled-file" + 0 "$root/targets/unwritable-empty-dir" + 20 "$root/targets/unwritable-empty-file" + 66 "$root/targets/unwritable-filled-dir" + 20 "$root/targets/unwritable-filled-dir/empty-subfile" + 66 "$root/targets/unwritable-filled-dir/filled-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unwritable-filled-dir/filled-subfile" + 20 "$root/targets/unwritable-filled-file" + + 0 "$root/symlinks/empty-dir" + 20 "$root/symlinks/empty-file" + 20 "$root/symlinks/filled-dir--empty-subfile" + 66 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/filled-dir--filled-subfile" + 20 "$root/symlinks/filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-empty-dir" + 20 "$root/symlinks/unaccessible-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 66 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-empty-dir" + 20 "$root/symlinks/unexecutable-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 66 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 20 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-file" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unreadable-empty-dir" + 20 "$root/symlinks/unreadable-empty-file" + "$(__status__root_or_nonroot 66 13)" "$root/symlinks/unreadable-filled-dir" + 20 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 66 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 20 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 20 "$root/symlinks/unwritable-empty-file" + 66 "$root/symlinks/unwritable-filled-dir" + 20 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 66 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 20 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + 20 "$root/targets/unaccessible-filled-dir/empty-subfile" + 66 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-dir/empty-subfile" + 66 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 66 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 66 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 20 "$root/targets/unaccessible-filled-dir/empty-subfile" + 66 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unaccessible-filled-dir/filled-subfile" + 20 "$root/targets/unexecutable-filled-dir/empty-subfile" + 66 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 20 "$root/targets/unexecutable-filled-dir/filled-subfile" + 20 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 66 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 20 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 66 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 20 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 9 "$root/symlinks/empty-dir" + 9 "$root/symlinks/empty-file" + 9 "$root/symlinks/filled-dir--empty-subfile" + 9 "$root/symlinks/filled-dir--filled-subdir" + 9 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/filled-dir--filled-subfile" + 9 "$root/symlinks/filled-file" + 9 "$root/symlinks/unaccessible-empty-dir" + 9 "$root/symlinks/unaccessible-empty-file" + 9 "$root/symlinks/unaccessible-filled-dir" + 9 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 9 "$root/symlinks/unaccessible-filled-file" + 9 "$root/symlinks/unexecutable-empty-dir" + 9 "$root/symlinks/unexecutable-empty-file" + 9 "$root/symlinks/unexecutable-filled-dir" + 9 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 9 "$root/symlinks/unexecutable-filled-file" + 9 "$root/symlinks/unreadable-empty-dir" + 9 "$root/symlinks/unreadable-empty-file" + 9 "$root/symlinks/unreadable-filled-dir" + 9 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 9 "$root/symlinks/unreadable-filled-file" + 9 "$root/symlinks/unwritable-empty-dir" + 9 "$root/symlinks/unwritable-empty-file" + 9 "$root/symlinks/unwritable-filled-dir" + 9 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 9 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + 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= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a readable empty directory + [2] if a was not found + [9] if a was a broken symlink + [13] if a was a non-readable directory, as such the length could not be determined, or if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [20] if a was found, but was not a directory nor an unbroken symlink to a directory + [22] if empty arguments are provided + [66] if a was a directory, or an unbroken symlink to a directory, but was not empty + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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..3fc509392 --- /dev/null +++ b/commands/is-empty-directory.bash @@ -0,0 +1,43 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -d $path ]]; then + if [[ ! -r $path || ! -x $path ]]; then + # does exist: not readable/executable however, so no ability to check contents, as would get: + # ls: $path: Permission denied + exit 13 # EACCES 13 Permission denied + fi + if [[ -n "$(ls -A "$path")" ]]; then + # does exist: is a symlink to a non-empty directory, or a non-empty directory + exit 66 # ENOTEMPTY 66 Directory not empty + else + # does exist: is a symlink to an empty directory, or an empty directory + continue + fi + elif [[ -e $path ]]; then + # does exist: not a symlink to a directory, nor a directory + exit 20 # ENOTDIR 20 Not a directory + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-empty-file b/commands/is-empty-file new file mode 100755 index 000000000..d99cadf94 --- /dev/null +++ b/commands/is-empty-file @@ -0,0 +1,288 @@ +#!/usr/bin/env bash + +function is_empty_file_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-empty-file' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 79 "$root/targets/empty-dir" + 0 "$root/targets/empty-file" + 0 "$root/targets/filled-dir/empty-subfile" + 79 "$root/targets/filled-dir/filled-subdir" + 79 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/filled-dir/filled-subfile" + 27 "$root/targets/filled-file" + 79 "$root/targets/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-empty-file" + 79 "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 27 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 27 13)" "$root/targets/unaccessible-filled-file" + 79 "$root/targets/unexecutable-empty-dir" + 0 "$root/targets/unexecutable-empty-file" + 79 "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 27 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 27 "$root/targets/unexecutable-filled-file" + 79 "$root/targets/unreadable-empty-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unreadable-empty-file" + 79 "$root/targets/unreadable-filled-dir" + 0 "$root/targets/unreadable-filled-dir/empty-subfile" + 79 "$root/targets/unreadable-filled-dir/filled-subdir" + 79 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unreadable-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 27 13)" "$root/targets/unreadable-filled-file" + 79 "$root/targets/unwritable-empty-dir" + 0 "$root/targets/unwritable-empty-file" + 79 "$root/targets/unwritable-filled-dir" + 0 "$root/targets/unwritable-filled-dir/empty-subfile" + 79 "$root/targets/unwritable-filled-dir/filled-subdir" + 79 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unwritable-filled-dir/filled-subfile" + 27 "$root/targets/unwritable-filled-file" + + 79 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 79 "$root/symlinks/filled-dir--filled-subdir" + 79 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/filled-dir--filled-subfile" + 27 "$root/symlinks/filled-file" + 79 "$root/symlinks/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-empty-file" + 79 "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 27 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 27 13)" "$root/symlinks/unaccessible-filled-file" + 79 "$root/symlinks/unexecutable-empty-dir" + 0 "$root/symlinks/unexecutable-empty-file" + 79 "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 79 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 27 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 27 "$root/symlinks/unexecutable-filled-file" + 79 "$root/symlinks/unreadable-empty-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unreadable-empty-file" + 79 "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 79 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 79 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unreadable-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 27 13)" "$root/symlinks/unreadable-filled-file" + 79 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 79 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 79 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 79 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 27 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + 0 "$root/targets/unaccessible-empty-file" + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 79 "$root/targets/unaccessible-filled-dir/filled-subdir" + 79 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unaccessible-filled-dir/filled-subfile" + 27 "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 79 "$root/targets/unexecutable-filled-dir/filled-subdir" + 79 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/targets/unreadable-empty-file" + 27 "$root/targets/unreadable-filled-file" + + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 27 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-empty-file" + 27 "$root/symlinks/unreadable-filled-file" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 0 "$root/targets/unaccessible-empty-file" + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 79 "$root/targets/unaccessible-filled-dir/filled-subdir" + 79 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unaccessible-filled-dir/filled-subfile" + 27 "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 79 "$root/targets/unexecutable-filled-dir/filled-subdir" + 79 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 27 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/targets/unreadable-empty-file" + 27 "$root/targets/unreadable-filled-file" + + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 79 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 27 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 79 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 27 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-empty-file" + 27 "$root/symlinks/unreadable-filled-file" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 9 "$root/symlinks/empty-dir" + 9 "$root/symlinks/empty-file" + 9 "$root/symlinks/filled-dir--empty-subfile" + 9 "$root/symlinks/filled-dir--filled-subdir" + 9 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/filled-dir--filled-subfile" + 9 "$root/symlinks/filled-file" + 9 "$root/symlinks/unaccessible-empty-dir" + 9 "$root/symlinks/unaccessible-empty-file" + 9 "$root/symlinks/unaccessible-filled-dir" + 9 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 9 "$root/symlinks/unaccessible-filled-file" + 9 "$root/symlinks/unexecutable-empty-dir" + 9 "$root/symlinks/unexecutable-empty-file" + 9 "$root/symlinks/unexecutable-filled-dir" + 9 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 9 "$root/symlinks/unexecutable-filled-file" + 9 "$root/symlinks/unreadable-empty-dir" + 9 "$root/symlinks/unreadable-empty-file" + 9 "$root/symlinks/unreadable-filled-dir" + 9 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 9 "$root/symlinks/unreadable-filled-file" + 9 "$root/symlinks/unwritable-empty-dir" + 9 "$root/symlinks/unwritable-empty-file" + 9 "$root/symlinks/unwritable-filled-dir" + 9 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 9 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + 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. + Companion to [is-nonempty-file]. + + USAGE: + is-empty-file [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a readable zero-length file + [2] if a was not found + [9] if a was a broken symlink + [13] if a was a non-readable file, as such the length could not be determined, or if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [27] if a was a file, or an unbroken symlink to a file, but was not empty + [79] if a was found, but was not a file nor an unbroken symlink to a file + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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..f369f7c1d --- /dev/null +++ b/commands/is-empty-file.bash @@ -0,0 +1,42 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -f $path ]]; then + if [[ ! -r $path ]]; then + # does exist: not readable however, so no ability to check contents + exit 13 # EACCES 13 Permission denied + fi + if [[ -s $path ]]; then + # does exist: is a symlink to a non-empty file, or a non-empty file + exit 27 # EFBIG 27 File too large + else + # does exist: is a symlink to an empty file, or an empty file + continue + fi + elif [[ -e $path ]]; then + # does exist: not a symlink to a file, nor a file + exit 79 # EFTYPE 79 Inappropriate file type or format + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-empty-ls b/commands/is-empty-ls deleted file mode 100755 index bd8afd2fc..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 test "$#" -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 test "$#" -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 test "${#option_paths[@]}" -eq 0; then - help "No s provided." - fi - - # ===================================== - # Action - - # call this again, but inside sudo - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -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 test ! -d "$path"; then - echo-error 'A path was was not a directory: ' --code="$path" - return 2 - fi - test -z "$(ls -A "$path")" - done - return 0 -) - -# fire if invoked standalone -if test "$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 abc283c29..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 test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process - local item option_inputs=() - while test "$#" -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 test "${#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 test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--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 aed77681e..70264c9df 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,10 +42,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +53,7 @@ function is_empty_value() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,19 +70,16 @@ function is_empty_value() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # process local value for value in "${option_inputs[@]}"; do - # check for empty values, or check for an empty string - if test -z "$value" -o \ - "$value" = 'null' -o "$value" = 'NULL' -o \ - "$value" = 'void' -o "$value" = 'VOID' -o \ - "$value" = 'undefined' -o "$value" = 'UNDEFINED' || is-empty-string -- "$value"; then - : # all good, conntinue + # check for empty values, including empty string and all whitespace + if [[ $value =~ ^([[:space:]]*|null|NULL|void|VOID|undefined|UNDEFINED)$ ]]; then + : # all good, continue else return 1 # not good fi @@ -68,6 +88,10 @@ function is_empty_value() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_empty_value "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + 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..a1dff10c4 --- /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='negative 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-executable b/commands/is-executable new file mode 100755 index 000000000..46e446223 --- /dev/null +++ b/commands/is-executable @@ -0,0 +1,323 @@ +#!/usr/bin/env bash + +function is_executable_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-executable' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # NOTE: + # unaccessible paths aren't actually accessible to only root + # it's more that root doesn't care, and bypasses the permissions anyway + # so [chmod a-rwx] removes access to everything, but root doesn't care + # however, unless [chown 0:0] is also done, then a standard user could undo the [chmod a-rwx] + + # NOTE: + # chmod a-x + # sudo test -x # 0 + # sudo test -x # 1 + # chown '0:0' + # sudo test -x # 0 + # sudo test -x # 1 + # the reason for this: + # root disregards dir/file read/write/exec permissions, except file exec permissions + # this is because everything, except bin execution, must be available to root + # this means that encryption is the only protection against root + # so make sure your volumes/secrets are encrypted to avoid a root user from accessing them + # such as via attaching the volume to a new sudo machine + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 0 "$root/targets/empty-dir" + 0 "$root/targets/empty-file" + 0 "$root/targets/filled-dir/empty-subfile" + 0 "$root/targets/filled-dir/filled-subdir" + 0 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/filled-dir/filled-subfile" + 0 "$root/targets/filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unexecutable-empty-dir" + 93 "$root/targets/unexecutable-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unexecutable-filled-dir/filled-subfile" + 93 "$root/targets/unexecutable-filled-file" + 0 "$root/targets/unreadable-empty-dir" + 0 "$root/targets/unreadable-empty-file" + 0 "$root/targets/unreadable-filled-dir" + 0 "$root/targets/unreadable-filled-dir/empty-subfile" + 0 "$root/targets/unreadable-filled-dir/filled-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subfile" + 0 "$root/targets/unreadable-filled-file" + 0 "$root/targets/unwritable-empty-dir" + 0 "$root/targets/unwritable-empty-file" + 0 "$root/targets/unwritable-filled-dir" + 0 "$root/targets/unwritable-filled-dir/empty-subfile" + 0 "$root/targets/unwritable-filled-dir/filled-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subfile" + 0 "$root/targets/unwritable-filled-file" + + 0 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/filled-dir--filled-subfile" + 0 "$root/symlinks/filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unexecutable-empty-dir" + 93 "$root/symlinks/unexecutable-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unexecutable-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 93 "$root/symlinks/unexecutable-filled-file" + 0 "$root/symlinks/unreadable-empty-dir" + 0 "$root/symlinks/unreadable-empty-file" + 0 "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 0 "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 0 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test with escalation + tuples=( + 0 "$root/targets/unaccessible-empty-dir" + 0 "$root/targets/unaccessible-empty-file" + 0 "$root/targets/unaccessible-filled-dir" + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subfile" + 0 "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-empty-dir" + 93 "$root/targets/unexecutable-empty-file" + 0 "$root/targets/unexecutable-filled-dir" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 93 "$root/targets/unexecutable-filled-file" + + 0 "$root/symlinks/unaccessible-empty-dir" + 0 "$root/symlinks/unaccessible-empty-file" + 0 "$root/symlinks/unaccessible-filled-dir" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 93 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 93 "$root/symlinks/unexecutable-filled-file" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-dir" + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unexecutable-empty-dir" + 93 "$root/targets/unexecutable-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unexecutable-filled-dir" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 93 "$root/targets/unexecutable-filled-file" + + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-dir" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unexecutable-empty-dir" + 93 "$root/symlinks/unexecutable-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unexecutable-filled-dir" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 93 "$root/symlinks/unexecutable-filled-file" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 9 "$root/symlinks/empty-dir" + 9 "$root/symlinks/empty-file" + 9 "$root/symlinks/filled-dir--empty-subfile" + 9 "$root/symlinks/filled-dir--filled-subdir" + 9 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/filled-dir--filled-subfile" + 9 "$root/symlinks/filled-file" + 9 "$root/symlinks/unaccessible-empty-dir" + 9 "$root/symlinks/unaccessible-empty-file" + 9 "$root/symlinks/unaccessible-filled-dir" + 9 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 9 "$root/symlinks/unaccessible-filled-file" + 9 "$root/symlinks/unexecutable-empty-dir" + 9 "$root/symlinks/unexecutable-empty-file" + 9 "$root/symlinks/unexecutable-filled-dir" + 9 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 9 "$root/symlinks/unexecutable-filled-file" + 9 "$root/symlinks/unreadable-empty-dir" + 9 "$root/symlinks/unreadable-empty-file" + 9 "$root/symlinks/unreadable-filled-dir" + 9 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 9 "$root/symlinks/unreadable-filled-file" + 9 "$root/symlinks/unwritable-empty-dir" + 9 "$root/symlinks/unwritable-empty-file" + 9 "$root/symlinks/unwritable-filled-dir" + 9 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 9 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + echo-style --g1="TEST: $0" + return 0 +) +function is_executable() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are executable. + Companion to [echo-if-executable]. + + USAGE: + is-executable [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s are executable + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [93] if a was found but not executable + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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-executable.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_executable_test + else + is_executable "$@" + fi +fi diff --git a/commands/is-executable.bash b/commands/is-executable.bash new file mode 100755 index 000000000..9ec7f04f9 --- /dev/null +++ b/commands/is-executable.bash @@ -0,0 +1,35 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -x $path ]]; then + # does exist: is executable + continue + elif [[ -e $path ]]; then + # does exist: is not executable + # discern if unable to detect executable status because it was inaccessible + is-accessible.bash -- "$path" || exit $? + exit 93 # ENOATTR 93 Attribute not found + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-file b/commands/is-file new file mode 100755 index 000000000..0af80008f --- /dev/null +++ b/commands/is-file @@ -0,0 +1,139 @@ +#!/usr/bin/env bash + +function is_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + 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=2 \ + -- is-file -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' --status=79 \ + -- is-file -- "$DOROTHY" "$DOROTHY" + + eval-tester --name='file' \ + -- is-file -- "$DOROTHY/README.md" "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=79 \ + -- is-file -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='file then invalid then dir' --status=22 \ + -- is-file -- "$DOROTHY/README.md" '' "$DOROTHY" + + eval-tester --name='file then missing then invalid then dir' --status=2 \ + -- is-file -- "$DOROTHY/README.md" "$DOROTHY/this-does-not-exist" '' "$DOROTHY" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-file-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + eval-tester --name='symlink dir' --status=79 \ + -- 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=9 \ + -- is-file -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=9 \ + -- is-file -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) +function is_file() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are a file, or an unbroken symlink to a file. + Companion to [echo-if-file]. + + USAGE: + is-file [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a file + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [79] if a was found but not a file nor an unbroken symlink to a file + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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-file.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_file_test + else + is_file "$@" + fi +fi diff --git a/commands/is-file.bash b/commands/is-file.bash new file mode 100755 index 000000000..19402c95b --- /dev/null +++ b/commands/is-file.bash @@ -0,0 +1,33 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -f $path ]]; then + # does exist: is a symlink to a file, or a file + continue + elif [[ -e $path ]]; then + # does exist: not a symlink to a file, nor a file + exit 79 # EFTYPE 79 Inappropriate file type or format + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-generic b/commands/is-generic index 5013ba53e..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,10 +38,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -39,7 +49,7 @@ function is_generic() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -55,30 +65,28 @@ function is_generic() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # 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 -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_generic_test else is_generic "$@" diff --git a/commands/is-globstar b/commands/is-globstar index 00387853b..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,10 +37,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -48,7 +48,7 @@ function is_globstar() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -64,7 +64,7 @@ function is_globstar() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -80,8 +80,8 @@ function is_globstar() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_globstar_test else is_globstar "$@" diff --git a/commands/is-gnome b/commands/is-gnome index 6c336d33b..6579cc7a7 100755 --- a/commands/is-gnome +++ b/commands/is-gnome @@ -18,7 +18,7 @@ function is_gnome() ( [0] if the session exposes Gnome [1] if the session does not expose Gnome EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_gnome() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function is_gnome() ( # ===================================== # Action - if test "${DESKTOP_SESSION-}" = 'gnome'; then + if [[ ${DESKTOP_SESSION-} == 'gnome' ]]; then return 0 else return 1 @@ -47,6 +47,6 @@ function is_gnome() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_gnome "$@" fi diff --git a/commands/is-group b/commands/is-group index 696df051d..8444dc05e 100755 --- a/commands/is-group +++ b/commands/is-group @@ -19,11 +19,11 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function is_group() ( # process local item option_groups=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -69,6 +69,6 @@ function is_group() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_group "$@" fi diff --git a/commands/is-headful b/commands/is-headful index 7a316d50c..6b43c8c8d 100755 --- a/commands/is-headful +++ b/commands/is-headful @@ -18,7 +18,7 @@ function is_headful() ( ABOUT: Checks if there is Desktop/GUI available to our current session. macOS and WSL will always return [0] success. - This is usually used in conjuction with [is-ssh]. + This is usually used in conjunction with [is-ssh]. USAGE: is-headful @@ -27,7 +27,7 @@ function is_headful() ( [0] if the session exposes a Desktop/GUI. [1] if the session does not expose a Desktop/GUI. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -35,7 +35,7 @@ function is_headful() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -48,7 +48,7 @@ function is_headful() ( # ===================================== # Action - if is-mac || is-wsl || test -n "${XDG_CURRENT_DESKTOP-}" -o -n "${DESKTOP_SESSION-}"; then + if is-mac || is-wsl || [[ -n ${XDG_CURRENT_DESKTOP-} || -n ${DESKTOP_SESSION-} ]]; then return 0 else return 1 @@ -56,6 +56,6 @@ function is_headful() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_headful "$@" fi diff --git a/commands/is-headless b/commands/is-headless index 28e21a505..0a9bc01b6 100755 --- a/commands/is-headless +++ b/commands/is-headless @@ -18,7 +18,7 @@ function is_headless() ( [0] if the session does not expose a Desktop/GUI. [1] if the session exposes a Desktop/GUI. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_headless() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_headless() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_headless "$@" fi diff --git a/commands/is-integer b/commands/is-integer index b702b5191..41e8bcd8f 100755 --- a/commands/is-integer +++ b/commands/is-integer @@ -3,9 +3,9 @@ # https://stackoverflow.com/a/29234612/130638 # a much more complicated version of this is: -# if test -z "$1"; then +# if [[ -z "$1" ]]; then # return 1 -# elif test "$1" = '0'; then +# elif [[ "$1" = '0' ]]; then # return 0 # else # # hide stdout and stderr @@ -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,10 +76,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -86,7 +87,7 @@ function is_integer() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -112,8 +113,8 @@ function is_integer() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_integer_test else is_integer "$@" diff --git a/commands/is-internet-working b/commands/is-internet-working index 699c09fde..31b8403a8 100755 --- a/commands/is-internet-working +++ b/commands/is-internet-working @@ -24,7 +24,7 @@ function is_internet_working() ( --quiet Toggle verbosity by having this empty (the default), enabled (quiet mode), and disabled (verbose mode). EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +32,7 @@ function is_internet_working() ( # process local item option_url='' option_quiet='' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -45,7 +45,7 @@ function is_internet_working() ( ;; '--'*) help "An unrecognised flag was provided: $item" ;; *) - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url="$item" else help "An unrecognised argument was provided: $item" @@ -55,7 +55,7 @@ function is_internet_working() ( done # ensure url - if test -z "$option_url"; then + if [[ -z $option_url ]]; then option_url='cloudflare.com' fi @@ -81,6 +81,6 @@ function is_internet_working() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_internet_working "$@" fi diff --git a/commands/is-kde b/commands/is-kde index eab3165aa..580e7eff2 100755 --- a/commands/is-kde +++ b/commands/is-kde @@ -18,7 +18,7 @@ function is_kde() ( [0] if the desktop environment is KDE or LXDE [1] if the desktop environment is not KDE nor LXDE EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_kde() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -40,7 +40,7 @@ function is_kde() ( # Action local desktop_session="${DESKTOP_SESSION-}" # not defined on macos - if [[ $desktop_session == 'LXDE'* ]] || [[ $desktop_session == 'KDE'* ]]; then + if [[ $desktop_session == 'LXDE'* || $desktop_session == 'KDE'* ]]; then return 0 else return 1 @@ -48,6 +48,6 @@ function is_kde() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_kde "$@" fi diff --git a/commands/is-linux b/commands/is-linux index a16ed791b..aff50171e 100755 --- a/commands/is-linux +++ b/commands/is-linux @@ -6,7 +6,7 @@ function is_linux_test() ( local os os="$(uname -s)" - if test "$os" = 'Linux'; then + if [[ $os == 'Linux' ]]; then eval-tester --name='is linux' --status='0' \ -- is-linux else @@ -35,7 +35,7 @@ function is_linux() ( [0] if the system is Linux [1] if the system is not Linux EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -43,7 +43,7 @@ function is_linux() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -57,7 +57,7 @@ function is_linux() ( # Action # this will/should pass on WSL on Windows - if test "$(uname -s)" = 'Linux'; then + if [[ "$(uname -s)" == 'Linux' ]]; then return 0 else return 1 @@ -65,8 +65,8 @@ function is_linux() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_linux_test else is_linux "$@" diff --git a/commands/is-mac b/commands/is-mac index 60b3d6d00..48d7c8870 100755 --- a/commands/is-mac +++ b/commands/is-mac @@ -6,7 +6,7 @@ function is_mac_test() ( local os os="$(uname -s)" - if test "$os" = 'Darwin'; then + if [[ $os == 'Darwin' ]]; then eval-tester --name='is mac' --status='0' \ -- is-mac else @@ -35,7 +35,7 @@ function is_mac() ( [0] if the system is nacOS [1] if the system is not nacOS EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -43,7 +43,7 @@ function is_mac() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -56,7 +56,7 @@ function is_mac() ( # ===================================== # Action - if test "$(uname -s)" = 'Darwin'; then + if [[ "$(uname -s)" == 'Darwin' ]]; then return 0 else return 1 @@ -64,8 +64,8 @@ function is_mac() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_mac_test else is_mac "$@" diff --git a/commands/is-manjaro b/commands/is-manjaro index 0663514e3..d126e01be 100755 --- a/commands/is-manjaro +++ b/commands/is-manjaro @@ -18,7 +18,7 @@ function is_manjaro() ( [0] if the system is Manjaro Linux [1] if the system is not Manjaro Linux EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_manjaro() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -48,6 +48,6 @@ function is_manjaro() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_manjaro "$@" fi diff --git a/commands/is-missing b/commands/is-missing index 37ebc4b31..d268a38f7 100755 --- a/commands/is-missing +++ b/commands/is-missing @@ -1,5 +1,67 @@ #!/usr/bin/env bash +function is_missing_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + 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=17 \ + -- is-missing -- "$DOROTHY" + + eval-tester --name='missing' \ + -- is-missing -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='file' --status=17 \ + -- is-missing -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=17 \ + -- is-missing -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='missing then file' --status=17 \ + -- is-missing -- "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" + + eval-tester --name='missing then file then invalid' --status=17 \ + -- is-missing -- "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" '' + + eval-tester --name='missing then invalid then file' --status=22 \ + -- is-missing -- "$DOROTHY/this-does-not-exist" '' "$DOROTHY/README.md" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-missing-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + eval-tester --name='symlink dir' --status=17 \ + -- is-missing -- "$dir_symlink" + + eval-tester --name='symlink file' --status=17 \ + -- is-missing -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=17 \ + -- is-missing -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=17 \ + -- is-missing -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_missing() ( source "$DOROTHY/sources/bash.bash" @@ -9,38 +71,40 @@ 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] [--] ... OPTIONS: - --sudo - If specified, use sudo on filesystem interactions. + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. --user= --group= - If specified use this user and/or group for filesystem interactions. + Forwarded to [sudo-helper]. RETURNS: - [0] if all s were missing - [1] if any s were missing + [0] if all s were missing (not even a broken symlink). + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [17] if a was not missing + [22] if empty arguments are provided EOF - if test "$#" -ne 0; then + 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 test "$#" -ne 0; do + local item option_inputs=() option_sudo='13' 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")" + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" ;; '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; @@ -55,37 +119,22 @@ function is_missing() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # Action - function __sudo_wrapper { - while test "$1" = '--'; do - shift - done - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -n "$option_group"; then - sudo-helper --inherit --user="$option_user" --group="$option_group" -- "$@" - return - else - "$@" - return - fi - } - - local input - for input in "${option_inputs[@]}"; do - # just -e is faulty, as -e fails on broken symlinks - if __sudo_wrapper -- test -e "$input" -o -L "$input"; then - return 1 - fi - done - 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 test "$0" = "${BASH_SOURCE[0]}"; then - is_missing "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_missing_test + else + is_missing "$@" + fi fi diff --git a/commands/is-missing.bash b/commands/is-missing.bash new file mode 100755 index 000000000..7dd435743 --- /dev/null +++ b/commands/is-missing.bash @@ -0,0 +1,26 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -e $path || -L $path ]]; then + # exists: is a symlink (broken or otherwise, accessible or otherwise), file, or directory + exit 17 # EEXIST 17 File exists + else + # discern if inaccessible, missing + is-accessible.bash -- "$path" || exit $? + continue # missing, which is what we want + fi +done +exit 0 diff --git a/commands/is-needle b/commands/is-needle index 0eaf589ac..8926cdc30 100755 --- a/commands/is-needle +++ b/commands/is-needle @@ -17,7 +17,25 @@ function is_needle_test() ( eval-tester --status=22 --ignore-stderr \ -- is-needle -- a b c - # capture what [test " ${items[*]} " =~ " $item "] cannot + eval-tester --status=22 --ignore-stderr \ + -- is-needle a b -- a b c + + eval-tester --status=22 --ignore-stderr \ + -- is-needle a b -- + + eval-tester --status=22 --ignore-stderr \ + -- is-needle a b + + eval-tester --status=1 \ + -- is-needle a -- + + eval-tester --status=1 \ + -- is-needle --any a b -- + + eval-tester --status=1 \ + -- is-needle --all a b -- + + # capture what [[ " ${items[*]} " =~ " $item " ]] cannot eval-tester --status=1 \ -- is-needle b -- a 'b b b' @@ -25,19 +43,40 @@ function is_needle_test() ( -- is-needle c -- a 'b b b' eval-tester --status=1 \ - -- is-needle b c -- a 'b b b' + -- is-needle --any b c -- a 'b b b' - eval-tester --status=0 \ - -- is-needle a 'b b b' -- a 'b b b' + eval-tester --status=1 \ + -- is-needle --all b c -- a 'b b b' + + eval-tester \ + -- is-needle --any a 'b b b' -- a 'b b b' + + eval-tester \ + -- is-needle --all a 'b b b' -- a 'b b b' + + eval-tester \ + -- is-needle --any --needle=a --needle='b b b' -- a 'b b b' - eval-tester --status=0 \ - -- is-needle --needle=a --needle='b b b' -- a 'b b b' + eval-tester \ + -- is-needle --all --needle=a --needle='b b b' -- a 'b b b' - eval-tester --status=0 \ - -- is-needle a a -- a 'b b b' + eval-tester \ + -- is-needle --any a a -- a 'b b b' + + eval-tester \ + -- is-needle --all a a -- a 'b b b' + + eval-tester \ + -- is-needle --any a c -- a 'b b b' eval-tester --status=1 \ - -- is-needle a -- + -- is-needle --all a c -- a 'b b b' + + eval-tester \ + -- is-needle --any a c -- a 'b b b' + + eval-tester \ + -- is-needle --all --ignore-case a C -- A 'b b b' c echo-style --g1="TEST: $0" return 0 @@ -52,81 +91,127 @@ 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] [--] ... + is-needle [...options] -- ... OPTIONS: + --all | --any + Required if multiple needles are provided: which technique to use for comparisons. + + --ignore-case + If provided, ignore case when comparing needle to elements. + --needle= | Verify this exists within the s 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 the is found within the s + [1] if the is not found EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument } # process our arguments - local item option_needles=() option_elements=() - while test "$#" -ne 0; do + local item option_all_or_any='' option_ignore_case='no' option_needles=() option_elements=() defined_needles='no' + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in '--help' | '-h') help ;; '--needle='*) option_needles+=("${item#*=}") ;; + '--all') + option_all_or_any='all' + ;; + '--any') + option_all_or_any='any' + ;; + '--no-ignore-case'* | '--ignore-case'*) + option_ignore_case="$(get-flag-value --affirmative --fallback="$option_ignore_case" -- "$item")" + ;; --) + defined_needles='yes' option_elements+=("$@") shift $# ;; - # don't check for invalid flags, as flags could be a needle - *) - if test "${#option_needles[@]}" -eq 0; then - option_needles+=("$item") - else - option_elements+=("$item" "$@") - shift "$#" - break - fi - ;; + # enforce needle flag for items that look like flags + '--'*) help "An unrecognised flag was provided: $item" ;; + *) option_needles+=("$item") ;; esac done - # check - if test "${#option_needles[@]}" -eq 0; then + # checks + if [[ ${#option_needles[@]} -eq 0 ]]; then help 'No s was provided.' fi + if [[ $defined_needles == 'no' ]]; then + help 'The -- flag was not provided to separate s from s, even if there are no s it is required.' + fi + if [[ ${#option_needles[@]} -gt 1 && -z $option_all_or_any ]]; then + help 'Multiple s were provided, but no --all or --any flag was provided.' + fi + if [[ ${#option_elements[@]} -eq 0 ]]; then + return 1 + fi # ===================================== # Action - # checks - if test "${#option_needles[@]}" -eq 0; then - return 0 - fi - if test "${#option_elements[@]}" -eq 0; then - return 1 + # if ignore case, convert them to lowercase + local index + if [[ $option_ignore_case == 'yes' ]]; then + for index in "${!option_needles[@]}"; do + item="$(__lowercase_string -- "${option_needles[index]}")" + option_needles[index]="$item" + done + for index in "${!option_elements[@]}"; do + item="$(__lowercase_string -- "${option_elements[index]}")" + option_elements[index]="$item" + done fi # process local needle element found='no' - for needle in "${option_needles[@]}"; do - found='no' + if [[ $option_all_or_any == 'all' ]]; then + for needle in "${option_needles[@]}"; do + found='no' + for element in "${option_elements[@]}"; do + if [[ $needle == "$element" ]]; then + found='yes' + break + fi + done + if [[ $found == 'no' ]]; then + break + fi + done + elif [[ $option_all_or_any == 'any' ]]; then + for needle in "${option_needles[@]}"; do + for element in "${option_elements[@]}"; do + if [[ $needle == "$element" ]]; then + found='yes' + break + fi + done + if [[ $found == 'yes' ]]; then + break + fi + done + else + needle="${option_needles[0]}" for element in "${option_elements[@]}"; do - if test "$needle" = "$element"; then + if [[ $needle == "$element" ]]; then found='yes' break fi done - if test "$found" = 'no'; then - break - fi - done - if test "$found" = 'yes'; then + fi + if [[ $found == 'yes' ]]; then return 0 else return 1 @@ -134,8 +219,8 @@ function is_needle() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_needle_test else is_needle "$@" diff --git a/commands/is-non-affirmative b/commands/is-non-affirmative index 912931ca2..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,11 +23,11 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" || return fi return 22 # EINVAL 22 Invalid argument @@ -34,7 +35,7 @@ function __is_non_affirmative() ( # process local item option_inputs=() option_ignore_empty='no' - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -62,7 +63,7 @@ function __is_non_affirmative() ( 'Y' | 'y' | 'YES' | 'yes' | 'TRUE' | 'true') return 1 ;; 'N' | 'n' | 'NO' | 'no' | 'FALSE' | 'false') had_non_affirmative='yes' ;; '') - if test "$option_ignore_empty" = 'yes'; then + if [[ $option_ignore_empty == 'yes' ]]; then continue else return 91 # ENOMSG 91 No message of desired type @@ -74,7 +75,7 @@ function __is_non_affirmative() ( esac done - if test "$had_non_affirmative" = 'no'; then + if [[ $had_non_affirmative == 'no' ]]; then return 91 # ENOMSG 91 No message of desired type else return 0 @@ -82,6 +83,6 @@ function __is_non_affirmative() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then __is_non_affirmative "$@" fi diff --git a/commands/is-nonempty-file b/commands/is-nonempty-file new file mode 100755 index 000000000..2d24f8c30 --- /dev/null +++ b/commands/is-nonempty-file @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +function is_nonempty_file_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # prep + local root dir file dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-nonempty-file-test')" + fs-rm --quiet --no-confirm -- "$root" + dir="$(fs-temp --root="$root" --directory --touch)" + file="$(fs-temp --root="$root" --file --touch)" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test standard paths + 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=2 \ + -- is-nonempty-file -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='empty dirs' --status=79 \ + -- is-nonempty-file -- "$dir" "$dir" + + eval-tester --name='empty files' --status=66 \ + -- is-nonempty-file -- "$file" "$file" + + eval-tester --name='non-empty dir' --status=79 \ + -- is-nonempty-file -- "$DOROTHY" + + eval-tester --name='non-empty file' \ + -- is-nonempty-file -- "$DOROTHY/README.md" + + # test working symlinks + eval-tester --name='symlink empty dir' --status=79 \ + -- is-nonempty-file -- "$dir_symlink" + + eval-tester --name='symlink empty file' --status=66 \ + -- is-nonempty-file -- "$file_symlink" + + # test non-empty symlinks + __print_line >"$file_target" + + eval-tester --name='symlink non-empty file' \ + -- is-nonempty-file -- "$file_symlink" + + eval-tester --name='symlink non-empty file then non-empty file' \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink non-empty file then empty file' --status=66 \ + -- is-nonempty-file -- "$file_symlink" "$file" + + eval-tester --name='symlink non-empty file then non-empty dir' --status=79 \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY" + + eval-tester --name='symlink non-empty file then missing' --status=2 \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY/this-does-not-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=9 \ + -- is-nonempty-file -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=9 \ + -- is-nonempty-file -- "$file_symlink" + + eval-tester --name='broken symlink file then non-empty dir' --status=9 \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=9 \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=9 \ + -- is-nonempty-file -- "$file_symlink" "$DOROTHY/this-does-not-exist" + + echo-style --g1="TEST: $0" + return 0 +) +function is_nonempty_file() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Checks if a is a file that has contents, aka a file that is not zero-length. + Companion to [is-empty-file]. + + USAGE: + is-nonempty-file [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a readable non-zero-length file + [2] if a was not found + [9] if a was a broken symlink + [13] if a was a non-readable file, as such the length could not be determined, or if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [17] if a was a zero-length file + [22] if empty arguments are provided + [79] if a was found but was not a file, nor an unbroken symlink to a file + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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-nonempty-file.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_nonempty_file_test + else + is_nonempty_file "$@" + fi +fi diff --git a/commands/is-nonempty-file.bash b/commands/is-nonempty-file.bash new file mode 100755 index 000000000..2387b26e5 --- /dev/null +++ b/commands/is-nonempty-file.bash @@ -0,0 +1,42 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -f $path ]]; then + if [[ ! -r $path ]]; then + # does exist: not readable however, so no ability to check contents + exit 13 # EACCES 13 Permission denied + fi + if [[ -s $path ]]; then + # does exist: is a symlink to a non-empty file, or a non-empty file + continue + else + # does exist: is a symlink to an empty file, or an empty file + exit 17 # EEXIST 17 File exists + fi + elif [[ -e $path ]]; then + # does exist: not a symlink to a file, nor a file + exit 79 # EFTYPE 79 Inappropriate file type or format + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-nonempty-string b/commands/is-nonempty-string deleted file mode 100755 index 47d871d9b..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 test "$#" -ne 0; then - echo-error "$@" - fi - return 22 # EINVAL 22 Invalid argument - } - - # process our arguments - local item option_inputs=() - while test "$#" -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 test "${#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 test "$0" = "${BASH_SOURCE[0]}"; then - is_nonempty_string "$@" -fi diff --git a/commands/is-not-directory b/commands/is-not-directory index 58e4852e5..994e3c8be 100755 --- a/commands/is-not-directory +++ b/commands/is-not-directory @@ -1,5 +1,88 @@ #!/usr/bin/env bash +function is_not_directory_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-not-directory -- + + eval-tester --name='empty args' --status=22 \ + -- is-not-directory -- '' '' + + eval-tester --name='missing' --status=2 \ + -- is-not-directory -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' --status=21 \ + -- is-not-directory -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-not-directory -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' --status=21 \ + -- is-not-directory -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' --status=21 \ + -- is-not-directory -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='dir then file then invalid' --status=21 \ + -- is-not-directory -- "$DOROTHY" "$DOROTHY/README.md" '' + + eval-tester --name='dir then invalid then file' --status=21 \ + -- is-not-directory -- "$DOROTHY" '' "$DOROTHY/README.md" + + eval-tester --name='file then invalid then dir' --status=22 \ + -- is-not-directory -- "$DOROTHY/README.md" '' "$DOROTHY" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-not-directory-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + eval-tester --name='symlink dir' --status=21 \ + -- is-not-directory -- "$dir_symlink" + + eval-tester --name='symlink file' \ + -- is-not-directory -- "$file_symlink" + + eval-tester --name='symlink dir then dir' --status=21 \ + -- is-not-directory -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' \ + -- is-not-directory -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=2 \ + -- is-not-directory -- "$file_symlink" "$DOROTHY/this-does-not-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=9 \ + -- is-not-directory -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=9 \ + -- is-not-directory -- "$file_symlink" + + eval-tester --name='broken symlink dir then dir' --status=9 \ + -- is-not-directory -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink dir then file' --status=9 \ + -- is-not-directory -- "$dir_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink dir then missing' --status=9 \ + -- is-not-directory -- "$dir_symlink" "$DOROTHY/this-does-not-exist" + + echo-style --g1="TEST: $0" + return 0 +) function is_not_directory() ( source "$DOROTHY/sources/bash.bash" @@ -9,37 +92,43 @@ 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], [is-empty-directory]. + Use this over [! is-directory -- ...], as that will pass for invalid arguments. USAGE: is-not-directory [...options] [--] ... OPTIONS: - --sudo - If specified, use sudo on filesystem interactions. + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. --user= --group= - If specified use this user and/or group for filesystem interactions. + Forwarded to [sudo-helper]. RETURNS: - [0] if all s were not directories - [1] if any s was a directory + [0] if all s existed, and were neither a directory, nor an unbroken symlink to a directory + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [21] if a was a directory, or an unbroken symlink to a directory + [22] if empty arguments are provided EOF - if test "$#" -ne 0; then + 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 test "$#" -ne 0; do + local item option_inputs=() option_sudo='13' 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")" + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" ;; '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; @@ -54,36 +143,22 @@ function is_not_directory() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # Action - function __sudo_wrapper { - while test "$1" = '--'; do - shift - done - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -n "$option_group"; then - sudo-helper --inherit --user="$option_user" --group="$option_group" -- "$@" - return - else - "$@" - return - fi - } - - local input - for input in "${option_inputs[@]}"; do - if __sudo_wrapper -- test ! -d "$input"; then - return 1 - fi - done - 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 -if test "$0" = "${BASH_SOURCE[0]}"; then - is_not_directory "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_not_directory_test + else + is_not_directory "$@" + fi fi diff --git a/commands/is-not-directory.bash b/commands/is-not-directory.bash new file mode 100755 index 000000000..6c139062b --- /dev/null +++ b/commands/is-not-directory.bash @@ -0,0 +1,33 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -d $path ]]; then + # does exist: is a symlink to a directory, or a directory + exit 21 # EISDIR 21 Is a directory + elif [[ -e $path ]]; then + # does exist and is not a symlink to a directory, nor a directory + continue + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-not-symlink b/commands/is-not-symlink new file mode 100755 index 000000000..dd975ee23 --- /dev/null +++ b/commands/is-not-symlink @@ -0,0 +1,147 @@ +#!/usr/bin/env bash + +function is_not_symlink_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + 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=2 \ + -- is-not-symlink -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' \ + -- is-not-symlink -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-not-symlink -- "$DOROTHY/README.md" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-not-symlink-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + eval-tester --name='symlink dir' --status=79 \ + -- is-not-symlink -- "$dir_symlink" + + eval-tester --name='symlink file' --status=79 \ + -- is-not-symlink -- "$file_symlink" + + eval-tester --name='symlink dir then dir' --status=79 \ + -- is-not-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' --status=79 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=79 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/this-does-not-exist" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + + eval-tester --name='broken symlink dir' --status=79 \ + -- is-not-symlink -- "$dir_symlink" + + eval-tester --name='broken symlink file' --status=79 \ + -- is-not-symlink -- "$file_symlink" + + eval-tester --name='broken symlink dir then dir' --status=79 \ + -- is-not-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=79 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=79 \ + -- is-not-symlink -- "$file_symlink" "$DOROTHY/this-does-not-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. + Companion to [is-broken-symlink], [is-symlink]. + + USAGE: + is-not-symlink [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s existed and were not a symlink (broken or otherwise) + [2] if a was not found + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [79] if a was a symlink (broken or otherwise) + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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..0f598fbbb --- /dev/null +++ b/commands/is-not-symlink.bash @@ -0,0 +1,29 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -L $path ]]; then + # does exist: is a symlink (accessible or otherwise, broken or otherwise) + exit 79 # EFTYPE 79 Inappropriate file type or format + elif [[ -e $path ]]; then + # does exist and is not a symlink (accessible or otherwise, broken or otherwise) + continue + else + # discern if inaccessible, missing + is-accessible.bash -- "$path" || exit $? + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-not-whitespace b/commands/is-not-whitespace new file mode 100755 index 000000000..a1fe3d4ad --- /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 standard failure pt. 1' --status=1 \ + -- is-not-whitespace -- '' 'b' + + eval-tester --name='mix is is standard 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 4ed9d61ba..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,10 +67,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -77,7 +78,7 @@ function is_number() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -93,7 +94,7 @@ function is_number() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi @@ -108,8 +109,8 @@ function is_number() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - if test "$*" = '--test'; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then is_number_test else is_number "$@" diff --git a/commands/is-odd b/commands/is-odd index a8fe6faf8..f101d1339 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='negative 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,21 +47,22 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -32,7 +70,7 @@ function is_odd() ( # process local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -48,13 +86,13 @@ function is_odd() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then - help "No s provided" + if [[ ${#option_inputs[@]} -eq 0 ]]; then + 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 - test "$((input % 2))" -ne 0 + [[ $((input % 2)) -ne 0 ]] || return # explicit return with [[ required for bash v3 done return 0 ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then - is_odd "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_odd_test + else + is_odd "$@" + fi fi diff --git a/commands/is-opensuse b/commands/is-opensuse index ad34fb68d..d9a905ac4 100755 --- a/commands/is-opensuse +++ b/commands/is-opensuse @@ -18,7 +18,7 @@ function is_opensuse() ( [0] if the system is OpenSUSE [1] if the system is not OpenSUSE EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_opensuse() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function is_opensuse() ( # ===================================== # Action - if test -f /etc/os-release && grep --quiet --ignore-case --fixed-strings --regexp='openSUSE' /etc/os-release 2>/dev/null; then + if [[ -f /etc/os-release ]] && grep --quiet --ignore-case --fixed-strings --regexp='openSUSE' /etc/os-release 2>/dev/null; then return 0 else return 1 @@ -47,6 +47,6 @@ function is_opensuse() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_opensuse "$@" fi diff --git a/commands/is-owner b/commands/is-owner new file mode 100755 index 000000000..403ce7384 --- /dev/null +++ b/commands/is-owner @@ -0,0 +1,177 @@ +#!/usr/bin/env bash + +function is_owner_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + local temp + temp="$(fs-temp --directory='is-owner' --file='test-file' --touch)" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-owner -- + + eval-tester --name='empty args' --status=22 \ + -- is-owner -- '' '' + + eval-tester --name='missing' --status=2 \ + -- is-owner -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='is owner of its created file' \ + -- is-owner -- "$temp" + + eval-tester --name='invalid uid is not owner' --status=93 \ + -- is-owner --uid=0123456789 -- "$temp" + + eval-tester --name='invalid gid is not owner' --status=93 \ + -- is-owner --gid=0123456789 -- "$temp" + + eval-tester --name='invalid uname is not owner' --status=93 \ + -- is-owner --uname=invalid -- "$temp" + + eval-tester --name='invalid gname is not owner' --status=93 \ + -- is-owner --gname=invalid -- "$temp" + + echo-style --g1="TEST: $0" + return 0 +) +function is_owner() ( + source "$DOROTHY/sources/bash.bash" + source "$(type -P sudo-helper)" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are present (including that of symlinks, broken or otherwise) and are owned by one of the provided s, s, s, s. + If no s, s, s, s are provided, those of the current user will be used. + + USAGE: + is-owner [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + --uid= | --user-id= + --gid= | --group-id= + --u= | --uname= | --user-name= + --g= | --gname= | --group-name= + Specific s, s, s, s to check against. + + RETURNS: + [0] if all s were present (file, directory, symlink, broken symlink) and owned by any of the provided s, s, s, s. + [2] if a was not present + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [93] if the owner of a does not match any of the provided s, s, s, s. + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' option_user='' option_group='' option_user_ids=() option_user_names=() option_group_ids=() option_group_names=() + while [[ $# -ne 0 ]]; do + item="$1" + shift + case "$item" in + '--help' | '-h') help ;; + '--no-sudo'* | '--sudo'*) + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" + ;; + '--user='*) option_user="${item#*=}" ;; + '--group='*) option_group="${item#*=}" ;; + '--uid='* | '--user-id='*) option_user_ids+=("${item#*=}") ;; + '--gid='* | '--group-id='*) option_group_ids+=("${item#*=}") ;; + '--u='* | '--uname='* | '--user-name='*) option_user_names+=("${item#*=}") ;; + '--g='* | '--gname='* | '--group-name='*) option_group_names+=("${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 + if [[ ${#option_user_ids[@]} -eq 0 && ${#option_user_names[@]} -eq 0 && ${#option_group_ids[@]} -eq 0 && ${#option_group_names[@]} -eq 0 ]]; then + option_user_ids+=("$(id -u)") + IFS=' ' read -ra option_group_ids < <(id -G) + fi + + # ===================================== + # Action + + local uid uname gid gname + if is-mac; then + function __get_owner { + IFS=' ' read -r uid uname gid gname < <(stat -Lf '%u %Su %g %Sg' -- "$1") + } + else + function __get_owner { + IFS=' ' read -r uid uname gid gname < <(stat -Lc '%u %U %g %G' -- "$1") + } + fi + function __is_owner { + local input item + if [[ ${1-} == '--' ]]; then + shift + fi + for input in "$@"; do + is-present.bash -- "$input" || return $? + __get_owner "$input" + if [[ ${#option_user_ids[@]} -ne 0 ]]; then + for item in "${option_user_ids[@]}"; do + if [[ $uid == "$item" ]]; then + return 0 + fi + done + fi + if [[ ${#option_user_names[@]} -ne 0 ]]; then + for item in "${option_user_names[@]}"; do + if [[ $uname == "$item" ]]; then + return 0 + fi + done + fi + if [[ ${#option_group_ids[@]} -ne 0 ]]; then + for item in "${option_group_ids[@]}"; do + if [[ $gid == "$item" ]]; then + return 0 + fi + done + fi + if [[ ${#option_group_names[@]} -ne 0 ]]; then + for item in "${option_group_names[@]}"; do + if [[ $gname == "$item" ]]; then + return 0 + fi + done + fi + done + return 93 # ENOATTR 93 Attribute not found + } + sudo_helper --inherit --sudo="$option_sudo" --user="$option_user" --group="$option_group" -- __is_owner -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_owner_test + else + is_owner "$@" + fi +fi diff --git a/commands/is-present b/commands/is-present index a8017430b..8230dbcb3 100755 --- a/commands/is-present +++ b/commands/is-present @@ -1,5 +1,67 @@ #!/usr/bin/env bash +function is_present_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + 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=2 \ + -- is-present -- "$DOROTHY/this-does-not-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" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-present-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + 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 is present' \ + -- is-present -- "$dir_symlink" + + eval-tester --name='broken symlink file is present' \ + -- is-present -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) function is_present() ( source "$DOROTHY/sources/bash.bash" @@ -9,38 +71,41 @@ 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 (including that of symlinks, broken or otherwise). + Companion to [is-missing], [echo-if-present]. + Similar to a [is-resolvable] which would exclude broken symlinks. USAGE: - is-presen [...options] [--] ... + is-present [...options] [--] ... OPTIONS: - --sudo - If specified, use sudo on filesystem interactions. + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. --user= --group= - If specified use this user and/or group for filesystem interactions. + Forwarded to [sudo-helper]. RETURNS: - [0] if all s were present - [1] if any s were not present + [0] if all s were present (file, directory, symlink, broken symlink). + [2] if a was not present + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided EOF - if test "$#" -ne 0; then + 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 test "$#" -ne 0; do + local item option_inputs=() option_sudo='13' 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")" + option_sudo="$(get-flag-value --affirmative --fallback-on-empty --fallback="$option_sudo" -- "$item")" ;; '--user='*) option_user="${item#*=}" ;; '--group='*) option_group="${item#*=}" ;; @@ -55,37 +120,22 @@ function is_present() ( done # verify - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s provided.' fi # ===================================== # Action - function __sudo_wrapper { - while test "$1" = '--'; do - shift - done - if test "$option_sudo" = 'yes' -o -n "$option_user" -o -n "$option_group"; then - sudo-helper --inherit --user="$option_user" --group="$option_group" -- "$@" - return - else - "$@" - return - fi - } - - local input - for input in "${option_inputs[@]}"; do - # just -e is faulty, as -e fails on broken symlinks - if __sudo_wrapper -- test ! -e "$input" -a ! -L "$input"; then - return 1 - fi - done - 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 test "$0" = "${BASH_SOURCE[0]}"; then - is_present "$@" +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_present_test + else + is_present "$@" + fi fi diff --git a/commands/is-present.bash b/commands/is-present.bash new file mode 100755 index 000000000..5cf60d025 --- /dev/null +++ b/commands/is-present.bash @@ -0,0 +1,26 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -e $path || -L $path ]]; then + # exists: is a symlink (broken or otherwise, accessible or otherwise), file, or directory + continue + else + # discern if inaccessible, missing + is-accessible.bash -- "$path" || exit $? + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-raspi b/commands/is-raspi index cb4580e01..488a26ed0 100755 --- a/commands/is-raspi +++ b/commands/is-raspi @@ -18,7 +18,7 @@ function is_raspi() ( [0] if the system is a Raspberry Pi [1] if the system is not a Raspberry Pi EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_raspi() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_raspi() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_raspi "$@" fi diff --git a/commands/is-readable b/commands/is-readable new file mode 100755 index 000000000..cd91ccf81 --- /dev/null +++ b/commands/is-readable @@ -0,0 +1,272 @@ +#!/usr/bin/env bash + +function is_readable_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + local root command='is-readable' + root="$(fs_tests__prep "$command")" + + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- "$command" -- + + # test no escalation + local tuples=( + 22 '' + + 2 "$root/missing-dir/missing-file" + 2 "$root/missing-file" + + 0 "$root/targets/empty-dir" + 0 "$root/targets/empty-file" + 0 "$root/targets/filled-dir/empty-subfile" + 0 "$root/targets/filled-dir/filled-subdir" + 0 "$root/targets/filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/filled-dir/filled-subfile" + 0 "$root/targets/filled-file" + # these aren't executable because they are only executable to root + # so in which case a fs-own +x on these, results in + # chmod: Unable to change file mode on /Users/balupton/.cache/dorothy/is-executable/targets/unaccessible-empty-file: Operation not permitted + # in which we need to detect that, and escalate to root, so there needs to be a is-owned command too, which fs-own uses + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/targets/unaccessible-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/targets/unaccessible-filled-file" + 0 "$root/targets/unexecutable-empty-dir" + 0 "$root/targets/unexecutable-empty-file" + 0 "$root/targets/unexecutable-filled-dir" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/targets/unexecutable-filled-file" + "$(__status__root_or_nonroot 93 13)" "$root/targets/unreadable-empty-dir" + "$(__status__root_or_nonroot 93 13)" "$root/targets/unreadable-empty-file" + "$(__status__root_or_nonroot 93 13)" "$root/targets/unreadable-filled-dir" + 0 "$root/targets/unreadable-filled-dir/empty-subfile" + 0 "$root/targets/unreadable-filled-dir/filled-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unreadable-filled-dir/filled-subfile" + "$(__status__root_or_nonroot 93 13)" "$root/targets/unreadable-filled-file" + 0 "$root/targets/unwritable-empty-dir" + 0 "$root/targets/unwritable-empty-file" + 0 "$root/targets/unwritable-filled-dir" + 0 "$root/targets/unwritable-filled-dir/empty-subfile" + 0 "$root/targets/unwritable-filled-dir/filled-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unwritable-filled-dir/filled-subfile" + 0 "$root/targets/unwritable-filled-file" + + 0 "$root/symlinks/empty-dir" + 0 "$root/symlinks/empty-file" + 0 "$root/symlinks/filled-dir--empty-subfile" + 0 "$root/symlinks/filled-dir--filled-subdir" + 0 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/filled-dir--filled-subfile" + 0 "$root/symlinks/filled-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-dir" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-empty-file" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-dir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--empty-subfile" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + "$(__status__root_or_nonroot 0 13)" "$root/symlinks/unaccessible-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 0 93)" "$root/symlinks/unaccessible-filled-file" + 0 "$root/symlinks/unexecutable-empty-dir" + 0 "$root/symlinks/unexecutable-empty-file" + 0 "$root/symlinks/unexecutable-filled-dir" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-file" + "$(__status__root_or_nonroot 93 13)" "$root/symlinks/unreadable-empty-dir" + "$(__status__root_or_nonroot 93 13)" "$root/symlinks/unreadable-empty-file" + "$(__status__root_or_nonroot 93 13)" "$root/symlinks/unreadable-filled-dir" + 0 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unreadable-filled-dir--filled-subfile" + "$(__status__root_or_nonroot 93 13)" "$root/symlinks/unreadable-filled-file" + 0 "$root/symlinks/unwritable-empty-dir" + 0 "$root/symlinks/unwritable-empty-file" + 0 "$root/symlinks/unwritable-filled-dir" + 0 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 0 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test no escalation' "$command" --no-sudo -- "${tuples[@]}" + + # test default escalation + tuples=( + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subfile" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test default escalation' "$command" -- "${tuples[@]}" + + # test with escalation + tuples=( + 0 "$root/targets/unaccessible-filled-dir/empty-subfile" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unaccessible-filled-dir/filled-subfile" + 0 "$root/targets/unexecutable-filled-dir/empty-subfile" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subdir/empty-subdir" + 0 "$root/targets/unexecutable-filled-dir/filled-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 0 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + ) + fs_tests__tuples --group='test with escalation' "$command" --sudo -- "${tuples[@]}" + + # break the symlinks + sudo-helper -- rm -rf "$root/targets" + tuples=( + 9 "$root/symlinks/empty-dir" + 9 "$root/symlinks/empty-file" + 9 "$root/symlinks/filled-dir--empty-subfile" + 9 "$root/symlinks/filled-dir--filled-subdir" + 9 "$root/symlinks/filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/filled-dir--filled-subfile" + 9 "$root/symlinks/filled-file" + 9 "$root/symlinks/unaccessible-empty-dir" + 9 "$root/symlinks/unaccessible-empty-file" + 9 "$root/symlinks/unaccessible-filled-dir" + 9 "$root/symlinks/unaccessible-filled-dir--empty-subfile" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unaccessible-filled-dir--filled-subfile" + 9 "$root/symlinks/unaccessible-filled-file" + 9 "$root/symlinks/unexecutable-empty-dir" + 9 "$root/symlinks/unexecutable-empty-file" + 9 "$root/symlinks/unexecutable-filled-dir" + 9 "$root/symlinks/unexecutable-filled-dir--empty-subfile" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unexecutable-filled-dir--filled-subfile" + 9 "$root/symlinks/unexecutable-filled-file" + 9 "$root/symlinks/unreadable-empty-dir" + 9 "$root/symlinks/unreadable-empty-file" + 9 "$root/symlinks/unreadable-filled-dir" + 9 "$root/symlinks/unreadable-filled-dir--empty-subfile" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unreadable-filled-dir--filled-subfile" + 9 "$root/symlinks/unreadable-filled-file" + 9 "$root/symlinks/unwritable-empty-dir" + 9 "$root/symlinks/unwritable-empty-file" + 9 "$root/symlinks/unwritable-filled-dir" + 9 "$root/symlinks/unwritable-filled-dir--empty-subfile" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subdir--empty-subdir" + 9 "$root/symlinks/unwritable-filled-dir--filled-subfile" + 9 "$root/symlinks/unwritable-filled-file" + ) + fs_tests__tuples --group='test broken symlinks' "$command" -- "${tuples[@]}" + + echo-style --g1="TEST: $0" + return 0 +) +function is_readable() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are readable. + + USAGE: + is-readable [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s are readable + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [93] if a was found but not readable + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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-readable.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_readable_test + else + is_readable "$@" + fi +fi diff --git a/commands/is-readable.bash b/commands/is-readable.bash new file mode 100755 index 000000000..7ae34beee --- /dev/null +++ b/commands/is-readable.bash @@ -0,0 +1,33 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -r $path ]]; then + # does exist: is readable + continue + elif [[ -e $path ]]; then + # does exist: is not readable + exit 93 # ENOATTR 93 Attribute not found + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-root b/commands/is-root new file mode 100755 index 000000000..ad3d798fb --- /dev/null +++ b/commands/is-root @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +# @todo rename to is-root-user or is-user-root + +function is_root_test() ( + source "$DOROTHY/sources/tests.bash" + echo-style --h1="TEST: $0" + + eval-helper --wrap --verbose -- whoami || : + eval-helper --wrap --verbose -- id || : + eval-helper --wrap --verbose -- id -u "$(whoami)" || : + + eval-helper --wrap --verbose -- is-root || : + eval-helper --wrap --verbose -- is-root --quiet || : + eval-helper --wrap --verbose -- is-root --verbose || : + + sudo-helper --wrap --verbose -- whoami || : + sudo-helper --wrap --verbose -- id || : + sudo-helper --wrap --verbose -- id -u "$(sudo-helper --no-wrap -- whoami)" || : + + sudo-helper --inherit --wrap --verbose -- is-root || : + sudo-helper --inherit --wrap --verbose -- is-root --quiet || : + sudo-helper --inherit --wrap --verbose -- is-root --verbose || : + + echo-style --g1="TEST: $0" + return 0 +) +function is_root() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if the currently logged in user, or the specified s are root. + + USAGE: + is-root [...options] [--] ... + + OPTIONS: + --user= + + RETURNS: + [0] if all s were root + [1] if a was not accessible + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_quiet='' option_inputs=() + 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")" + ;; + '--user='*) 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 + option_inputs+=("$(whoami)") + fi + + # ===================================== + # Action + + local user + for user in "${option_inputs[@]}"; do + # if [[ "$(id -u "$user")" -ne 0 ]]; then + if [[ $user != 'root' ]]; then + if [[ -z $option_quiet || $option_quiet == 'no' ]]; then + echo-style --error1='User ' --code-error1="$user" --error1=' is not root.' >/dev/stderr + fi + return 1 + elif [[ $option_quiet == 'no' ]]; then + echo-style --info1='User ' --code-info1="$user" --info1=' is root.' >/dev/stderr + fi + done + return 0 +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_root_test + else + is_root "$@" + fi +fi diff --git a/commands/is-same b/commands/is-same index 3eaa4be5b..c8416ac0d 100755 --- a/commands/is-same +++ b/commands/is-same @@ -28,7 +28,7 @@ function is_same() ( [0] if the same. [1] if different. EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -36,7 +36,7 @@ function is_same() ( # process local item option_algorithm='' option_paths=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -53,7 +53,7 @@ function is_same() ( done # verify - if test "${#option_paths[@]}" -ne 2; then + if [[ ${#option_paths[@]} -ne 2 ]]; then help "Not enough s provided." fi @@ -66,7 +66,7 @@ function is_same() ( second_path="$(fs-absolute -- "${option_paths[1]}")" # same path - if test "$first_path" = "$second_path"; then + if [[ $first_path == "$second_path" ]]; then echo-style \ --header1="$first_path" $'\n' \ $'\n' \ @@ -77,7 +77,7 @@ function is_same() ( fi # same target - if test "$(fs-realpath -- "$first_path")" = "$(fs-realpath -- "$second_path")"; then + if [[ "$(fs-realpath -- "$first_path")" == "$(fs-realpath -- "$second_path")" ]]; then echo-style \ --header1="$first_path" $'\n' \ $'\n' \ @@ -89,9 +89,9 @@ function is_same() ( # same structure local first_structure second_structure - first_structure="$(fs-structure -- "$first_path")" - second_structure="$(fs-structure -- "$second_path")" - if test "$first_structure" != "$second_structure"; then + 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' \ --dim="$first_structure" $'\n' \ @@ -108,7 +108,7 @@ function is_same() ( mapfile -t checksums < <(checksum --untitled --summary --algorithm="$option_algorithm" -- "$first_path" "$second_path") # sanity - if test "${#checksums[@]}" -ne 2; then + if [[ ${#checksums[@]} -ne 2 ]]; then echo-error "Invalid amount of checksums, there should only be two, a summary checksum for each directory, instead there were ${#checksums[@]}:" $'\n' "$(echo-verbose -- "${checksums[@]}")" return 1 fi @@ -117,7 +117,7 @@ function is_same() ( __print_lines "${checksums[@]}" # same checksum - if test "${checksums[0]}" = "${checksums[1]}"; then + if [[ ${checksums[0]} == "${checksums[1]}" ]]; then echo-style --good1="^ same because their checksums match" return 0 fi @@ -128,6 +128,6 @@ function is_same() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_same "$@" fi diff --git a/commands/is-snap b/commands/is-snap index d0012254c..06dd09383 100755 --- a/commands/is-snap +++ b/commands/is-snap @@ -18,7 +18,7 @@ function is_snap() ( [0] if the system has snap [1] if the system does not have snap, or is running on WSL EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_snap() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_snap() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_snap "$@" fi diff --git a/commands/is-ssh b/commands/is-ssh index bcbc2302b..38256863a 100755 --- a/commands/is-ssh +++ b/commands/is-ssh @@ -18,7 +18,7 @@ function is_ssh() ( [0] if the running within a SSH connection [1] if the not running within a SSH connection EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_ssh() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -40,7 +40,7 @@ function is_ssh() ( # Action # this code here should mirror what is inside the oz theme - if test -n "${SSH_CONNECTION-}" -o -n "${SSH_CLIENT-}" -o -n "${SSH_TTY-}"; then + if [[ -n ${SSH_CONNECTION-} || -n ${SSH_CLIENT-} || -n ${SSH_TTY-} ]]; then return 0 else return 1 @@ -48,6 +48,6 @@ function is_ssh() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_ssh "$@" fi diff --git a/commands/is-symlink b/commands/is-symlink new file mode 100755 index 000000000..ca0dad298 --- /dev/null +++ b/commands/is-symlink @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +function is_symlink_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + 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=2 \ + -- is-symlink -- "$DOROTHY/this-does-not-exist" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-symlink-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + 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=79 \ + -- is-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='symlink file then file' --status=79 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='symlink file then missing' --status=2 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/this-does-not-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=79 \ + -- is-symlink -- "$dir_symlink" "$DOROTHY" + + eval-tester --name='broken symlink file then file' --status=79 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/README.md" + + eval-tester --name='broken symlink file then missing' --status=2 \ + -- is-symlink -- "$file_symlink" "$DOROTHY/this-does-not-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. + Companion to [is-broken-symlink], [is-not-symlink]. + + USAGE: + is-symlink [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s were a symlink (broken or otherwise, accessible or otherwise) + [2] if a was not found + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [79] if a was found but not a symlink (broken or otherwise) + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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..c54ccdcdf --- /dev/null +++ b/commands/is-symlink.bash @@ -0,0 +1,29 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -L $path ]]; then + # is a symlink (broken or otherwise, accessible or otherwise) + continue + elif [[ -e $1 ]]; then + # does exist: but not a symlink + exit 79 # EFTYPE 79 Inappropriate file type or format + else + # discern if inaccessible, missing + is-accessible.bash -- "$path" || exit $? + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-ubuntu b/commands/is-ubuntu index 6d5989c04..7b925a450 100755 --- a/commands/is-ubuntu +++ b/commands/is-ubuntu @@ -18,7 +18,7 @@ function is_ubuntu() ( [0] if the system is Ubuntu [1] if the system is not Ubuntu EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_ubuntu() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_ubuntu() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_ubuntu "$@" fi diff --git a/commands/is-user b/commands/is-user index 457ef9179..35ccf2e80 100755 --- a/commands/is-user +++ b/commands/is-user @@ -20,10 +20,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -31,7 +31,7 @@ function is_user() ( # process local item option_users=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -60,6 +60,6 @@ function is_user() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_user "$@" fi diff --git a/commands/is-user-in-group b/commands/is-user-in-group index 8c4e94ea1..503b72398 100755 --- a/commands/is-user-in-group +++ b/commands/is-user-in-group @@ -22,10 +22,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -33,7 +33,7 @@ function is_user_in_group() ( # process local item option_users=() option_groups=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -53,12 +53,12 @@ function is_user_in_group() ( done # ensure user - if test "${#option_users[@]}" -eq 0; then + if [[ ${#option_users[@]} -eq 0 ]]; then option_users+=("$(whoami)") fi # check group - if test "${#option_groups[@]}" -eq 0; then + if [[ ${#option_groups[@]} -eq 0 ]]; then help "No s provided." fi @@ -87,6 +87,6 @@ function is_user_in_group() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_user_in_group "$@" fi diff --git a/commands/is-value b/commands/is-value index 5d39ab123..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,10 +20,10 @@ 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 test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -30,7 +31,7 @@ function is_value() ( # process our arguments local item option_inputs=() - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -50,7 +51,7 @@ function is_value() ( done # check - if test "${#option_inputs[@]}" -eq 0; then + if [[ ${#option_inputs[@]} -eq 0 ]]; then help 'No s was provided.' fi @@ -70,6 +71,6 @@ function is_value() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_value "$@" fi diff --git a/commands/is-vscode b/commands/is-vscode index 62c6f9abf..f72ab5f3d 100755 --- a/commands/is-vscode +++ b/commands/is-vscode @@ -18,7 +18,7 @@ function is_vscode() ( [0] if the environment is within Visual Studio Code [1] if the environment is not within Visual Studio Code EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_vscode() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -39,7 +39,7 @@ function is_vscode() ( # ===================================== # Action - if test "${TERM_PROGRAM-}" = vscode; then + if [[ ${TERM_PROGRAM-} == 'vscode' ]]; then return 0 else return 1 @@ -47,6 +47,6 @@ function is_vscode() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_vscode "$@" fi 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/is-writable b/commands/is-writable new file mode 100755 index 000000000..09a6fd35b --- /dev/null +++ b/commands/is-writable @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +function is_writable_test() ( + source "$DOROTHY/sources/bash.bash" + echo-style --h1="TEST: $0" + + # test standard paths + eval-tester --name='no args' --status=22 --ignore-stderr \ + -- is-writable -- + + eval-tester --name='empty args' --status=22 \ + -- is-writable -- '' '' + + eval-tester --name='missing' --status=2 \ + -- is-writable -- "$DOROTHY/this-does-not-exist" + + eval-tester --name='directory' \ + -- is-writable -- "$DOROTHY" + + eval-tester --name='file' \ + -- is-writable -- "$DOROTHY/README.md" + + eval-tester --name='file then dir' \ + -- is-writable -- "$DOROTHY/README.md" "$DOROTHY" + + eval-tester --name='dir then file' \ + -- is-writable -- "$DOROTHY" "$DOROTHY/README.md" + + eval-tester --name='dir then file then missing then invalid' --status=2 \ + -- is-writable -- "$DOROTHY" "$DOROTHY/README.md" "$DOROTHY/this-does-not-exist" '' + + eval-tester --name='dir then invalid then missing then file' --status=22 \ + -- is-writable -- "$DOROTHY" '' "$DOROTHY/this-does-not-exist" "$DOROTHY/README.md" + + # prep + local root dir_target dir_symlink file_target file_symlink + root="$(fs-temp --directory='is-writable-test')" + fs-rm --quiet --no-confirm -- "$root" + dir_target="$(fs-temp --root="$root" --directory='dir_target' --touch)" + file_target="$(fs-temp --root="$root" --file='file_target' --touch)" + dir_symlink="$(fs-temp --root="$root" --directory='dir_symlink' --no-touch)" + file_symlink="$(fs-temp --root="$root" --file='file_symlink' --no-touch)" + symlink-helper --existing="$dir_target" --symlink="$dir_symlink" --quiet + symlink-helper --existing="$file_target" --symlink="$file_symlink" --quiet + + # test working symlinks + eval-tester --name='target dir' \ + -- is-writable -- "$dir_target" + eval-tester --name='target file' \ + -- is-writable -- "$file_target" + eval-tester --name='symlink dir with writable target' \ + -- is-writable -- "$dir_symlink" + eval-tester --name='symlink file with writable target' \ + -- is-writable -- "$file_symlink" + + # test no longer writable + fs-own --quiet --permissions='-w' -- "$dir_target" "$file_target" + eval-tester --name='non-writable target dir' --status=93 \ + -- is-writable -- "$dir_target" + eval-tester --name='non-writable target file' --status=93 \ + -- is-writable -- "$file_target" + eval-tester --name='non-writable symlink dir with non-writable target' --status=93 \ + -- is-writable -- "$dir_symlink" + eval-tester --name='non-writable symlink file with non-writable target' --status=93 \ + -- is-writable -- "$file_symlink" + + # test broken symlinks + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + eval-tester --name='broken symlink dir with previously non-writable target (permissions discarded apparently)' --status=93 \ + -- is-writable -- "$dir_symlink" + eval-tester --name='broken symlink file with previously non-writable target (permissions discarded apparently)' --status=93 \ + -- is-writable -- "$file_symlink" + + # recreate targets, make them writable + __mkdirp "$dir_target" + touch "$file_target" + fs-own --quiet --permissions='+w' -- "$dir_target" "$file_target" + eval-tester --name='symlink dir with writable target' \ + -- is-writable -- "$dir_symlink" + eval-tester --name='symlink file with writable target' \ + -- is-writable -- "$file_symlink" + fs-rm --quiet --no-confirm -- "$dir_target" "$file_target" + eval-tester --name='broken symlink dir with previously writable target (permissions discarded apparently)' --status=93 \ + -- is-writable -- "$dir_symlink" + eval-tester --name='broken symlink file with previously writable target (permissions discarded apparently)' --status=93 \ + -- is-writable -- "$file_symlink" + + echo-style --g1="TEST: $0" + return 0 +) +function is_writable() ( + source "$DOROTHY/sources/bash.bash" + + # ===================================== + # Arguments + + function help { + cat <<-EOF >/dev/stderr + ABOUT: + Check if all s are writable. + + USAGE: + is-writable [...options] [--] ... + + OPTIONS: + --sudo= + Defaults to [13], escalating to root if permission would otherwise be denied. + --user= + --group= + Forwarded to [sudo-helper]. + + RETURNS: + [0] if all s are writable + [2] if a was not found + [9] if a was a broken symlink + [13] if a was not accessible: unless [--no-sudo] was provided a sudo request will be made to prevent this failure. + [22] if empty arguments are provided + [93] if a was found but not writable + EOF + if [[ $# -ne 0 ]]; then + echo-error "$@" + fi + return 22 # EINVAL 22 Invalid argument + } + + # process + local item option_inputs=() option_sudo='13' 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-on-empty --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-writable.bash -- "${option_inputs[@]}" + return +) + +# fire if invoked standalone +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then + if [[ $* == '--test' ]]; then + is_writable_test + else + is_writable "$@" + fi +fi diff --git a/commands/is-writable.bash b/commands/is-writable.bash new file mode 100755 index 000000000..ad70dca1a --- /dev/null +++ b/commands/is-writable.bash @@ -0,0 +1,33 @@ +#!/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 + path="$1" + shift + + # checks + if [[ -w $path ]]; then + # does exist: is writable + continue + elif [[ -e $path ]]; then + # does exist: is not writable + exit 93 # ENOATTR 93 Attribute not found + else + # discern if inaccessible, broken, missing + is-accessible.bash -- "$path" || exit $? + if [[ -L $path ]]; then + # broken symlink + exit 9 # EBADF 9 Bad file descriptor + fi + exit 2 # ENOENT 2 No such file or directory + fi +done +exit 0 diff --git a/commands/is-wsl b/commands/is-wsl index f81d2f436..5a2925bb5 100755 --- a/commands/is-wsl +++ b/commands/is-wsl @@ -18,7 +18,7 @@ function is_wsl() ( [0] if the environment is WSL [1] if the environment is not WSL EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -26,7 +26,7 @@ function is_wsl() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -47,6 +47,6 @@ function is_wsl() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then is_wsl "$@" fi diff --git a/commands/macos-drive b/commands/macos-drive index f2f6c12d2..53399b9c6 100755 --- a/commands/macos-drive +++ b/commands/macos-drive @@ -14,7 +14,7 @@ function macos_drive() ( USAGE: macos-drive EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -22,7 +22,7 @@ function macos_drive() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -54,7 +54,7 @@ function macos_drive() ( name="$(basename "$installer")" volume="$(choose_volume)" eval_capture --statusvar=confirm_status -- confirm_installation - if test "$confirm_status" -eq 0; then + if [[ $confirm_status -eq 0 ]]; then do_installation else return 125 # ECANCELED 125 Operation cancelled @@ -62,6 +62,6 @@ function macos_drive() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then macos_drive "$@" fi diff --git a/commands/macos-installer b/commands/macos-installer index d9833db30..29c1c5421 100755 --- a/commands/macos-installer +++ b/commands/macos-installer @@ -15,7 +15,7 @@ function macos_installer() ( USAGE: macos-installer EOF - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then echo-error "$@" fi return 22 # EINVAL 22 Invalid argument @@ -23,7 +23,7 @@ function macos_installer() ( # process local item - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do item="$1" shift case "$item" in @@ -44,7 +44,7 @@ function macos_installer() ( function do_download { local confirm_status eval_capture --statusvar=confirm_status -- confirm_download - if test "$confirm_status" -eq 0; then + if [[ $confirm_status -eq 0 ]]; then # `softwareupdate --download` flag doesn't work, despite documentation softwareupdate --fetch-full-installer >/dev/tty else @@ -54,10 +54,10 @@ function macos_installer() ( local installers=() installers=('/Applications/Install macOS'*) - if test "${#installers[@]}" -eq 0; then + if [[ ${#installers[@]} -eq 0 ]]; then do_download installers=('/Applications/Install macOS'*) - if test "${#installers[@]}" -eq 0; then + if [[ ${#installers[@]} -eq 0 ]]; then echo-error 'Could not find the downloaded installer. This is unexpected.' $'\n' 'Report an issue at: https://github.com/bevry/dorothy/issues' return 3 # ESRCH 3 No such process fi @@ -68,6 +68,6 @@ function macos_installer() ( ) # fire if invoked standalone -if test "$0" = "${BASH_SOURCE[0]}"; then +if [[ $0 == "${BASH_SOURCE[0]}" ]]; then macos_installer "$@" fi diff --git a/commands/mount-helper b/commands/mount-helper index d02f36d26..d773280ea 100755 --- a/commands/mount-helper +++ b/commands/mount-helper @@ -55,7 +55,7 @@ ## SAMBA - UBUNTU # //192.168.4.21/balupton on /home/balupton/Testing/samba type cifs (rw,relatime,vers=default,cache=strict,username=balupton,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.4.21,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=1,closetimeo=1) -## SAMBA - MACOS (afpfs is for legacy time capsule sharing, is legacy, is available by finder and open, it is not availale for mount command - however time capsule sharing is mountable as smbfs with mount command) +## SAMBA - MACOS (afpfs is for legacy time capsule sharing, is legacy, is available by finder and open, it is not available for mount command - however time capsule sharing is mountable as smbfs with mount command) # //url-escaped-username@server/share on /Volumes/share (smbfs, nodev, nosuid, mounted by balupton) # //Benjamin%20Lupton@tc._afpovertcp._tcp.local/Data on /Volumes/Data (afpfs, nodev, nosuid, mounted by balupton) # //balupton@balupton-mbp._smb._tcp.local/System on /Volumes/System (smbfs, nodev, nosuid, mounted by balupton) @@ -497,13 +497,16 @@ function mount_helper() ( # Helpers function __is_samba { - [[ $1 =~ ^(samba|smb|smbfs|cifs)$ ]] || return # explicit return with [[ required for bash v3 + [[ $1 =~ ^(samba|smb|smbfs|cifs)$ ]] + return } function __is_gocryptfs { - [[ $1 =~ ^(gocryptfs|fuse.gocryptfs)$ ]] || return # explicit return with [[ required for bash v3 + [[ $1 =~ ^(gocryptfs|fuse.gocryptfs)$ ]] + return } function join { echo-join ',' -- "$@" + return } function stringify_args { # output non-empty arguments as space separated strings @@ -518,14 +521,14 @@ function mount_helper() ( } function string_dump { local result=() arg value - while test "$#" -ne 0; do + while [[ $# -ne 0 ]]; do arg="$1" shift - if test -z "$arg"; then + if [[ -z $arg ]]; then result+=($'\n') else value="${!arg}" - if test -n "$value"; then + if [[ -n $value ]]; then value="--invert=$value" fi result+=( @@ -533,7 +536,7 @@ function mount_helper() ( ' = ' "$value" ) - if test "$#" -ne 0; then + if [[ $# -ne 0 ]]; then result+=($'\n') fi fi @@ -543,14 +546,14 @@ function mount_helper() ( function value_dump { local result=() arg value mode='var' for arg in "$@"; do - if test "$mode" = 'var'; then + if [[ $mode == 'var' ]]; then result+=( --bold="$arg" ' = ' ) mode='value' else - if test -n "$arg"; then + if [[ -n $arg ]]; then result+=( "--invert=$arg" ) @@ -604,29 +607,29 @@ function mount_helper() ( local uses_ownership='no' uses_fstab='' uses_cron='' uses_wait='no' uses_mkdir='no' uses_root_for_mount='no' uses_root_for_unmount='no' # skip if first -- - if test -z "$option_type" -a -z "$option_server" -a -z "$option_share" -a -z "$option_source" -a -z "$option_target"; then + if [[ -z $option_type && -z $option_server && -z $option_share && -z $option_source && -z $option_target ]]; then return 0 fi # prepare ownership - if test "$option_own" = 'yes' -o -n "$option_owner" -o -n "$option_user" -o -n "$option_group"; then + if [[ $option_own == 'yes' || -n $option_owner || -n $option_user || -n $option_group ]]; then uses_ownership='yes' fi # prepare automount # don't infer automount from fstab/cron, as [--fstab] can or cannot be automount, and [--fstab] is not [--cron] - if test -n "$option_automount"; then + if [[ -n $option_automount ]]; then # default both if not specified - if test -z "$option_cron"; then + if [[ -z $option_cron ]]; then option_cron="$option_automount" fi - if test -z "$option_fstab"; then + if [[ -z $option_fstab ]]; then option_fstab="$option_automount" fi fi # option_{automount,fstab,cron} could still be empty string, as intended, which allows autodetection - # generat the title, and use it to check we have a valid action + # generate the title, and use it to check we have a valid action local title='mount-helper' if [[ $option_actions == *'[parse]'* ]]; then title+=' --parse' @@ -640,28 +643,28 @@ function mount_helper() ( if [[ $option_actions == *'[unmount]'* ]]; then title+=' --unmount' fi - if test -n "$option_automount"; then + if [[ -n $option_automount ]]; then title+=" --automount=$option_automount" fi - if test "$title" = 'mount-helper'; then + if [[ $title == 'mount-helper' ]]; then help "No was provided, at least one of these is required: --parse, --check, --mount, --unmount, --automount, --noautomount" fi # now that the title is checked for a valid action, add important options to the title - if test -n "$option_cron"; then + if [[ -n $option_cron ]]; then title+=" --cron=$option_cron" fi - if test -n "$option_fstab"; then + if [[ -n $option_fstab ]]; then title+=" --fstab=$option_fstab" fi # coerce target - if test -n "$option_target"; then + if [[ -n $option_target ]]; then option_target="$(fs-realpath --no-validate -- "$option_target")" fi # coerce source options by parsing source - if test -n "$option_source"; then + if [[ -n $option_source ]]; then temp='' # {type}:?//{user}:{password}@{server}/{share} if [[ $temp == *'://'* ]]; then @@ -671,8 +674,8 @@ function mount_helper() ( option_type="${temp%%//*}" temp="${temp#*//}" fi - # check [what] was supplied, as inferance is possible but problematic - if test -z "$option_type"; then + # check [what] was supplied, as inference is possible but problematic + if [[ -z $option_type ]]; then help 'When using ' --code="--source=$option_source" ' ensure ' --code='://...' ' or ' --code='--type=' ' is provided' fi # {username}:{password}@{server}/{share} @@ -703,8 +706,8 @@ function mount_helper() ( option_type='samba' # validate - if test -n "$option_server" -o -n "$option_share"; then # optional under [check] - if test -z "$option_server" -o -z "$option_share"; then + if [[ -n $option_server || -n $option_share ]]; then # optional under [check] + if [[ -z $option_server || -z $option_share ]]; then help 'When using , both and must be used together' fi fi @@ -716,7 +719,7 @@ function mount_helper() ( fstab_type='smbfs' fstab_options+=( - noatime # ubuntu mount, macos mount, (not supported by gofscryptfs) + noatime # ubuntu mount, macos mount, (not supported by gocryptfs) nodev # ubuntu mount, macos mount, gocryptfs mount noexec # ubuntu mount, macos mount, gocryptfs mount noowners # macos mount @@ -732,22 +735,22 @@ function mount_helper() ( fi # coerce source - if test -n "$option_share"; then # optional under [check] + if [[ -n $option_share ]]; then # optional under [check] open_source="smb://" mount_source='//' mounted_source='//' fstab_source='//' # @todo consider using guest as default username - if test -n "$option_username" -o "$option_password"; then - if test -n "$option_username"; then + if [[ -n $option_username || -n $option_password ]]; then + if [[ -n $option_username ]]; then temp="$(echo-url-encode -- "$option_username")" open_source+="$temp" mount_source+="$temp" mounted_source+="$temp" fstab_source+="$temp" fi - if test -n "$option_password"; then + if [[ -n $option_password ]]; then temp=":$(echo-url-encode -- "$option_password")" open_source+="$temp" mount_source+="$temp" @@ -767,7 +770,7 @@ function mount_helper() ( mounted_source+="$temp" fstab_source+="$temp" - if test -z "$option_target"; then + if [[ -z $option_target ]]; then option_target="/Volumes/$option_share" fi if [[ $option_target == '/Volumes/'* ]]; then @@ -799,24 +802,24 @@ function mount_helper() ( noexec # ubuntu mount, macos mount noatime # ubuntu mount, macos mount nodev # ubuntu mount, macos mount - nosetuids # ubuntu mount (only samba), (not supported by gofscryptfs) + nosetuids # ubuntu mount (only samba), (not supported by gocryptfs) rw # ubuntu mount, macos mount has [rdonly], gocryptfs mount - user # ubuntu mount, (not supported by gofscryptfs) + user # ubuntu mount, (not supported by gocryptfs) # X-mount.mkdir # ubuntu mount (doesn't work) ) - if test -n "$option_username"; then + if [[ -n $option_username ]]; then fstab_options+=( "username=$(echo-fstab-encode -- "$option_username")" ) fi - if test -n "$option_password"; then + if [[ -n $option_password ]]; then fstab_options+=( "password=$(echo-fstab-encode -- "$option_password")" ) fi # coerce source - if test -n "$option_share"; then # check can miss this + if [[ -n $option_share ]]; then # check can miss this mount_source='//' mounted_source='//' fstab_source='//' @@ -844,7 +847,7 @@ function mount_helper() ( fstab_options+=( nodev # ubuntu mount, macos mount, gocryptfs mount noexec # ubuntu mount, macos mount, gocryptfs mount - noprealloc # gocryptfs mount (for btrfs peformance) + noprealloc # gocryptfs mount (for btrfs performance) nosuid # macos mount, gocryptfs mount rw # ubuntu mount, macos mount has [rdonly], gocryptfs mount ) @@ -853,11 +856,11 @@ function mount_helper() ( gocryptfs_options+=( '--nodev' # ubuntu mount, macos mount, gocryptfs mount '--noexec' # ubuntu mount, macos mount, gocryptfs mount - '--noprealloc' # gocryptfs mount (for btrfs peformance) + '--noprealloc' # gocryptfs mount (for btrfs performance) '--nosuid' # macos mount, gocryptfs mount '--rw' # ubuntu mount, macos mount has [rdonly], gocryptfs mount ) - if test -n "$option_password"; then + if [[ -n $option_password ]]; then # --extpass stringArray Use external program for the password prompt gocryptfs_options+=( "--extpass=echo $option_password" @@ -866,7 +869,7 @@ function mount_helper() ( "extpass=$(echo-fstab-encode -- "echo $option_password")" ) fi - if test -n "$option_owner"; then + if [[ -n $option_owner ]]; then if is-mac; then echo-error ' with is not supported on macOS.' return 19 # ENODEV 19 Operation not supported by device @@ -886,23 +889,23 @@ function mount_helper() ( fi # coerce source - if test -n "$option_share"; then # option under [check] + if [[ -n $option_share ]]; then # option under [check] option_share="$(fs-realpath --no-validate -- "$option_share")" gocryptfs_source="$option_share" if ! is-mac; then mount_source="$option_share" fi mounted_source="$option_share" - if test -n "$fstab_type"; then + if [[ -n $fstab_type ]]; then fstab_source="$(echo-fstab-encode -- "$option_share")" fi - if test -n "$option_target"; then # optional under [check] + if [[ -n $option_target ]]; then # optional under [check] # gocryptfs gocryptfs_cmd=( 'gocryptfs' ) - if test "${#gocryptfs_options[@]}" -ne 0; then + if [[ ${#gocryptfs_options[@]} -ne 0 ]]; then gocryptfs_cmd+=("${gocryptfs_options[@]}") fi gocryptfs_cmd+=( @@ -912,7 +915,7 @@ function mount_helper() ( fi fi - elif test -n "$option_type"; then + elif [[ -n $option_type ]]; then # btrfs, filesystem, etc mount_type="$option_type" mounted_type="$option_type" @@ -923,7 +926,7 @@ function mount_helper() ( if is-mac; then fstab_options+=( - noatime # ubuntu mount, macos mount, (not supported by gofscryptfs) + noatime # ubuntu mount, macos mount, (not supported by gocryptfs) nodev # ubuntu mount, macos mount, gocryptfs mount noexec # ubuntu mount, macos mount, gocryptfs mount noowners # macos mount @@ -944,14 +947,15 @@ function mount_helper() ( # verify # use --mounted then --no-mounted to prefer the node of the filesystem which is actually mounted eval_capture --statusvar=temp_status --stdoutvar=temp -- get-devices --quiet --result=node --mounted --node="$option_share" --filesystem="$mount_type" --label="$option_label" --count="$option_count" - if test -z "$temp" -o "$temp_status" = 6; then + if [[ -z $temp || $temp_status == 6 ]]; then # no mounted devices, so try unmount devices eval_capture --statusvar=temp_status --stdoutvar=temp -- get-devices --quiet --result=node --no-mounted --node="$option_share" --filesystem="$mount_type" --label="$option_label" --count="$option_count" fi - if test -n "$temp" -a "$option_label"; then + if [[ -n $temp ]]; then + # get only the first line option_share="$(echo-first-line --stdin <<<"$temp")" fi - if test "$temp_status" = 0 -a -n "$temp"; then + if [[ $temp_status == 0 && -n $temp ]]; then echo-style --success='Validated' ' ' --invert="$option_share" ' ' --invert="$mount_type" ' ' --invert="$option_label" ' ' --invert="$option_count" else echo-style --error='Failed to validate' ' ' --invert="$option_share" ' ' --invert="$mount_type" ' ' --invert="$option_label" ' ' --invert="$option_count" @@ -963,7 +967,7 @@ function mount_helper() ( fi # coerce source - if test -n "$option_share"; then # option under [check] + if [[ -n $option_share ]]; then # option under [check] mount_source="$option_share" mounted_source="$option_share" fstab_source="$(echo-fstab-encode -- "$option_share")" @@ -971,18 +975,18 @@ function mount_helper() ( fi # coerce automount lines - if test -z "$fstab_source" -o -z "$fstab_type"; then + if [[ -z $fstab_source || -z $fstab_type ]]; then fstab_source='' fstab_type='' uses_fstab='no' fi - if test -n "$option_share"; then + if [[ -n $option_share ]]; then cron_source="$(echo-escape-command -- "$option_share")" fi - if test -n "$option_target"; then + if [[ -n $option_target ]]; then # cron cron_target="$(echo-escape-command -- "$option_target")" - if test -n "$option_share"; then + if [[ -n $option_share ]]; then cron_args=( "$(type -P dorothy | echo-escape-command --stdin)" 'run' @@ -1005,7 +1009,7 @@ function mount_helper() ( ) cron_line="*/15 * * * * $(stringify_args "${cron_args[@]}")" # autodetect uses_cron - if test -z "$option_cron"; then + if [[ -z $option_cron ]]; then if config-edit --cron-system --has --line="$cron_target" || config-edit --cron-user --has --line="$cron_target"; then uses_cron='yes' else @@ -1017,12 +1021,12 @@ function mount_helper() ( fi # fstab fstab_target="$(echo-fstab-encode -- "$option_target")" - if test -n "$fstab_source" -a -n "$fstab_type"; then + if [[ -n $fstab_source && -n $fstab_type ]]; then # autodetect uses_fstab - if test -z "$uses_fstab"; then + if [[ -z $uses_fstab ]]; then uses_fstab="$option_fstab" fi - if test -z "$uses_fstab"; then + if [[ -z $uses_fstab ]]; then temp="$fstab_source $fstab_target $fstab_type" if config-edit --fstab --has --line="$temp"; then uses_fstab='yes' @@ -1031,7 +1035,7 @@ function mount_helper() ( fi fi # autodetect option_fstab, here as we still want autodetection even when uses_fstab is no (change their mind) - if test -z "$option_automount"; then + if [[ -z $option_automount ]]; then temp="$fstab_source $fstab_target $fstab_type $(join auto "${fstab_options[@]}") 0 0" if config-edit --fstab --has --line="$temp"; then option_automount='yes' @@ -1040,8 +1044,8 @@ function mount_helper() ( fi fi # generate the appropriate - if test "$uses_fstab" = 'yes'; then - if test "$option_automount" = 'yes'; then + if [[ $uses_fstab == 'yes' ]]; then + if [[ $option_automount == 'yes' ]]; then fstab_line="$fstab_source $fstab_target $fstab_type $(join auto "${fstab_options[@]}") 0 0" else fstab_line="$fstab_source $fstab_target $fstab_type $(join noauto "${fstab_options[@]}") 0 0" @@ -1049,12 +1053,12 @@ function mount_helper() ( fi fi fi - if test -n "$fstab_target"; then + if [[ -n $fstab_target ]]; then fstab_needle="$fstab_target" else fstab_needle="$fstab_source" fi - if test -n "$cron_target"; then + if [[ -n $cron_target ]]; then cron_needle="$cron_target" else cron_needle="$cron_source" @@ -1067,20 +1071,20 @@ function mount_helper() ( '--' ) exists_root_cmd+=("${root_cmd[@]}") - if test -n "$option_user" -o -n "$option_group"; then + if [[ -n $option_user || -n $option_group ]]; then owner_cmd=( 'sudo-helper' '--inherit' ) - if test -n "$option_user"; then + if [[ -n $option_user ]]; then owner_cmd+=("--user=$option_user") fi - if test -n "$option_group"; then + if [[ -n $option_group ]]; then owner_cmd+=("--group=$option_group") fi owner_cmd+=('--') fi - if test -n "$mount_source" -a -n "$mount_type" -a -n "$option_target"; then + if [[ -n $mount_source && -n $mount_type && -n $option_target ]]; then # here for --parse debugging if is-mac; then mount_cmd+=( @@ -1093,13 +1097,13 @@ function mount_helper() ( --verbose ) fi - if test "$uses_fstab" = 'yes'; then + if [[ $uses_fstab == 'yes' ]]; then mount_cmd+=("$option_target") else mount_cmd+=( '-t' "$mount_type" ) - if test "${#fstab_options[@]}" -ne 0; then + if [[ ${#fstab_options[@]} -ne 0 ]]; then mount_cmd+=( '-o' "$(join "${fstab_options[@]}")" ) @@ -1110,31 +1114,29 @@ function mount_helper() ( ) fi fi - if test -n "$option_target"; then + if [[ -n $option_target ]]; then rm_root_cmd=( - fs-rm - --sudo + fs-rm --sudo --no-confirm-if-empty -- "$option_target" ) - if test "$uses_root_for_mount" = 'yes'; then + if [[ $uses_root_for_mount == 'yes' ]]; then mounting_cmd+=("${root_cmd[@]}") mkdir_cmd+=("${root_cmd[@]}") rm_cmd+=( - fs-rm - --sudo + fs-rm --sudo --no-confirm-if-empty -- "$option_target" ) - elif test "$uses_ownership" = 'yes'; then + elif [[ $uses_ownership == 'yes' ]]; then mounting_cmd+=("${owner_cmd[@]}") mkdir_cmd+=("${owner_cmd[@]}") - rm_cmd+=('fs-rm') - if test -n "$option_user"; then + rm_cmd+=(fs-rm --no-confirm-if-empty) + if [[ -n $option_user ]]; then rm_cmd+=("--user=$option_user") fi - if test -n "$option_group"; then + if [[ -n $option_group ]]; then rm_cmd+=("--group=$option_group") fi rm_cmd+=( @@ -1143,26 +1145,26 @@ function mount_helper() ( ) else rm_cmd+=( - fs-rm + fs-rm --no-confirm-if-empty -- "$option_target" ) fi - if test "$uses_ownership" = 'yes'; then + if [[ $uses_ownership == 'yes' ]]; then exists_cmd+=("${owner_cmd[@]}") ls_cmd+=("${owner_cmd[@]}") fi - if test "$uses_fstab" = 'yes' -o "$uses_root_for_unmount" = 'yes'; then + if [[ $uses_fstab == 'yes' || $uses_root_for_unmount == 'yes' ]]; then unmount_cmd+=("${root_cmd[@]}") unmount_force_cmd+=("${root_cmd[@]}") fi - if test "$uses_fstab" = 'yes'; then + if [[ $uses_fstab == 'yes' ]]; then mounting_cmd+=("${mount_cmd[@]}") - elif test "${#open_cmd[@]}" -ne 0; then + elif [[ ${#open_cmd[@]} -ne 0 ]]; then mounting_cmd+=("${open_cmd[@]}") - elif test "${#gocryptfs_cmd[@]}" -ne 0; then + elif [[ ${#gocryptfs_cmd[@]} -ne 0 ]]; then mounting_cmd+=("${gocryptfs_cmd[@]}") - elif test "${#mount_cmd[@]}" -ne 0; then + elif [[ ${#mount_cmd[@]} -ne 0 ]]; then mounting_cmd+=("${mount_cmd[@]}") fi exists_cmd+=( @@ -1214,13 +1216,13 @@ function mount_helper() ( fi # coerce check - if test -n "$mounted_source"; then + if [[ -n $mounted_source ]]; then check_source="$mounted_source on " fi - if test -n "$option_target"; then + if [[ -n $option_target ]]; then check_target=" on $option_target " fi - if test -n "$mounted_type"; then + if [[ -n $mounted_type ]]; then if is-mac; then check_type+=" ($mounted_type" else @@ -1230,43 +1232,43 @@ function mount_helper() ( # coerce log log_source="$option_type" - if test -n "$option_own"; then + if [[ -n $option_own ]]; then log_source+="[own=$option_own]" fi - if test -n "$option_user"; then + if [[ -n $option_user ]]; then log_source+="[user=$option_user]" fi - if test -n "$option_group"; then + if [[ -n $option_group ]]; then log_source+="[group=$option_group]" fi - if test -n "$option_owner"; then + if [[ -n $option_owner ]]; then log_source+="[owner=$option_owner]" fi - if test -n "$option_username"; then + if [[ -n $option_username ]]; then log_source+="[username=$option_username]" fi - if test -n "$option_password"; then + if [[ -n $option_password ]]; then log_source+="[password=redacted]" fi - if test -n "$option_server"; then + if [[ -n $option_server ]]; then log_source+="[server=$option_server]" fi - if test -n "$option_label"; then + if [[ -n $option_label ]]; then log_source+="[label=$option_label]" fi - if test -n "$option_count"; then + if [[ -n $option_count ]]; then log_source+="[count=$option_count]" fi - if test -n "$option_share"; then + if [[ -n $option_share ]]; then log_source+="[share=$option_share]" fi # helpers function log_source_target { - if test -n "$log_source"; then + if [[ -n $log_source ]]; then echo-style --bold='source' ' = ' --code="$log_source" fi - if test -n "$option_target"; then + if [[ -n $option_target ]]; then echo-style --bold='target' ' = ' --code="$option_target" fi } @@ -1310,26 +1312,26 @@ function mount_helper() ( local add_fstab_line add_cron_line # determine - if test -n "$fstab_line" -a "$uses_fstab" = 'yes' -a "${1-}" != 'remove'; then + if [[ -n $fstab_line && $uses_fstab == 'yes' && ${1-} != 'remove' ]]; then add_fstab_line='yes' else add_fstab_line='no' fi - if test -n "$cron_line" -a "$uses_cron" = 'yes' -a "${1-}" != 'remove'; then + if [[ -n $cron_line && $uses_cron == 'yes' && ${1-} != 'remove' ]]; then add_cron_line='yes' else add_cron_line='no' fi # fstab - if test -n "$fstab_needle"; then + if [[ -n $fstab_needle ]]; then echo-style --h3='fstab' - if test -n "$fstab_source"; then + if [[ -n $fstab_source ]]; then echo-style --invert='fstab' ' is ' --positive='supported' ' for ' --code="$log_source" else echo-style --invert='fstab' ' is ' --negative='unsupported' ' for ' --code="$log_source" fi - if test "$add_fstab_line" = 'yes'; then + if [[ $add_fstab_line == 'yes' ]]; then echo-style --invert='fstab' ' is ' --positive='desired.' else echo-style --invert='fstab' ' is ' --negative='undesired.' @@ -1340,20 +1342,20 @@ function mount_helper() ( fi # cron - if test -n "$cron_needle"; then + if [[ -n $cron_needle ]]; then echo-style --h3='cron' - if test -n "$cron_line"; then + if [[ -n $cron_line ]]; then echo-style --invert='cron' ' is ' --positive='supported' ' for ' --code="$log_source" else echo-style --invert='cron' ' is ' --negative='unsupported' ' for ' --code="$log_source" fi - if test "$add_cron_line" = 'yes'; then + if [[ $add_cron_line == 'yes' ]]; then echo-style --invert='cron' ' is ' --positive='desired.' else echo-style --invert='cron' ' is ' --negative='undesired.' fi # apply - if test "$uses_root_for_mount" = 'yes'; then + if [[ $uses_root_for_mount == 'yes' ]]; then config-edit --cron-user --needle="$cron_target" --remove config-edit --cron-system --line="$cron_line" --needle="$cron_target" --add="$add_cron_line" else @@ -1369,11 +1371,11 @@ function mount_helper() ( local filter="$1" shift for arg in "$@"; do - if test -n "$arg"; then + if [[ -n $arg ]]; then filter="$(grep --fixed-strings --regexp="$arg" <<<"$filter" || :)" fi done - if test -n "$filter"; then + if [[ -n $filter ]]; then __print_lines "$filter" fi } @@ -1387,7 +1389,7 @@ function mount_helper() ( fi } function do_unmount_existing { - if test "${#EXISTING_MOUNTS[@]}" -eq 0; then + if [[ ${#EXISTING_MOUNTS[@]} -eq 0 ]]; then return 0 fi local existing_mount @@ -1413,7 +1415,7 @@ function mount_helper() ( EXISTING_MOUNTS=() # check if invalid - if test -z "$check_source" -a -z "$check_target" -a -z "$check_type"; then + if [[ -z $check_source && -z $check_target && -z $check_type ]]; then help '[--check] requires at least , , or ' fi @@ -1421,9 +1423,9 @@ function mount_helper() ( echo-style --h2='check mount entries' local haystack found haystack="$(mount)" - if test -n "$check_type"; then + if [[ -n $check_type ]]; then found="$(scan_haystack_for_string_needle "$haystack" "$check_type")" - if test -n "$found"; then + if [[ -n $found ]]; then SHARED_CHECK_STATUS+='[type-found]' echo-style --positive='matching type:' ' ' --invert="$check_type" $'\n' --code="$found" update_existing_mounts "$found" # used for when source and target are not defined @@ -1434,9 +1436,9 @@ function mount_helper() ( else SHARED_CHECK_STATUS+='[type-unknown]' fi - if test -n "$check_source"; then + if [[ -n $check_source ]]; then found="$(scan_haystack_for_string_needle "$haystack" "$check_source")" - if test -n "$found"; then + if [[ -n $found ]]; then SHARED_CHECK_STATUS+='[source-found]' echo-style --positive="matching source: " --invert="$check_source" $'\n' --code="$found" update_existing_mounts "$found" # override the earlier update from type @@ -1447,9 +1449,9 @@ function mount_helper() ( else SHARED_CHECK_STATUS+='[source-unknown]' fi - if test -n "$check_target"; then + if [[ -n $check_target ]]; then found="$(scan_haystack_for_string_needle "$haystack" "$check_target")" - if test -n "$found"; then + if [[ -n $found ]]; then SHARED_CHECK_STATUS+='[target-found]' echo-style --positive='matching target:' ' ' --invert="$check_target" $'\n' --code="$found" else @@ -1461,7 +1463,7 @@ function mount_helper() ( fi # @todo a nicety would be output extra matches, in case there is a perfect match but there are also other matches... found="$(scan_haystack_for_string_needle "$haystack" "$check_type" "$check_source" "$check_target")" - if test -n "$found"; then + if [[ -n $found ]]; then SHARED_CHECK_STATUS+='[valid]' echo-style --positive='matching expectation:' $'\n' --code="$found" echo-style --g2='check mount entries' @@ -1475,18 +1477,18 @@ function mount_helper() ( # trunk-ignore(shellcheck/SC2120) function do_unmount { local goal='unmount' status - if test "${1-}" = '--goal=mount'; then + if [[ ${1-} == '--goal=mount' ]]; then goal='mount' fi # remove from automount - if test "$goal" = 'unmount'; then + if [[ $goal == 'unmount' ]]; then do_automount 'remove' fi # it exists, check if mounted local local_check_status='' - while true; do + while :; do # update check eval_capture -- do_check local_check_status="$SHARED_CHECK_STATUS" @@ -1505,30 +1507,30 @@ function mount_helper() ( elif [[ $local_check_status == *'[source-found][target-found][valid]'* ]]; then # needs both as unmounting with only --target and no source information is still [valid] echo-style --positive="Correct source is mounted to correct target." # if the goal is mounting, then check if they want to remount - if test "$option_remount" = 'yes'; then + if [[ $option_remount == 'yes' ]]; then # remount echo-style --notice="will remount..." # continue to unmount - elif test "$option_remount" = 'no'; then + elif [[ $option_remount == 'no' ]]; then # user changed their mind and doesn't want to ummount - if test "$goal" = 'mount'; then + if [[ $goal == 'mount' ]]; then echo-style --positive="will skip..." return 200 # ECUSTOM 200 Not applicable as already unmounted as desired else echo-style --warning='will abort...' return 98 # EADDRINUSE 98 Address already in use fi - elif test "$goal" = 'unmount'; then + elif [[ $goal == 'unmount' ]]; then : # goal is to unmount, so continue to unmount else # already mounted, remount? eval_capture --statusvar=status -- \ confirm --linger --negative --ppid=-1 -- 'Do you wish to unmount to enable remounting?' - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then : # user wants to remount, so continue to unmount - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then # user changed their mind and doesn't want to ummount - if test "$goal" = 'mount'; then + if [[ $goal == 'mount' ]]; then echo-style --positive="will skip..." return 200 # ECUSTOM 200 Not applicable as already unmounted as desired else @@ -1540,7 +1542,7 @@ function mount_helper() ( return 98 # EADDRINUSE 98 Address already in use else # failure - echo-error 'An unexpected failure occured.' + echo-error 'An unexpected failure occurred.' return "$status" fi fi @@ -1548,14 +1550,14 @@ function mount_helper() ( echo-style --positive='Source not mounted.' ' ' --negative='Target mounted to an unexpected source.' eval_capture --statusvar=status -- \ confirm --linger --positive --ppid=-1 -- 'Unmount the target to mount to the correct source?' '[y] to unmount, [n] to abort' - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then : # continue to unmount - elif test "$status" -eq 1 || is-abort -- "$status"; then + elif [[ $status -eq 1 ]] || is-abort -- "$status"; then echo-style --warning='will abort...' return 98 # EADDRINUSE 98 Address already in use else # failure - echo-error 'An unexpected failure occured.' + echo-error 'An unexpected failure occurred.' return "$status" fi elif [[ $local_check_status == *'[target-found]'* ]]; then @@ -1568,7 +1570,7 @@ function mount_helper() ( echo-style --positive='Type not mounted.' break # skip to cleanup elif [[ $local_check_status == *'[type-found][source-unknown][target-unknown]'* ]]; then - if test "${#EXISTING_MOUNTS[@]}" -eq 0; then + if [[ ${#EXISTING_MOUNTS[@]} -eq 0 ]]; then echo-style --positive='Type found.' ' ' --error='However, no targets found that can be unmounted.' return 76 # EPROCUNAVAIL 76 Bad procedure for program else @@ -1578,7 +1580,7 @@ function mount_helper() ( continue # now that they should be unmounted, try again fi elif [[ $local_check_status == *'[source-found][target-unknown]'* ]]; then - if test "${#EXISTING_MOUNTS[@]}" -eq 0; then + if [[ ${#EXISTING_MOUNTS[@]} -eq 0 ]]; then echo-style --positive='Source found.' ' ' --error='However, no targets found that can be unmounted.' return 76 # EPROCUNAVAIL 76 Bad procedure for program else @@ -1588,7 +1590,7 @@ function mount_helper() ( continue # now that they should be unmounted, try again fi elif [[ $local_check_status == *'[source-found][target-missing]'* ]]; then - if test "${#EXISTING_MOUNTS[@]}" -eq 0; then + if [[ ${#EXISTING_MOUNTS[@]} -eq 0 ]]; then echo-style --positive='Source found.' ' ' --error='However, no targets found that can be unmounted.' return 76 # EPROCUNAVAIL 76 Bad procedure for program else @@ -1598,10 +1600,10 @@ function mount_helper() ( --code="$option_target" eval_capture --statusvar=status -- \ confirm --linger --positive --ppid=-1 -- 'Unmount the existing targets of the source?' '[y] to unmount existing targets, [n] to leave existing targets intact, [ctrl+c] to abort' - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then do_unmount_existing continue # now that they should be unmounted, try again - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then # we want to be an addition to this alternative target break # skip to cleanup elif is-abort -- "$status"; then @@ -1609,7 +1611,7 @@ function mount_helper() ( return 98 # EADDRINUSE 98 Address already in use else # failure - echo-error 'An unexpected failure occured.' + echo-error 'An unexpected failure occurred.' return "$status" fi fi @@ -1619,7 +1621,7 @@ function mount_helper() ( fi # verify unmount - if test -z "$option_target"; then + if [[ -z $option_target ]]; then echo-error "Unmount was triggered without a target for procedure: $local_check_status" return 76 # EPROCUNAVAIL 76 Bad procedure for program fi @@ -1629,7 +1631,7 @@ function mount_helper() ( echo-style --notice='Unmounting target...' ' ' --invert="$option_target" eval_capture --statusvar=status -- \ eval-helper --no-quiet --wrap -- "${unmount_cmd[@]}" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then echo-style --positive='Unmounted.' break # skip to cleanup elif is-abort -- "$status"; then @@ -1641,14 +1643,14 @@ function mount_helper() ( what-is-using -- "$option_target" eval_capture --statusvar=status -- \ confirm --linger --positive --ppid=-1 -- '[y] to try again, [n] to force unmount, [ctrl+c] to abort' - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then # user doesn't want to force unmount, but just try again, perhaps they made a change somewhere else to make it successful continue # try again - elif test "$status" -eq 1; then + elif [[ $status -eq 1 ]]; then # force unmount eval_capture --statusvar=status -- \ eval-helper --no-quiet --wrap -- "${unmount_force_cmd[@]}" - if test "$tatus" -eq 0; then + if [[ $status -eq 0 ]]; then echo-style --positive='Unmounted forcefully.' break # skip to cleanup elif is-abort -- "$status"; then @@ -1663,26 +1665,26 @@ function mount_helper() ( return 98 # EADDRINUSE 98 Address already in use else # failure - echo-error 'An unexpected failure occured.' + echo-error 'An unexpected failure occurred.' return "$status" fi fi done # cleanup - if test -n "$option_target"; then + if [[ -n $option_target ]]; then # check if it exists as our intended user eval_capture --statusvar=status -- "${exists_cmd[@]}" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then # log echo-style --notice='Cleaning unmounted target...' ' ' --invert="$option_target" # it exists as our intended user, so we can remove it as our intended user - if test "$status" -eq 0; then - "${rm_cmd[@]}" # eval, no need to eval wrap as it is noisey enough + if [[ $status -eq 0 ]]; then + "${rm_cmd[@]}" # eval, no need to eval wrap as it is noisy enough fi # check eval_capture --statusvar=status -- "${exists_cmd[@]}" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then echo-style --warning='Failed to clean...' return 66 # ENOTEMPTY 66 Directory not empty fi @@ -1690,13 +1692,13 @@ function mount_helper() ( else # check if it exists as root eval_capture --statusvar=status -- "${exists_root_cmd[@]}" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then # log echo-style --notice='Cleaning unmounted target as root...' ' ' --invert="$option_target" - "${rm_root_cmd[@]}" # eval, no need to eval wrap as it is noisey enough + "${rm_root_cmd[@]}" # eval, no need to eval wrap as it is noisy enough # check eval_capture --statusvar=status -- "${exists_root_cmd[@]}" - if test "$status" -eq 0; then + if [[ $status -eq 0 ]]; then echo-style --warning='Failed to clean...' return 66 # ENOTEMPTY 66 Directory not empty fi @@ -1713,22 +1715,22 @@ function mount_helper() ( local unmount_status ls_status fix_perms=() # check if invalid - if test -z "$option_share" -o -z "$option_target"; then + if [[ -z $option_share || -z $option_target ]]; then help '[--mount] requires and ' fi # verify mounting - if test "${#mounting_cmd[@]}" -eq 0; then + if [[ ${#mounting_cmd[@]} -eq 0 ]]; then echo-error "Unable to understand how to mount the source:" ' ' --code="$log_source" return 22 # EINVAL 22 Invalid argument fi # unmount with appropriate handling for remounting eval_capture --statusvar=unmount_status -- do_unmount --goal=mount - if test "$unmount_status" -eq 200; then + if [[ $unmount_status -eq 200 ]]; then echo-style --notice='Skipped remount.' return 0 - elif test "$unmount_status" -ne 0; then + elif [[ $unmount_status -ne 0 ]]; then echo-style --warning='Failed to unmount the target.' return "$unmount_status" fi @@ -1737,7 +1739,7 @@ function mount_helper() ( echo-style --h2='mount' # validate gocryptfs here instead of during parsing, as if it is during parsing, it will crash if checking if an unmounted gocryptfs source is unmounted (e.g. /mnt/an-unmounted-hard-drive/a-gocryptfs-source) - if test -n "$gocryptfs_source"; then + if [[ -n $gocryptfs_source ]]; then if ! gocryptfs-helper verify -- "$gocryptfs_source" --user="$option_user" --group="$option_group"; then echo-error 'Failed to verify gocryptfs mount source:' ' ' --code="$gocryptfs_source" return 22 # EINVAL 22 Invalid argument @@ -1745,7 +1747,7 @@ function mount_helper() ( fi # validate samba here instead of during parsing, for the same reason as gocryptfs - if test "$mount_type" = 'cifs' && is-apt; then + if [[ $mount_type == 'cifs' ]] && is-apt; then # [cifs-utils] is necessary for [mount.cifs] which offers advanced samba mounting support, such as [iocharset] option setup-util --quiet --cli='mount.cifs' APT='cifs-utils' # UBUNTU # ensure UTF-8 encoding support @@ -1756,18 +1758,18 @@ function mount_helper() ( fi # verify fstab - if test "$uses_fstab" = 'yes'; then + if [[ $uses_fstab == 'yes' ]]; then do_automount fi # if not mounted, go through the process - if test "$uses_mkdir" = 'yes'; then + if [[ $uses_mkdir == 'yes' ]]; then # make the directory "${mkdir_cmd[@]}" - # @todo instead of the fix_perms workaround always being needed when saying making /Volumes/TARGET for the first time (as /Volumes will be root beccause / is root, despite /Volumes/TARGET being desired), we should make a [fs-mkdir] helper that accepts the desired ownership arguments, uses sudo to create and own each missing parent before moving on to create and own each missing child + # @todo instead of the fix_perms workaround always being needed when saying making /Volumes/TARGET for the first time (as /Volumes will be root because / is root, despite /Volumes/TARGET being desired), we should make a [fs-mkdir] helper that accepts the desired ownership arguments, uses sudo to create and own each missing parent before moving on to create and own each missing child # then chown it - if test "$uses_ownership" = 'yes'; then + if [[ $uses_ownership == 'yes' ]]; then eval-helper --no-quiet --wrap --shapeshifter \ -- fs-own --no-quiet --changes \ --sudo \ @@ -1795,7 +1797,7 @@ function mount_helper() ( # then ls it eval_capture --statusvar=ls_status -- eval-helper --no-quiet --wrap -- "${ls_cmd[@]}" - if test "$ls_status" -ne 0; then + if [[ $ls_status -ne 0 ]]; then echo-style --warning="Failed to access the mount target:" ' ' --code="$option_target" $'\n' \ --notice='This is likely due to incorrect permissions on the target or its parent directories, and probably requires running the following to resolve:' $'\n' \ --code_notice="${fix_perms[*]}" @@ -1808,12 +1810,12 @@ function mount_helper() ( -- "${mounting_cmd[@]}" # wait - if test "$uses_wait" = 'yes'; then + if [[ $uses_wait == 'yes' ]]; then waiter --exists="$option_target" fi # chown - if ! __is_samba "$option_type" && test "$uses_ownership" = 'yes'; then + if ! __is_samba "$option_type" && [[ $uses_ownership == 'yes' ]]; then eval-helper --no-quiet --wrap --shapeshifter \ -- fs-own --no-quiet --changes \ --sudo \ @@ -1828,9 +1830,9 @@ function mount_helper() ( # 1 = minor problem (e.g., cannot access subdirectory) # 2 = serious trouble (e.g., cannot access command-line argument) eval_capture --statusvar=ls_status -- eval-helper --no-quiet --wrap -- "${ls_cmd[@]}" - if test "$ls_status" -eq 1; then + if [[ $ls_status -eq 1 ]]; then echo-style --notice='Listing failed to access a subdirectory, but that is okay.' - elif test "$ls_status" -ne 0; then + elif [[ $ls_status -ne 0 ]]; then echo-style --warning='Failed to list the target.' return "$ls_status" fi @@ -1856,7 +1858,7 @@ function mount_helper() ( # config # done here to prevent double running on --automount --mount dual use - if [[ $option_actions == *'[configure]'* ]] || test "$option_fstab" = 'yes' -o "$option_cron" = 'yes'; then + if [[ $option_actions == *'[configure]'* || $option_fstab == 'yes' || $option_cron == 'yes' ]]; then do_configure fi @@ -1867,7 +1869,7 @@ function mount_helper() ( fi # automount - if test -n "$option_automount"; then + if [[ -n $option_automount ]]; then do_automount fi @@ -1883,7 +1885,7 @@ function mount_helper() ( log_source_target eval_capture --statusvar=action_status -- do_action log_source_target - if test "$action_status" -eq 0; then + if [[ $action_status -eq 0 ]]; then echo-style --g2="$title" else echo-style --e2="$title" @@ -1933,7 +1935,7 @@ function mount_helper() ( [--password=] Applicable to and . [--server=] - Aplicable to . + Applicable to . [--label=