A Makefile => Makefile +85 -0
@@ 0,0 1,85 @@
+
+BIN_DIR?=/usr/local/bin
+LIB_DIR?=/usr/local/lib
+
+install: clean
+ install -m755 src/mylib.bash $(LIB_DIR)/mylib.bash
+ install -m755 src/myparse.bash $(LIB_DIR)/myparse.bash
+ install -m755 src/myminiparse.sh $(LIB_DIR)/myminiparse.sh
+ install -m755 src/unittest-color.awk $(LIB_DIR)/unittest-color.awk
+ install -m755 src/check-x $(BIN_DIR)/check-x
+ install -m755 src/ctdir $(BIN_DIR)/ctdir
+ install -m755 src/debom $(BIN_DIR)/debom
+ install -m755 src/epub $(BIN_DIR)/epub
+ install -m755 src/enumerate $(BIN_DIR)/enumerate
+ install -m755 src/mkbak $(BIN_DIR)/mkbak
+ install -m755 src/mktar $(BIN_DIR)/mktar
+ install -m755 src/rand $(BIN_DIR)/rand
+ install -m755 src/rebom $(BIN_DIR)/rebom
+ install -m755 src/rmtar $(BIN_DIR)/rmtar
+ install -m755 src/rmzip $(BIN_DIR)/rmzip
+ install -m755 src/tarcat $(BIN_DIR)/tarcat
+ install -m755 src/unittest $(BIN_DIR)/unittest
+ install -m755 src/untar $(BIN_DIR)/untar
+ install -m755 src/whichcat $(BIN_DIR)/whichcat
+ install -m755 src/whiched $(BIN_DIR)/whiched
+ install -m755 src/whichhead $(BIN_DIR)/whichhead
+ install -m755 src/whichvi $(BIN_DIR)/whichvi
+ install -m755 src/whisper $(BIN_DIR)/whisper
+ install -m755 src/wttr $(BIN_DIR)/wttr
+
+uninstall:
+ rm $(LIB_DIR)/mylib.bash
+ rm $(LIB_DIR)/myparse.bash
+ rm $(LIB_DIR)/myminiparse.sh
+ rm $(LIB_DIR)/unittest-color.awk
+ rm $(BIN_DIR)/check-x
+ rm $(BIN_DIR)/ctdir
+ rm $(BIN_DIR)/debom
+ rm $(BIN_DIR)/epub
+ rm $(BIN_DIR)/enumerate
+ rm $(BIN_DIR)/mkbak
+ rm $(BIN_DIR)/mktar
+ rm $(BIN_DIR)/rand
+ rm $(BIN_DIR)/rebom
+ rm $(BIN_DIR)/rmtar
+ rm $(BIN_DIR)/rmzip
+ rm $(BIN_DIR)/tarcat
+ rm $(BIN_DIR)/unittest
+ rm $(BIN_DIR)/untar
+ rm $(BIN_DIR)/whichcat
+ rm $(BIN_DIR)/whiched
+ rm $(BIN_DIR)/whichhead
+ rm $(BIN_DIR)/whichvi
+ rm $(BIN_DIR)/whisper
+ rm $(BIN_DIR)/wttr
+
+test: clean static-test-files
+ shellcheck src/*.bash src/*.sh $(LIB_DIR)/mylib.bash $(LIB_DIR)/myparse.bash $(LIB_DIR)/myminiparse.sh
+ sh tests/compression_test.sh
+ sh tests/ctdir_test.sh
+ sh tests/which_test.sh
+ sh tests/bom_test.sh
+
+static-test-files:
+ printf "[1]\n[2]\n[3]\n\n[1]\n[2]\n[3]\n\n[1]\n[2]\n[3]\n\n[1]\n[2]\n[3]\n\n[1]\n[2]\n[3]\n\n" > tests/static/compression_result.txt
+ printf "[1]\n[2]\n[3]\n\n" > tests/static/compression_target.txt
+ printf "foo bar\n" > tests/static/debom_result.txt
+ printf "\xEF\xBB\xBFfoo bar\n" > tests/static/debom_target.txt
+ printf "(a)\n(b)\n(c)\n\n" > tests/static/decompression_result.txt
+ cp tests/static/decompression_result.txt tests/static/tar.txt
+ cp tests/static/decompression_result.txt tests/static/gzip.txt
+ cp tests/static/decompression_result.txt tests/static/xz.txt
+ cp tests/static/decompression_result.txt tests/static/zstd.txt
+ cp tests/static/decompression_result.txt tests/static/bzip2.txt
+ cd tests/static/; tar -cf decompression_target.tar tar.txt
+ cd tests/static/; tar -czf decompression_target.tar.gz gzip.txt
+ cd tests/static/; tar -cJf decompression_target.tar.xz xz.txt
+ cd tests/static/; tar --zstd -cf decompression_target.tar.zst zstd.txt
+ cd tests/static/; tar -cjf decompression_target.tar.bz2 bzip2.txt
+ rm tests/static/tar.txt tests/static/gzip.txt tests/static/xz.txt tests/static/zstd.txt tests/static/bzip2.txt
+
+clean:
+ rm -rf tests/temp_compression
+ rm -rf tests/temp_bom
+
M README.md => README.md +31 -169
@@ 1,95 1,46 @@
-Set of scripts that I use frequently.
+# my-utils
+A set of scripts that I use frequently. Written in a mix of shell (some POSIX sh, some bash), sed, awk, and so on. Any dependencies *beyond* the POSIX standard and inter-dependency are noted.
-Everything is as POSIX as I can make it. Unfortunately, some utilities are
-intrinsically based in arrays or regex matching, which are *not* POSIX.
-With scripts that are intended for direct use (i.e., `ctdir`), just try it and
-see what happens. With scripts intended to be used in other scripts
-(i.e., `is-int.sh`), the filename extensions accurately represent dependencies.
+## Scripts
+Executable |Description |Extra Dependencies
+:----------|:-------------------------------------------------------------|:------------------------------------------
+check-x |Check if an X11 server is running
+ctdir |Count entries in a target directory(ies)
+debom |Remove BOM from a target file |`bash`
+enumerate |Dumps HTML from an 'epub' e-book archive |`bash`, `zipinfo`, `unzip`, `w3m`
+epub |Rename files in current directory into sequential numbers |`bash`
+mkbak |Create a backup of a target file |`bash`
+mktar |Wrapper around `tar` for easier compression |`bash` *
+rand |Get a random number within an inclusive range |`shuf`
+rebom |Add BOM to a target file |
+rmtar |Delete 'tar' archive files
+rmzip |Delete 'zip' archive files
+stop-at |Re-print until a pattern is matched
+tarcat |Print contents of target archive file(s) |*
+unittest |Wrapper around Python's `unittest` module |`python3`, GNU or New (AT&T) `awk`
+untar |Wrapper around `tar` for easier decompression |*
+whichcat |Print all lines from a program
+whiched |Open a program with your editor
+whichhead |Print the first 10 lines from a program |`bash`
+whichvi |Open a program with your visual editor
+whisper |Wrapper around `espeak` to mirror `say` in macOS |`espeak`
+wttr |Wrapper around `wttr` to fix double-wide runes for some fonts |`wego`
-## check-x.sh
+\* *While you **technically** won't run into an error, these scripts **do** expect `tar` to support Zstandard, which isn't necessarily POSIX standard.*
-Check if an X server is running
+*All* scripts support `-h` and `--help` for printing built-in documentation.
-Usage: `check-x.sh`
+*Almost all* scripts do nothing if no input arguments are given. (The exceptions are `whisper` and `wttr`.)
-## ctdir
+## Development
-Count entries in a target directory.
-
-Usage: `ctdir TARGET [OPTIONS]`
-
-###### Options (inherited from `ls`):
-+ `-a`, `--all`: do not ignore entries starting with `.`
-+ `-A`, `--almost-all`: do not list implied `.` and `..`
-+ `-B`, `--ignore-backups`: do not list implied entries ending with `~`
-+ `-F`, `--classify`: append indicator (one of `*/=>@|`)
-+ `--file-type`: likewise, except do not append `*`
-+ `-R`, `--recursive`: list subdirectories recursively
-+ `-h`, `--help`: print help message
-
-
-
-## debom
-
-Remove the byte order mark (BOM) from a target file, saving to a new file.
-
-Usage: `debom TARGET [OUTPUT]`
-
-###### Options:
-+ `-h`, `--help`: print help message
-
-
-
-## filecheck.sh
-
-Check if a target file exists, and prompt user to remove it.
-
-Usage: filecheck.sh TARGET
-
-
-
-## is-int.sh
-
-Check if an item is an integer.
-
-Usage: `is-int.sh ITEM`
-
-###### Example:
-```bash
-$ is-int.sh 1 && echo "Y" || echo "N"
-Y
-$ is-int.sh a && echo "Y" || echo "N"
-N
-$ is-int.sh "" && echo "Y" || echo "N" #handles empty strings
-N
-```
-
-
-
-## match.bash
-
-Check if any items match a pattern.
-
-Usage: `match.bash PATTERN ITEM1 [ITEM2 ...]`
-
-###### Example:
-```bash
-$ match a abc && echo "Y" || echo "N"
-N
-$ match a abc abc a && echo "Y" || echo "N"
-Y
-$ match a* abc && echo "Y" || echo "N" #handles globbing
-Y
-```
-
-###### Options:
-+ `-h`, `--help`: print help message
+These being re-used scripts, there's been a great deal of feature creep, bikeshedding, and over-engineering. That is to say, there's a lot of uncertainty as to how *reliable* these scripts are. To mitigate these concerns, `shellcheck` is a development dependency.
@@ 105,51 56,6 @@ Usage: `rand START END [OPTIONS]`
-## safe-rm.bash
-
-Check if a target matches a pattern before removing it.
-
-Usage: `safe-rm.bash TARGET PATTERN`
-
-
-
-## tlog
-
-Writes input to a target file while stripping ANSI codes, and returns input as
-it was.
-
-Usage: tlog TARGET [OPTIONS]
-
-###### Options:
-+ `-a`, `--append`: append to target file instead of overwriting
-+ `-h`, `--help`: print help message
-
-
-
-## unittest.sh, clean-unittest.awk, and color-unittest.sh
-
-Run Python tests in a target directory while cleaning and colorizing the output.
-
-Usage: `unittest.sh [TARGET] [OPTIONS] | clean-unittest.awk | color-unittest.sh`
-
-###### Options:
-+ `-v`, `--verbose`: verbose output
-+ `-p`, `--pattern`: pattern to match test files (default: `test*.py`)
-+ `-h`, `--help`: print help message
-
-
-
-## untar
-
-Uncompress a tarball. Expects standardized file names (i.e., `.tar.gz.gpg`).
-
-Usage: `untar FILE [OPTIONS]`
-
-###### Options:
-+ `-h`, `--help`: print help text
-
-
-
## weather
Wrapper around `wego`--fixes spacing. Shows weather for 2 days unless a number
@@ 159,47 65,3 @@ Usage: `weather [NUMBER]`
-## whichcat
-
-Print lines from a program to the terminal.
-
-Usage: `whichcat PROGRAM [OPTIONS]`
-
-###### Options (inherited from `cat`)
-+ `-n`, `--number`: number all output lines
-+ `-s`, `--squeeze-blank`: suppress repeated empty output lines
-+ `-h`, `--help`: print help message
-
-
-
-## whiched and whichvi
-
-Open a program in your editor or visual editor.
-
-Usage: `whiched PROGRAM [OPTIONS]`
-
-###### Options:
-+ `-h`, `--help`: print help message
-
-
-
-## whichhead
-
-Print the first 10 lines from a program to the terminal.
-
-Usage: `whichhead PROGRAM [OPTIONS]`
-
-###### Options (inherited from `head`)
-+ `-n N`, `--lines=N`: print the first N lines instead of the first 10
-+ `-h`, `--help`: print help message
-
-
-
-## whisper
-
-Wrapper on `espeak` to mirror macOS's `say` using whisper voice.
-
-Usage: `whisper [TEXT]`
-
-
-
A bash-completion/mybashcompletion.bash => bash-completion/mybashcompletion.bash +45 -0
@@ 0,0 1,45 @@
+#!/bin/bash
+
+# epub
+# Function tries to find a target archive in arguments already specified, and
+# then tries to list the filenames stored inside the archive
+_epub_completion() {
+ # set fallback completion as default (filenames) with 'complete -o default'
+ COMPREPLY=()
+ # search for a filename in arguments, skipping the first (executable name)
+ for arg in "${COMP_WORDS[@]:1}"; do
+ if [[ -f "$arg" ]]; then
+ # on finding a filename, try to complete with archive entries
+ local list=( $(epub --list "$arg" | sort) )
+ if [[ "${#list[@]}" -ge 1 ]]; then
+ COMPREPLY=( $(compgen -W "${list[*]}" -- "${COMP_WORDS[$COMP_CWORD]}") )
+ else
+ # if not an archive, or an archive with no entries, halt completion
+ compopt +o default
+ fi
+ break
+ fi
+ done
+}
+# Complete with function, and fallback to filenames
+complete -o default -F _epub_completion epub
+
+# rmzip
+# Complete with filenames matching pattern '*.zip', and fallback to filenames
+complete -o default -f -X '!*.zip' rmzip
+
+# untar, tarcat, rmtar
+# Complete with filenames matching pattern
+# '*.@(tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).gpg)', and fallback to
+# filenames
+complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).gpg)' tarcat
+complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).gpg)' untar
+complete -o default -f -X '!*.@(tar|tar.@(gz|xz|zst|bz2)|tar.@(gz|xz|zst|bz2).gpg)' rmtar
+
+# whichcat, whiched. whichhead, whichvi
+# Complete with program names
+complete -c whichcat
+complete -c whichhead
+complete -c whiched
+complete -c whichvi
+
D check-x.sh => check-x.sh +0 -14
@@ 1,14 0,0 @@
-#!/bin/sh
-
-# check-x.sh
-# ==========
-# Usage: check-x.sh
-#
-# Check if an X server is running
-
-if ! xset q &>/dev/null; then
- exit 1
-else
- exit 0
-fi
-
D clean-unittest.awk => clean-unittest.awk +0 -45
@@ 1,45 0,0 @@
-#!/usr/bin/awk -f
-
-# clean-unittest.awk
-# ==================
-# Usage: python -m unittest | clean-unittest.awk
-#
-# Clean output of Python's unittest
-
-BEGIN { last_testcase="PROBABLYWILLNOTMATCH"; blank_lines=0; first_line=1 }
-{
- if ($0 ~ /^ *$/) {
- if (blank_lines == 0 && first_line == 0) {
- print $0
- }
- blank_lines+=1
- }
- else if ($0 ~ /^test/) {
- blank_lines=0
- if ($0 !~ last_testcase) {
- split($0, last_array, "[()]")
- last_testcase=last_array[2]
- print last_testcase
- }
- sub(/ \(.*\) /, " ")
- $0=" " $0
- print $0
- }
- else if ($0 ~ /Ran [0-9]+ tests in [0-9]+.[0-9]+s/) {
- num_tests=$0
- }
- else if ($0 ~ /^FAIL: / || $0 ~ /^ERROR: /) {
- blank_lines=0
- print "\n" $0
- }
- else if ($0 ~ /^OK$/ || $0 ~ /^FAILED /) {
- blank_lines=0
- print num_tests " ... " $0
- }
- else if ($0 !~ /^ *[-=]+ *$/) {
- print $0
- }
- first_line=0
-}
-
-
D color-unittest.sh => color-unittest.sh +0 -47
@@ 1,47 0,0 @@
-#!/bin/sh
-
-# color-unittest.sh
-# =================
-# Usage: python -m unittest | color-unittest.sh
-#
-# Colorize output of Python's unittest
-
-grey_grep() {
- GREP_COLOR='1;30' grep --color=always -e "$1"
-}
-
-red_grep() {
- GREP_COLOR='1;31' grep --color=always -e "$1"
-}
-
-green_grep() {
- GREP_COLOR='1;32' grep --color=always -e "$1"
-}
-
-yellow_grep() {
- GREP_COLOR='1;33' grep --color=always -e "$1"
-}
-
-blue_grep() {
- GREP_COLOR='1;34' grep --color=always -e "$1"
-}
-
-magenta_grep() {
- GREP_COLOR='1;35' grep --color=always -e "$1"
-}
-
-cyan_grep() {
- GREP_COLOR='1;36' grep --color=always -e "$1"
-}
-
-white_grep() {
- GREP_COLOR='1;37' grep --color=always -e "$1"
-}
-
-cat <&0 \
- | green_grep 'ok$\|OK\|' \
- | red_grep 'ERROR:\|ERROR\|FAIL:\|FAIL\|FAILED\|' \
- | red_grep 'failures=[0-9]\+\|' \
- | red_grep 'errors=[0-9]\+\|' \
- | yellow_grep 'skipped=[0-9]\+\|skipped .*\|skipped\|'
-
D ctdir => ctdir +0 -39
@@ 1,39 0,0 @@
-#!/bin/sh
-
-# ctdir
-# =====
-# Usage: ctdir TARGET [OPTIONS]
-#
-# Count entries in a directory
-
-help_msg() {
- cat <<-EOF
- Count entries in a target directory
- Usage: ctdir TARGET [OPTIONS]
- Options:
- -a, --all do not ignore entries starting with .
- -A, --almost-all do not list implied . and ..
- -B, --ignore-backups do not list implied entries ending with ~
- -r, --recursive list subdirectories recursively
- -h, --help print this message
- EOF
- exit 1
-}
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-LISTING=$(ls -1 "$@" 2>/dev/null | wc -l)
-if [ "$LISTING" -eq 0 ]; then
- err_msg "No files as '${*}'"
-else
- echo "$LISTING"
-fi
-
D debom => debom +0 -39
@@ 1,39 0,0 @@
-#!/bin/bash
-
-# debom
-# =====
-# Usage: debom TARGET [OUTPUT]
-#
-# Remove byte order mark (BOM) from a target file, saving to a new file
-
-help_msg() {
- cat <<-EOF
- Remove BOM from a target file, saving to a new file
- Usage: debom TARGET [OUTPUT]
- Options:
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ $# -lt 1 ]; then
- err_msg "Usage: debom TARGET [OUTPUT]"
-elif [[ $# -lt 2 ]]; then
- OUTFILE="${1}.new"
-else
- OUTFILE="$2"
-fi
-
-sed '1s/^\xEF\xBB\xBF//' < "$1" > "$OUTFILE"
-
D filecheck.sh => filecheck.sh +0 -19
@@ 1,19 0,0 @@
-#!/bin/sh
-
-# filecheck.sh
-# ============
-# Usage: filecheck.sh TARGET
-#
-# Check if a target file exists, and prompt user to remove it
-
-exec < /dev/tty #clears stdin
-
-if [ -e "$1" ]; then
- printf "File ${1} already exists. Overwrite? (Y/n) "
- read RESP
- case "$RESP" in
- [Yy]* ) echo "Overwriting..."; rm "$1";;
- * ) echo "Quitting..."; exit 1;;
- esac
-fi
-
D is-int.sh => is-int.sh +0 -19
@@ 1,19 0,0 @@
-#!/bin/sh
-
-# is-int.sh
-# =========
-# Usage: is-int.sh ITEM
-#
-# Check if an item is an integer.
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-if [ $# -lt 1 ]; then
- err_msg "Usage: is-int.sh ITEM"
-fi
-
-[ "$1" -eq "$1" ] 2> /dev/null && exit 0 || exit 1
-
D match.bash => match.bash +0 -41
@@ 1,41 0,0 @@
-#!/bin/bash
-
-# match.bash
-# ==========
-# Usage: match.bash PATTERN ITEM1 [ITEM2 ...]
-#
-# Check if any items match a pattern
-
-help_msg() {
- cat <<-EOF
- Check if any items match a pattern
- Usage: match PATTERN ITEM1 [ITEM2 ...]
- Options:
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [[ $# -lt 2 ]]; then
- err_msg "Usage: match.bash PATTERN ITEM1 [ITEM2 ...]"
-fi
-
-for item in "${@:2}"; do
- if [[ $item == $1 ]]; then
- exit 0
- fi
-done
-
-exit 1
-
D rand => rand +0 -54
@@ 1,54 0,0 @@
-#!/bin/bash
-
-# rand
-# ====
-# Usage: rand START END [OPTIONS]
-#
-# Returns a random number within a range (inclusive)
-
-help_msg() {
- cat <<-EOF
- Returns a random number within a range (inclusive)
- Usage: rand START END [OPTIONS]
- Options:
- -w, --width N zero-pad number to be N wide
- -h, --help print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-START=
-END=
-WIDTH=1
-POSITIONAL=()
-
-while [[ $# -gt 0 ]]; do
- case $1 in
- -h|--help) help_msg;;
- -w|--width) WIDTH="$2"; shift; shift;;
- *) POSITIONAL+=("$1"); shift;;
- esac
-done
-
-if [[ ${#POSITIONAL[@]} -lt 2 ]]; then
- err_msg "Usage: rand START END"
- exit 1
-else
- START="${POSITIONAL[0]}"
- END="${POSITIONAL[1]}"
- if ! is-int.sh "$START"; then
- err_msg "Expected numeric argument (given '${START}')"
- elif ! is-int.sh "$END"; then
- err_msg "Expected numeric argument (given '${END}')"
- elif [[ $START -ge $END ]]; then
- err_msg "Expected ascending range ('${END}' not greater than '${START}')"
- fi
-fi
-
-seq -f "%0${WIDTH}g" "$START" "$END" | shuf -n 1
-
D safe-rm.bash => safe-rm.bash +0 -22
@@ 1,22 0,0 @@
-#!/bin/bash
-
-# save_rm.bash
-# ============
-# Usage: safe_rm.bash TARGET PATTERN
-#
-# Checks if a target matches a pattern before removing it
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-if [[ $# -lt 2 ]]; then
- err_msg "Usage: safe_rm.bash TARGET PATTERN"
-fi
-
-PATTERN="^${2}$"
-if [[ $1 =~ $PATTERN ]]; then
- rm $1
-fi
-
A src/check-x => src/check-x +19 -0
@@ 0,0 1,19 @@
+#!/bin/sh
+# shellcheck disable=SC2034
+
+name="check-x"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Check if an X11 server is running
+ Usage: check-x
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+if ! /usr/bin/xset q >/dev/null 2>&1; then
+ exit 1
+else
+ exit 0
+fi
+
A src/ctdir => src/ctdir +49 -0
@@ 0,0 1,49 @@
+#!/bin/sh
+
+name="ctdir"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Count entries in target directory(ies)
+ Usage: ctdir TARGETS [..] [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# error if no directory names given
+if [ "$#" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: ctdir TARGET [OPTIONS]\n")
+ exit 1
+elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
+ exit 1
+fi
+
+# loop through arguments
+code=0
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ # main routine
+ if [ ! -d "$arg" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "%s: No such directory '%s'\n" "$name" "$arg")
+ fi
+ code=1
+ else
+ /usr/bin/ls -1A "$arg" 2>/dev/null | wc -l
+ fi
+ ;;
+ esac
+done
+
+# exit with stored code
+exit "$code"
+
A src/debom => src/debom +150 -0
@@ 0,0 1,150 @@
+#!/bin/bash
+
+name="debom"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Remove BOM from a target file
+ Usage: debom TARGET [OPTIONS]
+ Options:
+ -b, --backup output a backup file, editing TARGET in-place
+ -d, --dump print to STDOUT
+ -f, --force overwrite without asking
+ -h, --help print this message
+ -n FILE, --name FILE set output filename (Default: TARGET.new, or
+ TARGET.bak in backup mode)
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+quiet=0
+verbose=0
+backup=0
+dump=0
+force=0
+output_fn=""
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+
+ -b|--backup)
+ debug_msg "Setting backup option to 1 (was ${backup})"
+ backup=1
+ shift
+ ;;
+
+ -d|--dump)
+ debug_msg "Setting dump option to 1 (was ${dump})"
+ dump=1
+ shift
+ ;;
+
+ -f|--force)
+ debug_msg "Setting force option to 1 (was ${force})"
+ force=1
+ shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -n|--name)
+ debug_msg "Setting output filename to ${2} (was ${output_fn})"
+ output_fn="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# error if no filenames given
+if [[ ${#positional[@]} -eq 0 ]]; then
+ debug_msg "No input filename was given"
+ usage_msg
+fi
+original_fn="${positional[0]}"
+
+# output filename
+if [[ "$dump" -eq 1 ]]; then
+ debug_msg "Output is STDOUT"
+elif [[ -z "$output_fn" ]]; then
+ if [[ "$backup" -eq 1 ]]; then
+ debug_msg "No output filename was given, so defaulting to 'TARGET.bak'"
+ output_fn="${original_fn}.bak"
+ else
+ debug_msg "No output filename was given, so defaulting to 'TARGET.new'"
+ output_fn="${original_fn}.new"
+ fi
+else
+ debug_msg "Output filename given as '${output_fn}'"
+fi
+
+# check if files exist
+debug_msg "Beginning file existence checks"
+if [[ ! -f "$original_fn" ]]; then
+ debug_msg "Input file does not exist"
+ error_msg "No such file '${original_fn}'"
+elif [[ "$dump" -eq 0 && "$force" -eq 0 && -f "$output_fn" ]]; then
+ debug_msd "Output file exists; prompting for permission to overwrite"
+ if ! prompt_overwrite "${output_fn}"; then
+ debug_msg "Could not obtain permission to overwrite output file"
+ exit 1
+ fi
+fi
+debug_msg "Finished file existence checks"
+
+# backup routine
+if [[ "$dump" -eq 0 && "$backup" -eq 1 ]]; then
+ debug_msg "Beginning backup routine"
+ backup_fn="${output_fn}"
+ if ! /usr/local/bin/mkbak "${original_fn}" --force --name "${backup_fn}"; then
+ error_msg "Error occured while executing backup"
+ exit 1
+ fi
+ output_fn=${original_fn}
+ original_fn=${backup_fn}
+ debug_msg "Finished backup routine"
+fi
+
+# main routine
+debug_msg "Beginning main routine"
+code=0
+if [[ "$dump" -eq 1 ]]; then
+ if ! /usr/bin/sed '1s/^\xEF\xBB\xBF//' < "${original_fn}"; then
+ code=1
+ fi
+else
+ if ! /usr/bin/sed '1s/^\xEF\xBB\xBF//' < "${original_fn}" > "${output_fn}"; then
+ code=1
+ fi
+fi
+debug_msg "Finished main routine"
+
+# return stored code
+exit "$code"
+
A src/enumerate => src/enumerate +124 -0
@@ 0,0 1,124 @@
+#!/bin/bash
+
+name="enumerate"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Rename files in current directory into sequential numbers
+ Usage: enumerate [OPTIONS]
+ Options:
+ -d, --dry-run print current and intended filenames side-by-side
+ -f P, --filter P filter targets with filename pattern P
+ -h, --help print this message
+ -q, --quiet suppress error messages
+ -s N, --start N start enumeration at N (Default: 1)
+ -S N, --step N step enumeration by N (Default: 1)
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+ -w N, --width N rename files to numbers N wide (Default: 4)
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+execute=0
+filter="*"
+enum_start=1
+enum_step=1
+width=4
+quiet=0
+verbose=0
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+
+ -f|--filter)
+ debug_msg "Setting filter option to ${2} (was ${filter})"
+ filter="$2"
+ shift; shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -s|--start)
+ if ! is_natural "$2"; then
+ error_msg "Cannot set enumeration start to '${2}' (not an integer >= 0)"
+ fi
+ debug_msg "Setting enumeration start to ${2} (was ${enum_start})"
+ enum_start="$2"
+ shift; shift
+ ;;
+
+ -S|--step)
+ if ! is_positive_integer "$2"; then
+ error_msg "Cannot set enumeration step to '${2}' (not an integer >= 1)"
+ fi
+ debug_msg "Setting enumeration step to ${2} (was ${enum_step})"
+ enum_step="$2"
+ shift; shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ -w|--width)
+ if ! is_natural "$2"; then
+ error_msg "Cannot set filename width to '${2}' (not an integer >= 0)"
+ fi
+ debug_msg "Setting filename width to ${2} (was ${width})"
+ width="$2";
+ shift; shift
+ ;;
+
+ -x|--execute)
+ debug_msg "Setting execution to 1 (was ${execute})"
+ execute=1
+ shift
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# main routine
+n="$enum_start"
+s="$enum_step"
+code=0
+for original_fn in $(find . -maxdepth 1 -name "$filter" -type f -printf "%f\n" | sort); do
+ wide_n=$(printf "%0*d\n" "$width" "$n")
+ enum_fn="${wide_n}.$(fn_extension "$original_fn")"
+ debug_msg "Widened format of '${n}' is '${wide_n}'"
+ debug_msg "Input filename is '${original_fn}'"
+ debug_msg "Output filename is '${enum_fn}'"
+ if [[ "$execute" -eq 1 ]]; then
+ if ! mv "$original_fn" "${enum_fn}"; then
+ code=1
+ fi
+ else
+ printf "#mv %s %s\n" "$original_fn" "$enum_fn"
+ fi
+ n=$((n+s))
+done
+
+# return stored code
+exit "$code"
+
A src/epub => src/epub +155 -0
@@ 0,0 1,155 @@
+#!/bin/bash
+
+name="epub"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Dumps HTML from an epub e-book archive
+ Usage: epub TARGET [FILENAMES] [OPTIONS]
+ Options:
+ -d, --dump print to STDOUT
+ -h, --help print this message
+ -l, --list list HTML entries of archive
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+target_archive=""
+positional=()
+quiet=0
+verbose=0
+dump=0
+list=0
+width="$(/usr/bin/tput cols)"
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+
+ -d|--dump)
+ debug_msg "Setting dump option to 1 (was ${dump})"
+ dump=1
+ shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -l|--list)
+ debug_msg "Setting list option to 1 (was ${list})"
+ list=1
+ shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ -w|--width)
+ if ! is_positive_integer "$2"; then
+ error_msg "Cannot set column width to '${2}' (not an integer >= 1)"
+ fi
+ debug_msg "Setting column width to ${2} (was ${width})"
+ width="$2";
+ shift; shift
+ ;;
+
+ *)
+ if [ -z "$target_archive" ]; then
+ debug_msg "Setting target filename to '${1}'"
+ target_archive="$1"
+ else
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ fi
+ shift
+ ;;
+ esac
+done
+
+# error if no filenames given
+if [[ -z "$target_archive" ]]; then
+ debug_msg "No input filename was given"
+ usage_msg
+elif [[ ! -f "$target_archive" ]]; then
+ error_msg "No such file '${target_archive}'"
+fi
+
+# listing subroutine
+list_target_archive() {
+ if ! /usr/bin/zipinfo -1 "$target_archive" 2>/dev/null; then
+ debug_msg "Error in listing subroutine"
+ code=1
+ fi
+}
+
+# extract subroutine
+extract_from_target_archive() {
+ if ! /usr/bin/unzip -caaqq "$target_archive" "$target_fn"; then
+ code=1
+ fi
+}
+
+# render subroutine
+render_html() {
+ if ! /usr/bin/w3m -T text/html -cols "$width" -dump; then
+ code=1
+ fi
+}
+
+# dump subroutine
+dump_target_archive() {
+ if [[ "${#positional[@]}" -eq 0 ]]; then
+ for fn in "${list_fn[@]}"; do
+ target_fn="$fn"
+ extract_from_target_archive | render_html
+ done
+ else
+ for fn in "${positional[@]}"; do
+ target_fn="$fn"
+ extract_from_target_archive | render_html
+ done
+ fi
+}
+
+# main routine
+code=0
+if [[ "$list" -eq 1 ]]; then
+ list_target_archive | sort
+else
+ list_fn=( $(list_target_archive | grep -E '\.xml|\.html|\.xhtml' | sort) )
+
+ if [[ "$code" -eq 1 || "${#list_fn[@]}" -eq 0 ]]; then
+ error_msg "'${target_archive}' is not a valid archive"
+ else
+ for target_fn in "${positional[@]}"; do
+ if ! contains "$target_fn" "${list_fn[@]}"; then
+ error_msg "'${target_fn}' not in archive '${target_archive}'"
+ fi
+ done
+ fi
+
+ if [[ "$dump" -eq 1 ]]; then
+ dump_target_archive
+ else
+ dump_target_archive | ${PAGER:=/usr/bin/less}
+ fi
+fi
+
+# return stored code
+exit "$code"
+
A src/mkbak => src/mkbak +107 -0
@@ 0,0 1,107 @@
+#!/bin/bash
+
+name="mkbak"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Create a backup of a target file
+ Usage: mkbak TARGET [OPTIONS]
+ Options:
+ -d, --diff diff files before asking to overwrite
+ -f, --force overwrite without asking
+ -h, --help print this message and exit
+ -n FILE, --name FILE name of backup file (Default: TARGET.bak)
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+quiet=0
+verbose=0
+force=0
+backup_fn=""
+while [[ $# -gt 0 ]]; do
+ case $1 in
+
+ -d|--diff)
+ debug_msg "Setting diff option to 1 (was ${diff})"
+ diff=1
+ shift
+ ;;
+
+ -f|--force)
+ debug_msg "Setting force option to 1 (was ${force})"
+ force=1
+ shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -n|--name)
+ debug_msg "Setting output filename to ${2} (was ${backup_fn})"
+ backup_fn="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# error if no filenames given
+if [[ "${#positional[@]}" -lt 1 ]]; then
+ debug_msg "No input filename was given"
+ usage_msg
+fi
+original_fn="${positional[0]}"
+
+# output filename
+if [[ -z "$backup_fn" ]]; then
+ debug_msg "No output filename was given, so defaulting to 'TARGET.bak'"
+ backup_fn="${original_fn}.bak"
+fi
+
+# check files
+if [[ ! -f "$original_fn" ]]; then
+ error_msg "No such file '${original_fn}'"
+elif [[ "$force" -eq 0 && -f "$backup_fn" ]]; then
+ if /usr/bin/cmp "$backup_fn" "$original_fn" >/dev/null 2>&1; then
+ msg "File '${backup_fn}' already exists and is a copy of '${original_fn}'"
+ elif [[ $quiet -eq 0 ]]; then
+ if [[ "$diff" -eq 1 ]]; then
+ /usr/bin/diff --color "$backup_fn" "$original_fn"
+ fi
+ if ! prompt_overwrite "$backup_fn"; then
+ exit 1
+ fi
+ fi
+fi
+
+# main routine
+/usr/bin/cp "$original_fn" "$backup_fn"
+exit "$?"
+
A src/mktar => src/mktar +269 -0
@@ 0,0 1,269 @@
+#!/bin/bash
+
+name="mktar"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Wrapper around 'tar' for easier compression
+ Usage: mktar FILES [..] [OPTIONS]
+ Options:
+ -c, --compress compress archive with gzip
+ --compress=ALGO compress archive with [none|gzip|xz|zstd|bzip2]
+ -C, --checksum create checksum with SHA1
+ -e, --encrypt encrypt files with GPG
+ -h, --help print this message
+ -n FILE, --name FILE name of backup file (Default: archive.tar)
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+quiet=0
+verbose=0
+compress=-1
+encrypt=-1
+checksum=-1
+archive_fn=""
+while [[ $# -gt 0 ]]; do
+ case $1 in
+
+ --compress=xz|--compress=2)
+ debug_msg "Setting compress option to 2 (=xz) (was ${compress})"
+ compress=2
+ shift
+ ;;
+
+ --compress=zst|--compress=zstd|--compress=3)
+ debug_msg "Setting compress option to 3 (=zstd) (was ${compress})"
+ compress=3
+ shift
+ ;;
+
+ --compress=bz2|--compress=bzip2|--compress=4)
+ debug_msg "Setting compress option to 4 (=bzip2) (was ${compress})"
+ compress=4
+ shift
+ ;;
+
+ --compress=gz|--compress=gzip|--compress=1)
+ debug_msg "Setting compress option to 1 (=gzip) (was ${compress})"
+ compress=1
+ shift
+ ;;
+
+ --compress=no|--compress=none|--compress=0)
+ debug_msg "Setting compress option to 0 (=none) (was ${compress})"
+ compress=0
+ shift
+ ;;
+
+ --compress=*)
+ attempted_compression="$(printf "%s\n" "$1" | sed -e 's/^.*=//' )"
+ error_msg "Unknown compression '${attempted_compression}'"
+ ;;
+
+ -c|--compress)
+ debug_msg "Setting compress option to 1 (=gzip) (was ${compress})"
+ compress=1
+ shift
+ ;;
+
+ -C|--checksum)
+ debug_msg "Setting checksum option to 1 (=SHA1) (was ${checksum})"
+ checksum=1
+ shift
+ ;;
+
+ -e|--encrypt)
+ debug_msg "Setting encrypt option to 1 (=GPG) (was ${encrypt})"
+ encrypt=1
+ shift
+ ;;
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -n|--name)
+ debug_msg "Setting output filename to ${2} (was ${archive_fn})"
+ archive_fn="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# error if no filenames given
+if [[ "${#positional[@]}" -lt 1 ]]; then
+ debug_msg "No input filenames were given"
+ usage_msg
+fi
+
+# determine tar action
+if [[ "$compress" -eq 1 ]]; then
+ archive_action="tar.gz"
+elif [[ "$compress" -eq 2 ]]; then
+ archive_action="tar.xz"
+elif [[ "$compress" -eq 3 ]]; then
+ archive_action="tar.zst"
+elif [[ "$compress" -eq 4 ]]; then
+ archive_action="tar.bz2"
+elif [[ -n "$archive_fn" && "$compress" -eq -1 ]]; then
+ case "$archive_fn" in
+ *.tar)
+ archive_action="tar"
+ ;;
+
+ *.tar.gz)
+ archive_action="tar.gz"
+ ;;
+
+ *.tar.xz)
+ archive_action="tar.xz"
+ ;;
+
+ *.tar.zst)
+ archive_action="tar.zst"
+ ;;
+
+ *.tar.bz2)
+ archive_action="tar.bz2"
+ ;;
+ esac
+else
+ archive_action="tar"
+fi
+if [[ "$encrypt" -eq 1 ]]; then
+ archive_action="${archive_action}.gpg"
+elif [[ -n "$archive_fn" && "$encrypt" -eq -1 ]]; then
+ case "$archive_fn" in
+ *.gpg)
+ archive_action="${archive_action}.gpg"
+ ;;
+ esac
+fi
+
+# output filename
+if [[ -z "$archive_fn" ]]; then
+ archive_fn="archive.${archive_action}"
+ checksum_fn="archive.sha1"
+ debug_msg "No output filename was given, defaulting to '${archive_fn}'"
+else
+ checksum_fn="$(fn_basename "$archive_fn").sha1"
+fi
+
+# check files
+if contains "$archive_fn" "${positional[@]}"; then
+ error_msg "Output file cannot also be input file"
+elif [[ "$checksum" -eq 1 ]] && contains "$checksum_fn" "${positional[@]}"; then
+ error_msg "Output file cannot also be input file"
+fi
+if ! prompt_overwrite "$archive_fn"; then
+ exit 1
+elif [[ "$checksum" -eq 1 ]] && ! prompt_overwrite "$checksum_fn"; then
+ exit 1
+fi
+for target in "${positional[@]}";do
+ if [[ ! -f "$target" ]]; then
+ error_msg "No such file '${target}'"
+ fi
+done
+
+# main routine
+code=0
+case "$archive_action" in
+tar)
+ if ! /usr/bin/tar -cf "$archive_fn" "${positional[@]}"; then
+ code=1
+ fi
+ ;;
+
+tar.gpg)
+ if ! /usr/bin/tar -c "${positional[@]}" | /usr/bin/gpg -c -o "$archive_fn"; then
+ code=1
+ fi
+ ;;
+
+tar.gz)
+ if ! /usr/bin/tar -czf "$archive_fn" "${positional[@]}"; then
+ code=1
+ fi
+ ;;
+
+tar.gz.gpg)
+ if ! /usr/bin/tar -cz "${positional[@]}" | /usr/bin/gpg -c -o "$archive_fn"; then
+ code=1
+ fi
+ ;;
+
+tar.xz)
+ if ! /usr/bin/tar -cJf "$archive_fn" "${positional[@]}"; then
+ code=1
+ fi
+ ;;
+
+tar.xz.gpg)
+ if ! /usr/bin/tar -cJ "${positional[@]}" | /usr/bin/gpg -c -o "$archive_fn"; then
+ code=1
+ fi
+ ;;
+
+tar.zst)
+ if ! /usr/bin/tar --zstd -cf "$archive_fn" "${positional[@]}"; then
+ code=1
+ fi
+ ;;
+
+tar.zst.gpg)
+ if ! /usr/bin/tar --zstd -c "${positional[@]}" | /usr/bin/gpg -c -o "$archive_fn"; then
+ code=1
+ fi
+ ;;
+
+tar.bz2)
+ if ! /usr/bin/tar -cjf "$archive_fn" "${positional[@]}"; then
+ code=1
+ fi
+ ;;
+
+tar.bz2.gpg)
+ if ! /usr/bin/tar -cj "${positional[@]}" | /usr/bin/gpg -c -o "$archive_fn"; then
+ code=1
+ fi
+ ;;
+esac
+
+# checksum routine
+if [[ "$checksum" -eq 1 ]]; then
+ if ! /usr/bin/sha1sum "$archive_fn" | /usr/bin/awk '{print $1}' > "$checksum_fn"; then
+ code=1
+ fi
+fi
+
+# return stored code
+exit "$code"
+
A src/mylib.bash => src/mylib.bash +248 -0
@@ 0,0 1,248 @@
+#!/bin/bash
+# shellcheck disable=SC2030,SC2031
+
+# This library gives access to these functions:
+# msg MSG prints MSG, except if $quiet==1
+# prompt MSG prints MSG and sets $response to user input
+# debug_msg MSG prints MSG only if $verbose==1
+# nonfatal_error_msg MSG prints MSG to STDERR, except if $quiet==1
+# error_msg MSG prints MSG to STDERR, except if $quiet==1
+# help_msg prints built-in documentation
+# usage_msg prints usage instructions to STDERR, except if $quiet==1
+# version_msg prints version to STDERR, except if $quiet==1
+# is_integer VALUE checks if VALUE is an integer
+# is_natural VALUE checks if VALUE is an integer >= 0
+# is_positive_integer VALUE checks if VALUE is an integer
+# contains NEEDLE HAYSTACK checks if NEEDLE is in array HAYSTACK
+# prompt_overwrite FILE prompts user for permission to overwrite FILE
+# fn_basename FN extracts identifiable base from FN
+# fn_extension FN extracts file extension from FN
+#
+# Through some means, these environment variables should be set:
+# name name of program
+# version version of program
+# help_message built-in documentation
+# verbose 0 or 1; should more messages be shown, with debugging prefixes?
+# quiet 0 or 1; should messages be suppressed?
+
+
+# Internal API - precedes a message
+# Normal ->
+# Quiet ->
+# Verbose -> <LEVEL>:<PROGRAM>:
+# Verbose AND Quiet -> <LEVEL>:<PROGRAM>:
+verbose_prefix() {
+ if [[ "${verbose:=0}" -eq 1 ]]; then
+ (/usr/bin/printf "%s:%s:" "$1" "$name")
+ fi
+}
+
+
+# Internal API - precedes a message
+# Normal -> <PROGRAM>:
+# Quiet ->
+# Verbose -> ERROR:<PROGRAM>:
+# Verbose AND Quiet -> ERROR:<PROGRAM>:
+error_prefix() {
+ if [[ "${verbose:=0}" -eq 1 ]]; then
+ (>&2 /usr/bin/printf "ERROR:%s:" "$name")
+ elif [[ "${quiet:=0}" -eq 0 ]]; then
+ (>&2 /usr/bin/printf "%s: " "$name")
+ fi
+}
+
+
+# Internal API - follows a prefix
+# Normal -> <MESSAGE>
+# Quiet ->
+# Verbose -> <MESSAGE>
+# Verbose AND Quiet -> <MESSAGE> (NOTE: unsuppressed)
+base_msg() {
+ if [[ "${quiet:=0}" -eq 0 ]]; then
+ /usr/bin/printf "%s\n" "$1"
+ elif [[ "${verbose:=0}" -eq 1 ]]; then
+ /usr/bin/printf "%s (NOTE: unsuppressed)\n" "$1"
+ fi
+}
+
+
+# Internal API
+# Normal -> <MESSAGE>
+# Quiet ->
+# Note: ignore Verbose
+dump_msg() {
+ if [[ "${quiet:=0}" -eq 0 ]]; then
+ /usr/bin/printf "%s\n" "$1"
+ fi
+}
+
+
+# Normal -> <MESSAGE>
+# Quiet ->
+# Verbose -> INFO:<PROGRAM>:<MESSAGE>
+# Verbose AND Quiet -> INFO:<PROGRAM>:<MESSAGE> (NOTE: unsuppressed)
+msg() {
+ verbose_prefix "INFO"
+ base_msg "$1"
+}
+
+
+# Normal -> <MESSAGE>
+# Verbose -> PROMPT:<PROGRAM>:<MESSAGE>
+# DEBUG:<PROGRAM>:Received value of <VALUE>
+# Note: if Quiet, exit as failure
+prompt() {
+ if [[ "${quiet:=0}" -eq 0 ]]; then
+ verbose_prefix "PROMPT"
+ read -r -p "$1" response
+ debug_msg "Received value of '${response}'"
+ else
+ exit 1
+ fi
+}
+
+
+# Normal ->
+# Quiet ->
+# Verbose -> DEBUG:<PROGRAM>:<MESSAGE>
+# Verbose AND Quiet -> DEBUG:<PROGRAM>:<MESSAGE>
+debug_msg() {
+ if [[ "${verbose:=0}" -eq 1 ]]; then
+ verbose_prefix "DEBUG"
+ /usr/bin/printf "%s\n" "$1"
+ fi
+}
+
+
+# Normal -> <PROGRAM>: <MESSAGE>
+# Quiet ->
+# Verbose -> ERROR:<PROGRAM>:<MESSAGE>
+# Verbose AND Quiet -> ERROR:<PROGRAM>:<MESSAGE> (NOTE: unsuppressed)
+nonfatal_error_msg() {
+ error_prefix
+ (>&2 base_msg "$1")
+}
+
+
+# Normal -> <PROGRAM>: <MESSAGE>
+# Quiet ->
+# Verbose -> ERROR:<PROGRAM>:<MESSAGE>
+# Verbose AND Quiet -> ERROR:<PROGRAM>:<MESSAGE> (NOTE: unsuppressed)
+# Note: exit as error
+error_msg() {
+ nonfatal_error_msg "$1"
+ exit 1
+}
+
+
+# Normal -> <USAGE MESSAGE>
+# Quiet ->
+# Note: exit as error
+# Note: ignore Verbose
+usage_msg() {
+ (>&2 dump_msg "$(/usr/bin/printf "${help_message:=Usage: don\'t}\n" | grep -e 'Usage' | head -n 1)")
+ exit 1
+}
+
+
+# Normal -> <HELP MESSAGE>
+# <HELP MESSAGE>
+# <HELP MESSAGE>
+# Quiet ->
+# Note: exit as success
+# Note: ignore Verbose
+help_msg() {
+ (dump_msg "${help_message:=git gud}")
+ exit 0
+}
+
+
+# Normal -> <PROGRAM> <VERSION>
+# Quiet ->
+# Note: exit as success
+# Note: ignore Verbose
+version_msg() {
+ (dump_msg "${name:=my_program} ${version:=X.Y}")
+ exit 0
+}
+
+
+# is integer -> 0
+# else -> 1
+is_integer() {
+ [ "$1" -eq "$1" ] 2>/dev/null && return 0 || return 1
+}
+
+
+# is integer >=0 -> 0
+# else -> 1
+is_natural() {
+ [ "$1" -ge 0 ] 2>/dev/null && return 0 || return 1
+}
+
+
+# is integer >=1 -> 0
+# else -> 1
+is_positive_integer() {
+ [ "$1" -ge 1 ] 2>/dev/null && return 0 || return 1
+}
+
+
+# is in array -> 0
+# else -> 1
+contains() {
+ pattern="$1"; shift
+ code=1
+ for arg; do
+ if [[ "$arg" == "$pattern" ]]; then
+ code=0
+ break
+ fi
+ done
+ return "$code"
+}
+
+
+# file does not exist OR user input 'Yy*' -> 0
+# else -> 1
+prompt_overwrite() {
+ code=0
+ if [[ -f "$1" ]]; then
+ prompt "File '${1}' already exists. Overwrite? "
+ case "$response" in
+ [Yy]*)
+ msg "Overwriting..."
+ ;;
+
+ *)
+ nonfatal_error_msg "Exiting"
+ code=1
+ ;;
+ esac
+ fi
+ return "$code"
+}
+
+
+# filename begins in . -> original filename
+# else -> original filename up to first .
+fn_basename() {
+ if [[ "$1" = ".*" ]]; then
+ printf "%s\n" "$1"
+ else
+ printf "%s\n" "$1" | cut -f 1 -d '.'
+ fi
+}
+
+
+# filename begins in . -> original filename
+# else -> original filename after first .
+fn_extension() {
+ if [[ "$1" = ".*" ]]; then
+ printf "%s\n" "$1"
+ else
+ printf "%s\n" "$1" | cut -f 2- -d '.'
+ fi
+}
+
+
A src/myminiparse.sh => src/myminiparse.sh +27 -0
@@ 0,0 1,27 @@
+#!/bin/sh
+# shellcheck disable=SC2034
+
+# A very basic argument parser written for POSIX sh. Only supports printing
+# a help message or a version number. Intended for scripts that have no good
+# reason to support debugging messages.
+
+quiet=0
+
+for arg; do
+ case "$arg" in
+ -h|--help)
+ /usr/bin/printf "${help_message:=git gud}\n"
+ exit 0
+ ;;
+
+ -q|--quiet)
+ quiet=1
+ ;;
+
+ -v|--version)
+ /usr/bin/printf "${name:=my_program} ${version:=X.Y}\n"
+ exit 0
+ ;;
+ esac
+done
+
A src/myparse.bash => src/myparse.bash +42 -0
@@ 0,0 1,42 @@
+#!/bin/bash
+
+# A basic argument parser written in bash. Depends on and supplies all
+# variables expected by mylib.bash. Sufficient for any script that takes
+# positional arguments only.
+
+positional=()
+quiet=0
+verbose=0
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting QUIET option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting VERBOSE option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
A src/rand => src/rand +98 -0
@@ 0,0 1,98 @@
+#!/bin/bash
+
+name="rand"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Get a random number within an inclusive range
+ Usage: rand START END [OPTIONS]
+ Options:
+ -h, --help print this message
+ -n N, --number N print N numbers (Default: 1)
+ -q, --quiet suppress error messages and prompts
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+ -w N, --width N print numbers zero-padded to N-wide (Default: 0)
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+quiet=0
+verbose=0
+width=1
+number=1
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -n|--number)
+ if ! is_positive_integer "$2"; then
+ error_msg "Cannot set number of outputs to '${2}' (not an integer >= 1)"
+ fi
+ debug_msg "Setting number of outputs to ${2} (was ${number})"
+ number="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ -w|--width)
+ if ! is_natural "$2"; then
+ error_msg "Cannot set zero-padding width to '${2}' (not an integer >= 0)"
+ fi
+ debug_msg "Setting zero-padding width to ${2} (was ${width})"
+ width="$2";
+ shift; shift
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# check arguments
+if [[ ${#positional[@]} -lt 2 ]]; then
+ debug_msg "Expected 2 arguments (given ${#positional[@]})"
+ usage_msg
+else
+ range_start="${positional[0]}"
+ range_end="${positional[1]}"
+ if ! is_integer "$range_start"; then
+ error_msg "Expected integer for range start (given '${range_start}')"
+ elif ! is_integer "$range_end"; then
+ error_msg "Expected integer for range end (given '${range_end}')"
+ elif [[ "$range_start" -ge "$range_end" ]]; then
+ error_msg "Expected ascending range (given '${range_start}' and '${range_end}')"
+ fi
+fi
+
+# main routine
+code=0
+if ! seq -f "%0${width}g" "$range_start" "$range_end" | shuf -n "$number"; then
+ code=1
+fi
+
+# return stored code
+exit "$code"
+
A src/rebom => src/rebom +41 -0
@@ 0,0 1,41 @@
+#!/bin/sh
+
+name="rebom"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Add BOM to a target file
+ Usage: rebom TARGET [OPTIONS]
+ -h, --help print this message
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# re-print first non-option argument, then exit
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ # main routine
+ if [ ! -f "$arg" ]; then
+ (>&2 printf "%s: No such file '%s'\n" "$name" "$arg")
+ exit 1
+ else
+ /usr/bin/printf "\xEF\xBB\xBF"
+ /usr/bin/cat "$1"
+ exit $?
+ fi
+ ;;
+ esac
+done
+
+# if have not exited, no filename was specified
+if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: rebom TARGET [OPTIONS]\n")
+fi
+exit 1
+
A src/rmtar => src/rmtar +41 -0
@@ 0,0 1,41 @@
+#!/bin/sh
+
+name="rmtar"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Delete 'tar' archive files
+ Usage: rmtar TARGET [..] [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# error if no directory names given
+if [ "$#" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: rmtar TARGET [OPTIONS]\n")
+ exit 1
+elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
+ exit 1
+fi
+
+# main routine
+code=0
+for arg; do
+ if [ ! -f "$arg" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such file '%s'\n" "$name" "$arg")
+ fi
+ code=1
+ else
+ if ! /usr/bin/rm "$arg"; then
+ code=1
+ fi
+ fi
+done
+
+# return stored code
+exit "$code"
+
A src/rmzip => src/rmzip +41 -0
@@ 0,0 1,41 @@
+#!/bin/sh
+
+name="rmzip"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Delete 'zip' archive files
+ Usage: rmzip TARGET [..] [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# error if no directory names given
+if [ "$#" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: rmzip TARGET [OPTIONS]\n")
+ exit 1
+elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
+ exit 1
+fi
+
+# main routine
+code=0
+for arg; do
+ if [ ! -f "$arg" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such file '%s'\n" "$name" "$arg")
+ fi
+ code=1
+ else
+ if ! /usr/bin/rm "$arg"; then
+ code=1
+ fi
+ fi
+done
+
+# return stored code
+exit "$code"
+
A src/stop-at => src/stop-at +22 -0
@@ 0,0 1,22 @@
+#!/bin/awk -f
+
+# stop-at
+# ===========
+# Usage: <command> | stop-at -v pattern='^-+$'
+#
+# Re-print until a pattern is matched
+
+BEGIN {
+ if (pattern == "" || inclusive !~ /^[01]?$/) {
+ print "Usage: stop-at -v pattern=PATTERN -v inclusive=0|1"
+ exit 1
+ }
+}
+{
+ if ($0 ~ pattern) {
+ if (inclusive==1) print;
+ exit 0
+ }
+ print $0
+}
+
A src/tarcat => src/tarcat +97 -0
@@ 0,0 1,97 @@
+#!/bin/sh
+
+name="tarcat"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Print contents of target archive file(s)
+ Usage: tarcat [TARGET ..] [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+code=0
+
+for target; do
+ if [ ! -f "$target" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such file '%s'\n" "$name" "$target")
+ fi
+ code=1
+ else
+ case "$target" in
+ *tar)
+ if ! /usr/bin/tar -xOf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xO; then
+ code=1
+ fi
+ ;;
+
+ *tar.gz)
+ if ! /usr/bin/tar -xzOf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.gz.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xzO; then
+ code=1
+ fi
+ ;;
+
+ *tar.xz)
+ if ! /usr/bin/tar -xJOf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.xz.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xJO; then
+ code=1
+ fi
+ ;;
+
+ *tar.zst)
+ if ! /usr/bin/tar --zstd -xOf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.zst.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar --zstd -xO; then
+ code=1
+ fi
+ ;;
+
+ *tar.bz2)
+ if ! /usr/bin/tar -xjOf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.bz2.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xjO; then
+ code=1
+ fi
+ ;;
+
+ *)
+ if ! /usr/bin/tar -xOf "$target"; then
+ code=1
+ fi
+ ;;
+ esac
+ fi
+done
+
+exit "$code"
+
A src/unittest => src/unittest +80 -0
@@ 0,0 1,80 @@
+#!/bin/sh
+
+name="unittest"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Wrapper around Python's 'unittest' module
+ Usage: unittest TARGET [OPTIONS]
+ Options:
+ -c, --color print with color
+ -h, --help print this message and exit
+ -v, --verbose show verbose 'unittest' output
+ -V, --version print version number and exit
+EOF
+)
+
+target=""
+color=0
+verbose=0
+python_executable="/usr/bin/env python3"
+for arg; do
+ case "$arg" in
+ -c|--color)
+ color=1
+ ;;
+
+ -h|--help)
+ printf "%s\n" "$help_message"
+ exit 0
+ ;;
+
+ -v|--verbose)
+ verbose=1
+ ;;
+
+ -V|--version)
+ printf "%s %s\n" "$name" "$version"
+ exit 0
+ ;;
+
+ *)
+ if [ -z "$target" ]; then
+ target="$arg"
+ fi
+ ;;
+ esac
+done
+
+# unittest subroutine
+unittest_subroutine() {
+ if [ "$verbose" -eq 1 ]; then
+ if ! $python_executable -m unittest discover -v -s "$target" 2>&1; then
+ code=1
+ fi
+ else
+ if ! $python_executable -m unittest discover -s "$target" 2>&1 | /usr/bin/tail -n 1; then
+ code=1
+ fi
+ fi
+}
+
+# check directory
+if [ -z "${target+x}" ]; then
+ (>&2 printf "Usage: unittest TARGET [OPTIONS]")
+ exit 1
+elif [ ! -d "$target" ]; then
+ (>&2 printf "%s: No such directory '%s'\n" "$name" "$target")
+ exit 1
+fi
+
+# main routine
+code=0
+if [ "$color" -eq 0 ] || { [ -n "${NO_COLOR+x}" ] && [ "$NO_COLOR" -eq 1 ]; }; then
+ unittest_subroutine
+else
+ unittest_subroutine | /usr/local/lib/unittest-color.awk
+fi
+
+# return stored code
+exit "$code"
+
A src/unittest-color.awk => src/unittest-color.awk +74 -0
@@ 0,0 1,74 @@
+#!/bin/awk -f
+
+# unittest-color.awk
+# ==================
+# Color filter for Python's 'unittest'
+
+BEGIN {
+ red="\033[31;1m"
+ green="\033[32;1m"
+ yellow="\033[33m"
+ cyan="\033[36m";
+ reset="\033[0m";
+ dim="\033[2m";
+
+ comma_replacement=reset ","
+ brace_replacement=reset ")"
+ equal_replacement="=" yellow
+}
+{
+ # color tracebacks
+ if ($0 ~ /^ERROR:/) {
+ $1=red $1 reset dim cyan
+ $0=$0 reset
+ }
+ else if ($0 ~ /^\s*File ".+", line [1-9][0-9]*, in/) {
+ $2=yellow $2 reset dim cyan
+ $4=yellow $4 reset dim cyan
+ $6=yellow $6 reset dim cyan
+ $0=" " dim cyan $0 reset
+ }
+
+ # highlight numeric measures
+ else if ($0 ~ /^Ran [1-9][0-9]* tests?/) {
+ $2=yellow $2 reset
+ $5=yellow $5 reset
+ }
+
+ # color results
+ else if ( $0 ~ /^OK\s*$/ || $0 ~ /^OK \(/ ) {
+ $1=green $1 reset
+ gsub(/=/, equal_replacement)
+ gsub(/,/, comma_replacement)
+ gsub(/)/, brace_replacement)
+ $0=$0 reset
+ }
+ else if ($0 ~ /^(ERROR|FAILED) \(/) {
+ $1=red $1 reset
+ $0=$0 reset
+ gsub(/=/, equal_replacement)
+ gsub(/,/, comma_replacement)
+ gsub(/)/, brace_replacement)
+ $0=$0 reset
+ }
+ else if ($0 ~ /\.\.\. (ok|skipped)/) {
+ $2=dim cyan $2
+ $0=$0 reset
+ }
+ else if ($0 ~ /\.\.\. (ERROR|FAIL)/) {
+ $2=dim cyan $2
+ $NF=reset red $NF
+ $0=$0 reset
+ }
+
+ # color string diffs
+ else if ($0 ~ /^\+ /) {
+ $1=green $1 reset
+ }
+ else if ($0 ~ /^\- /) {
+ $1=red $1 reset
+ }
+
+ print $0
+}
+
A src/untar => src/untar +105 -0
@@ 0,0 1,105 @@
+#!/bin/sh
+
+name="untar"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Wrapper around 'tar' for easier decompression
+ Usage: untar TARGET [..] [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# error if no directory names given
+if [ "$#" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: untar TARGET [OPTIONS]\n")
+ exit 1
+elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
+ exit 1
+fi
+
+# main routine
+code=0
+for target; do
+ if [ ! -f "$target" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such file '%s'\n" "$name" "$target")
+ fi
+ code=1
+ else
+ case "$target" in
+ *tar)
+ if ! /usr/bin/tar -xf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -x; then
+ code=1
+ fi
+ ;;
+
+ *tar.gz)
+ if ! /usr/bin/tar -xzf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.gz.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xz; then
+ code=1
+ fi
+ ;;
+
+ *tar.xz)
+ if ! /usr/bin/tar -xJf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.xz.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xJ; then
+ code=1
+ fi
+ ;;
+
+ *tar.zst)
+ if ! /usr/bin/tar --zstd -xf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.zst.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar --zstd -x; then
+ code=1
+ fi
+ ;;
+
+ *tar.bz2)
+ if ! /usr/bin/tar -xjf "$target"; then
+ code=1
+ fi
+ ;;
+
+ *tar.bz2.gpg)
+ if ! /usr/bin/gpg -d "$target" | /usr/bin/tar -xj; then
+ code=1
+ fi
+ ;;
+
+ *)
+ if ! /usr/bin/tar -xf "$target"; then
+ code=1
+ fi
+ ;;
+ esac
+ fi
+done
+
+exit "$code"
+
A src/whichcat => src/whichcat +42 -0
@@ 0,0 1,42 @@
+#!/bin/sh
+
+name="whichcat"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Print all lines from a program to the terminal
+ Usage: whichcat [PROGRAM ..] [OPTIONS]
+ Options:
+ -h, --help print this message
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# loop through input
+code=0
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ # main routine
+ target=$(command -v "$arg")
+ if [ -z "$target" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "%s: No such program '%s'\n" "$name" "$arg")
+ fi
+ code=1
+ else
+ /usr/bin/cat "$target"
+ fi
+ ;;
+ esac
+done
+
+# return stored code
+exit "$code"
+
A src/whiched => src/whiched +46 -0
@@ 0,0 1,46 @@
+#!/bin/sh
+
+name="whiched"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Open a program with your editor
+ Usage: whiched PROGRAM [OPTIONS]
+ Options:
+ -h, --help print this message
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# open first non-option argument in editor, then exit
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ # main routine
+ target=$(command -v "$arg")
+ if [ -z "$target" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such directory '%s'\n" "$name" "$target")
+ fi
+ exit 1
+ else
+ my_editor=${EDITOR:=/usr/bin/ed}
+ $my_editor "$target"
+ exit $?
+ fi
+ ;;
+ esac
+done
+
+# if have not exited, no program was specified
+if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: whiched PROGRAM [OPTIONS]\n")
+fi
+exit 1
+
A src/whichhead => src/whichhead +78 -0
@@ 0,0 1,78 @@
+#!/bin/bash
+
+name="whichhead"
+version="1.0"
+read -r -d '' help_message <<-EOF
+ Print the first 10 lines from a program
+ Usage: whichhead [PROGRAM ..] [OPTIONS]
+ Options:
+ -h, --help print this message
+ -n N, --number=N print the first N lines (Default: 10)
+ -q, --quiet suppress error messages
+ -v, --verbose show additional messages
+ -V, --version print version number and exit
+EOF
+
+source /usr/local/lib/mylib.bash
+
+positional=()
+num_lines=10
+quiet=0
+verbose=0
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+
+ -h|--help)
+ help_msg
+ shift
+ ;;
+
+ -n|--number)
+ if ! is_natural "$2"; then
+ error_msg "Cannot set number of lines to '${2}' (not an integer >= 0)"
+ fi
+ debug_msg "Setting number of lines to ${2} (was ${num_lines})"
+ num_lines="$2"
+ shift; shift
+ ;;
+
+ -q|--quiet)
+ debug_msg "Setting quiet option to 1 (was ${quiet})"
+ quiet=1
+ shift
+ ;;
+
+ -v|--verbose)
+ debug_msg "Setting verbose option to 1 (was ${verbose})"
+ verbose=1;
+ shift
+ ;;
+
+ -V|--version)
+ version_msg
+ ;;
+
+ *)
+ debug_msg "Argument '${1}' added to positional array"
+ positional+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# main routine
+code=0
+for target in "${positional[@]}"; do
+ abs_target=$(command -v "$target")
+ if [[ -z $abs_target ]]; then
+ nonfatal_error_msg "No such program '${target}'"
+ code=1
+ else
+ /usr/bin/head "$abs_target" -n "$num_lines"
+ fi
+done
+
+# return stored code
+exit "$code"
+
A src/whichvi => src/whichvi +46 -0
@@ 0,0 1,46 @@
+#!/bin/sh
+
+name="whichvi"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Open a program with your visual editor
+ Usage: whichvi PROGRAM [OPTIONS]
+ Options:
+ -h, --help print this message
+ -q, --quiet suppress error messages
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+# open first non-option argument in editor, then exit
+for arg; do
+ case "$arg" in
+ -q|--quiet)
+ #ignore these
+ ;;
+
+ *)
+ # main routine
+ target=$(command -v "$arg")
+ if [ -z "$target" ]; then
+ if [ "$quiet" -eq 0 ]; then
+ (>&2 printf "%s: No such directory '%s'\n" "$name" "$target")
+ fi
+ exit 1
+ else
+ my_visual=${VISUAL:=/usr/bin/ed}
+ $my_visual "$target"
+ exit $?
+ fi
+ ;;
+ esac
+done
+
+# if have not exited, no program was specified
+if [ "$quiet" -eq 0 ]; then
+ (>&2 /usr/bin/printf "Usage: whichvi PROGRAM [OPTIONS]\n")
+fi
+exit 1
+
A src/whisper => src/whisper +24 -0
@@ 0,0 1,24 @@
+#!/bin/sh
+
+name="whisper"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Wrapper around 'espeak' to mirror 'say' in macOS
+ Usage: mkbak MESSAGE [OPTIONS]
+ Options:
+ -h, --help print this message and exit
+ -v, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+if [ "$#" -eq 0 ]; then
+ my_message="present day <break time='1000ms'/> present time."
+else
+ my_message="$*"
+fi
+
+/usr/bin/espeak -v +whisper -s 8 -m "$my_message" 2>/dev/null
+exit "$?"
+
A src/wttr => src/wttr +17 -0
@@ 0,0 1,17 @@
+#!/bin/sh
+
+name="wttr"
+version="1.0"
+help_message=$(/usr/bin/cat <<-EOF
+ Wrapper around 'wego' to fix double-wide runes for some fonts
+ Usage: wttr
+ Options:
+ -h, --help print this message and exit
+ -V, --version print version number and exit
+EOF
+)
+
+. /usr/local/lib/myminiparse.sh
+
+wego $@ | sed -e 's/[↘↗↖↙]/ &/g'
+
A tests/bom_test.sh => tests/bom_test.sh +57 -0
@@ 0,0 1,57 @@
+#!/bin/sh
+
+# debom
+
+# create temp directory
+mkdir -p tests/temp_bom
+
+# exit codes
+if ! debom -V >/dev/null 2>&1; then
+ printf "Wrong exit code on 'debom -V' - should be 0\n"
+ exit 1
+elif ! debom -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'debom -h' - should be 0\n"
+ exit 1
+elif debom >/dev/null 2>&1; then
+ printf "Wrong exit code on 'debom' - should be 1\n"
+ exit 1
+elif debom non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'debom non-existant' should be 1\n"
+ exit 1
+fi
+
+# de-BOM a file
+cd tests/temp_bom
+debom -d ../static/debom_target.txt > debom.txt
+if ! cmp ../static/debom_result.txt debom.txt >/dev/null 2>&1; then
+ printf "Failure in de-BOM test\n"
+ exit 1
+fi
+cd ../..
+
+# rebom
+
+# exit codes
+if ! rebom -v >/dev/null 2>&1; then
+ printf "Wrong exit code on 'rebom -v' - should be 0\n"
+ exit 1
+elif ! rebom -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'rebom -h' - should be 0\n"
+ exit 1
+elif rebom >/dev/null 2>&1; then
+ printf "Wrong exit code on 'rebom' - should be 1\n"
+ exit 1
+elif rebom non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'rebom non-existant' should be 1\n"
+ exit 1
+fi
+
+# re-BOM a file
+cd tests/temp_bom
+rebom ../static/debom_result.txt > rebom.txt
+if ! cmp ../static/debom_target.txt rebom.txt >/dev/null 2>&1; then
+ printf "Failure in re-BOM test\n"
+ exit 1
+fi
+cd ../..
+
A tests/compression_test.sh => tests/compression_test.sh +99 -0
@@ 0,0 1,99 @@
+#!/bin/sh
+
+# mktar and tarcat
+
+# create temp directory
+mkdir -p tests/temp_compression
+
+# compress file with algorithms set by options
+cd tests/static
+mktar compression_target.txt --compress=none
+mktar compression_target.txt --compress=gzip
+mktar compression_target.txt --compress=xz
+mktar compression_target.txt --compress=zstd
+mktar compression_target.txt --compress=bzip2
+mv archive* ../temp_compression/
+cd ..
+
+# uncompress and combine compressed files
+cd temp_compression
+tarcat archive* > basic_concat.txt
+cd ..
+
+# test
+if ! cmp static/compression_result.txt temp_compression/basic_concat.txt >/dev/null 2>&1; then
+ printf "Failure in compression tests: basic compression\n"
+ exit 1
+fi
+
+# compress file with algorithms implicitly set by output filename
+cd static
+mktar compression_target.txt -n implicit.tar
+mktar compression_target.txt -n implicit.tar.gz
+mktar compression_target.txt -n implicit.tar.xz
+mktar compression_target.txt -n implicit.tar.zst
+mktar compression_target.txt -n implicit.tar.bz2
+mv implicit* ../temp_compression/
+cd ..
+
+# uncompress and combine compressed files
+cd temp_compression
+tarcat implicit* > implicit_concat.txt
+cd ..
+
+# test
+if ! cmp static/compression_result.txt temp_compression/implicit_concat.txt >/dev/null 2>&1; then
+ printf "Failure in compression tests: implicit compression\n"
+ exit 1
+fi
+
+# compress file with algorithms set and filenames set
+cd static
+mktar compression_target.txt --compress=none -n explicit.tar
+mktar compression_target.txt --compress=gzip -n explicit.tar.gz
+mktar compression_target.txt --compress=xz -n explicit.tar.xz
+mktar compression_target.txt --compress=zstd -n explicit.tar.zst
+mktar compression_target.txt --compress=bzip2 -n explicit.tar.bz2
+mv explicit* ../temp_compression/
+cd ..
+
+# uncompress and combine compressed files
+cd temp_compression
+tarcat explicit* > explicit_concat.txt
+cd ..
+
+# test
+if ! cmp static/compression_result.txt temp_compression/explicit_concat.txt >/dev/null 2>&1; then
+ printf "Failure in compression tests: explicit compression\n"
+ exit 1
+fi
+
+# untar
+
+# copy archives to temp directory
+cp static/decompression_target* temp_compression/
+
+# decompress files
+cd temp_compression
+untar decompression_target.tar decompression_target.tar.gz decompression_target.tar.xz decompression_target.tar.zst decompression_target.tar.bz2
+cd ..
+
+# test
+if ! cmp static/decompression_result.txt temp_compression/tar.txt >/dev/null 2>&1; then
+ printf "Failure in decompression tests: tar\n"
+ exit 1
+elif ! cmp static/decompression_result.txt temp_compression/gzip.txt >/dev/null 2>&1; then
+ printf "Failure in decompression tests: gzip\n"
+ exit 1
+elif ! cmp static/decompression_result.txt temp_compression/xz.txt >/dev/null 2>&1; then
+ printf "Failure in decompression tests: xz\n"
+ exit 1
+elif ! cmp static/decompression_result.txt temp_compression/zstd.txt >/dev/null 2>&1; then
+ printf "Failure in decompression tests: zstd\n"
+ exit 1
+elif ! cmp static/decompression_result.txt temp_compression/bzip2.txt >/dev/null 2>&1; then
+ printf "Failure in decompression tests: bzip2\n"
+ exit 1
+fi
+
+
A tests/ctdir_test.sh => tests/ctdir_test.sh +36 -0
@@ 0,0 1,36 @@
+#!/bin/sh
+
+# ctdir
+
+# exit codes
+if ! ctdir -v >/dev/null 2>&1; then
+ printf "Wrong exit code on 'ctdir -v' - should be 0\n"
+ exit 1
+elif ! ctdir -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'ctdir -h' - should be 0\n"
+ exit 1
+elif ctdir >/dev/null 2>&1; then
+ printf "Wrong exit code on 'ctdir' - should be 1\n"
+ exit 1
+elif ctdir non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'ctdir non-existant' should be 1\n"
+ exit 1
+elif ! ctdir . >/dev/null 2>&1; then
+ printf "Wrong exit code on 'ctdir .' should be 0\n"
+ exit 1
+fi
+
+# output
+num=$(ctdir .)
+if [ ! "$num" -eq "$num" ] 2>/dev/null; then
+ printf "Did not return a number on 'ctdir .'\n"
+ exit 1
+fi
+
+# behavior on multiple inputs
+lines=$(ctdir . . | wc -l)
+if [ "$lines" -ne 2 ] 2>/dev/null; then
+ printf "Did not return a number for each input on 'ctdir . .'\n"
+ exit 1
+fi
+
A tests/static/compression_result.txt => tests/static/compression_result.txt +20 -0
A tests/static/compression_target.txt => tests/static/compression_target.txt +4 -0
@@ 0,0 1,4 @@
+[1]
+[2]
+[3]
+
A tests/static/debom_result.txt => tests/static/debom_result.txt +1 -0
A tests/static/debom_target.txt => tests/static/debom_target.txt +1 -0
A tests/static/decompression_result.txt => tests/static/decompression_result.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/static/decompression_target.tar => tests/static/decompression_target.tar +0 -0
A tests/static/decompression_target.tar.bz2 => tests/static/decompression_target.tar.bz2 +0 -0
A tests/static/decompression_target.tar.gz => tests/static/decompression_target.tar.gz +0 -0
A tests/static/decompression_target.tar.xz => tests/static/decompression_target.tar.xz +0 -0
A tests/static/decompression_target.tar.zst => tests/static/decompression_target.tar.zst +0 -0
A tests/temp_bom/debom.txt => tests/temp_bom/debom.txt +1 -0
A tests/temp_bom/rebom.txt => tests/temp_bom/rebom.txt +1 -0
A tests/temp_compression/archive.tar => tests/temp_compression/archive.tar +0 -0
A tests/temp_compression/archive.tar.bz2 => tests/temp_compression/archive.tar.bz2 +0 -0
A tests/temp_compression/archive.tar.gz => tests/temp_compression/archive.tar.gz +0 -0
A tests/temp_compression/archive.tar.xz => tests/temp_compression/archive.tar.xz +0 -0
A tests/temp_compression/archive.tar.zst => tests/temp_compression/archive.tar.zst +0 -0
A tests/temp_compression/basic_concat.txt => tests/temp_compression/basic_concat.txt +20 -0
A tests/temp_compression/bzip2.txt => tests/temp_compression/bzip2.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/temp_compression/decompression_target.tar => tests/temp_compression/decompression_target.tar +0 -0
A tests/temp_compression/decompression_target.tar.bz2 => tests/temp_compression/decompression_target.tar.bz2 +0 -0
A tests/temp_compression/decompression_target.tar.gz => tests/temp_compression/decompression_target.tar.gz +0 -0
A tests/temp_compression/decompression_target.tar.xz => tests/temp_compression/decompression_target.tar.xz +0 -0
A tests/temp_compression/decompression_target.tar.zst => tests/temp_compression/decompression_target.tar.zst +0 -0
A tests/temp_compression/explicit.tar => tests/temp_compression/explicit.tar +0 -0
A tests/temp_compression/explicit.tar.bz2 => tests/temp_compression/explicit.tar.bz2 +0 -0
A tests/temp_compression/explicit.tar.gz => tests/temp_compression/explicit.tar.gz +0 -0
A tests/temp_compression/explicit.tar.xz => tests/temp_compression/explicit.tar.xz +0 -0
A tests/temp_compression/explicit.tar.zst => tests/temp_compression/explicit.tar.zst +0 -0
A tests/temp_compression/explicit_concat.txt => tests/temp_compression/explicit_concat.txt +20 -0
A tests/temp_compression/gzip.txt => tests/temp_compression/gzip.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/temp_compression/implicit.tar => tests/temp_compression/implicit.tar +0 -0
A tests/temp_compression/implicit.tar.bz2 => tests/temp_compression/implicit.tar.bz2 +0 -0
A tests/temp_compression/implicit.tar.gz => tests/temp_compression/implicit.tar.gz +0 -0
A tests/temp_compression/implicit.tar.xz => tests/temp_compression/implicit.tar.xz +0 -0
A tests/temp_compression/implicit.tar.zst => tests/temp_compression/implicit.tar.zst +0 -0
A tests/temp_compression/implicit_concat.txt => tests/temp_compression/implicit_concat.txt +20 -0
A tests/temp_compression/tar.txt => tests/temp_compression/tar.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/temp_compression/xz.txt => tests/temp_compression/xz.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/temp_compression/zstd.txt => tests/temp_compression/zstd.txt +4 -0
@@ 0,0 1,4 @@
+(a)
+(b)
+(c)
+
A tests/temp_enumerate/0001.a => tests/temp_enumerate/0001.a +0 -0
A tests/temp_enumerate/0002.b => tests/temp_enumerate/0002.b +0 -0
A tests/temp_enumerate/0003.c => tests/temp_enumerate/0003.c +0 -0
A tests/temp_enumerate/0004.d => tests/temp_enumerate/0004.d +0 -0
A tests/temp_enumerate/0005.e => tests/temp_enumerate/0005.e +0 -0
A tests/which_test.sh => tests/which_test.sh +92 -0
@@ 0,0 1,92 @@
+#!/bin/sh
+
+# whichcat
+
+# exit codes
+if ! whichcat -v >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichcat -v' - should be 0\n"
+ exit 1
+elif ! whichcat -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichcat -h' - should be 0\n"
+ exit 1
+elif ! whichcat >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichcat' - should be 0\n"
+ exit 1
+elif whichcat non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichcat non-existant' - should be 1\n"
+ exit 1
+elif ! whichcat whichcat >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichcat whichcat' - should be 0\n"
+ exit 1
+fi
+
+# behavior on multiple inputs
+lines_once=$(whichcat whichcat | wc -l)
+lines_twice=$(whichcat whichcat whichcat | wc -l)
+if [ "$lines_once" -ge "$lines_twice" ] 2>/dev/null; then
+ printf "Did not print output for each input on 'whichcat whichcat whichcat'\n"
+ exit 1
+fi
+
+# whichhead
+
+# exit codes
+if ! whichhead -V >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichhead -v' - should be 0\n"
+ exit 1
+elif ! whichhead -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichhead -V' - should be 0\n"
+ exit 1
+elif ! whichhead >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichhead' - should be 0\n"
+ exit 1
+elif whichhead non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichhead non-existant' - should be 1\n"
+ exit 1
+elif ! whichhead whichhead >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichhead whichhead' - should be 0\n"
+ exit 1
+fi
+
+# behavior on multiple inputs
+lines_once=$(whichhead whichhead | wc -l)
+lines_twice=$(whichhead whichhead whichhead | wc -l)
+if [ "$lines_once" -ge "$lines_twice" ] 2>/dev/null; then
+ printf "Did not print output for each input on 'whichhead whichhead whichhead'\n"
+ exit 1
+fi
+
+# whiched
+
+# exit codes
+if ! whiched -v >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whiched -v' - should be 0\n"
+ exit 1
+elif ! whiched -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whiched -V' - should be 0\n"
+ exit 1
+elif whiched >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whiched' - should be 1\n"
+ exit 1
+elif whiched non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whiched non-existant' - should be 1\n"
+ exit 1
+fi
+
+# whichvi
+
+# exit codes
+if ! whichvi -v >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichvi -v' - should be 0\n"
+ exit 1
+elif ! whichvi -h >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichvi -V' - should be 0\n"
+ exit 1
+elif whichvi >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichvi' - should be 1\n"
+ exit 1
+elif whichvi non-existant >/dev/null 2>&1; then
+ printf "Wrong exit code on 'whichvi non-existant' - should be 1\n"
+ exit 1
+fi
+
D tlog => tlog +0 -46
@@ 1,46 0,0 @@
-#!/bin/bash
-
-# tlog
-# ====
-# Usage: tlog TARGET [OPTIONS]
-#
-# Writes input to a target file while stripping ANSI codes, and returns input
-# as it was.
-
-help_msg() {
- cat <<-EOF
- Usage: tlog FILENAME [OPTIONS]
- Options:
- -a, --append append to target file instead of overwriting
- -h, --help print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-if [[ $# -lt 1 ]]; then
- err_msg "Usage: tlog TARGET [OPTIONS]"
-fi
-
-APPEND=0
-LOGFILE=
-while [[ $# -gt 0 ]]; do
- case $1 in
- -a|--append) APPEND=1; shift;;
- -h|--help) help_msg;;
- *) LOGFILE=$1; shift;;
- esac
-done
-
-if [[ -z $LOGFILE ]]; then
- err_msg "Usage: tlog TARGET [OPTIONS]"
-elif [[ $APPEND -eq 1 ]]; then
- tee >(sed 's/\x1b\[[0-9;]*[mK]//g' >> "$LOGFILE")
-else
- tee >(sed 's/\x1b\[[0-9;]*[mK]//g' > "$LOGFILE")
-fi
-
D unittest.sh => unittest.sh +0 -32
@@ 1,32 0,0 @@
-#!/bin/sh
-
-# unittest.sh
-# ===========
-# Usage: unittest.sh [TARGET] [OPTIONS]
-#
-# Run Python tests in a target directory
-
-help_msg() {
- cat <<-EOF
- Run Python tests in a target directory
- Usage: unittest.sh [TARGET] [OPTIONS]
- Options:
- -v, --verbose: verbose output
- -p, --pattern: pattern to match test files
- -h, --help: print this message
- EOF
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-DIR=${1:-.}
-OPTS=${2}
-python3 -m unittest discover -s $DIR $OPTS 2>&1 \
- | clean-unittest.awk \
- | color-unittest.sh
-
D untar => untar +0 -60
@@ 1,60 0,0 @@
-#!/bin/sh
-
-# untar
-# =====
-# Usage: untar FILE [OPTIONS]
-#
-# Uncompress a tarball. Expects standardized file names (i.e., '.tar.gz.gpg')
-
-help_msg() {
- cat <<-EOF
- Uncompress a tarball
- Usage: untar FILE [OPTIONS]
- Options:
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- # Takes one argument:
- # error message
- (>&2 echo "$1")
- exit 1
-}
-
-gpg_then_tar() {
- # Takes two arguments:
- # path to target file
- # tar options
- TEMP=$(basename "$1" ".gpg")
- gpg -o "$TEMP" -d "$1" || err_msg "Failed to decrypt '${1}'"
- tar "${2}" "$TEMP"
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ "$#" -lt 1 ]; then
- err_msg "Usage: untar FILE [OPTIONS]"
-elif ! test -f "$1"; then
- err_msg "No file '${1}'"
- exit
-fi
-
-case "$1" in
- *.tar) tar -xvf "$1";;
- *.tar.gz) tar -xzvf "$1";;
- *.tar.gz.gpg) gpg_then_tar "$1" "-xzvf";;
- *.tar.bz2) tar -xjvf "$1";;
- *.tar.bz2.gpg) gpg_then_tar "$1" "-xjvf";;
- *.tar.xz) tar -xJvf "$1";;
- *.tar.xz.gpg) gpg_then_tar "$1" "-xJvf";;
- *.tar.zst) tar --zstd -xvf "$1";;
- *.tar.zst.gpg) gpg_then_tar "$1" "--zstd -xvf";;
- *) err_msg "Not a known file extension '${1}'"
-esac
-
D weather => weather +0 -11
@@ 1,11 0,0 @@
-#!/bin/sh
-
-# weather
-# =======
-# Usage weather [NUMBER]
-#
-# Wrapper aroung `wego`. Show weather for 2 days unless a number is specified.
-# Location should be set in `$WEGORC`.
-
-wego ${1:-2} | sed -e 's/[↘↗↖↙]/ &/g' -e '2,7d'
-
D whichcat => whichcat +0 -42
@@ 1,42 0,0 @@
-#!/bin/sh
-
-# whichcat
-# ========
-# USAGE: whichcat PROGRAM
-#
-# Print all lines of a program
-
-help_msg() {
- cat <<-EOF
- Print all lines from a program to the terminal
- Usage: whichcat PROGRAM [OPTIONS]
- Options:
- -n, --number: number all output lines
- -s, --squeeze-blank: suppress repeated empty output lines
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ "$#" -lt 1 ]; then
- err_msg "Usage: whichcat PROGRAM [OPTIONS]"
-fi
-
-BIN=$(which "$1" 2>/dev/null)
-if [ -z "$BIN" ]; then
- err_msg "No program '${1}'"
-fi
-
-cat "$BIN"
-
D whiched => whiched +0 -41
@@ 1,41 0,0 @@
-#!/bin/sh
-
-# whiched
-# =======
-# USAGE: whiched PROGRAM
-#
-# Open a program in `$EDITOR`
-
-help_msg() {
- cat <<-EOF
- Open a program with your editor
- Usage: whiched PROGRAM [OPTIONS]
- Options:
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ "$#" -lt 1 ]; then
- err_msg "Usage: whiched PROGRAM [OPTIONS]"
-fi
-
-BIN=$(which "$1" 2>/dev/null)
-if [ -z "$BIN" ]; then
- err_msg "No program '${1}'"
-fi
-
-ED=${EDITOR:-ed}
-"$ED" "$BIN"
-
D whichhead => whichhead +0 -42
@@ 1,42 0,0 @@
-#!/bin/sh
-
-# whichhead
-# =========
-# USAGE: whichhead PROGRAM [OPTIONS]
-#
-# Print the first lines of a program
-
-help_msg() {
- cat <<-EOF
- Print the first 10 lines from a program
- Usage: whichhead PROGRAM [OPTIONS]
- Options:
- -n N, --lines=N: print the first N lines
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ "$#" -lt 1 ]; then
- err_msg "Usage: whichhead PROGRAM [OPTIONS]"
-fi
-
-BIN=$(which "$1" 2>/dev/null)
-if [ -z "$BIN" ]; then
- err_msg "No program '${1}'"
-fi
-
-OPTS="${@:2}"
-head "$BIN" ${OPTS[@]}
-
D whichvi => whichvi +0 -41
@@ 1,41 0,0 @@
-#!/bin/sh
-
-# whichvi
-# =======
-# USAGE: whichvi PROGRAM
-#
-# Open a program in `$VISUAL`
-
-help_msg() {
- cat <<-EOF
- Open a program with your visual editor
- Usage: whichvi PROGRAM [OPTIONS]
- Options:
- -h, --help: print this message
- EOF
- exit 1
-}
-
-err_msg() {
- (>&2 echo "$1")
- exit 1
-}
-
-for i in "$@"; do
- case $i in
- -h|--help) help_msg;;
- esac
-done
-
-if [ "$#" -lt 1 ]; then
- err_msg "Usage: whichvi PROGRAM [OPTIONS]"
-fi
-
-BIN=$(which "$1" 2>/dev/null)
-if [ -z "$BIN" ]; then
- err_msg "No program '${1}'"
-fi
-
-VIS=${VISUAL:-vi}
-"$VIS" "$BIN"
-
D whisper => whisper +0 -16
@@ 1,16 0,0 @@
-#!/bin/sh
-
-# whisper
-# =======
-# Usage: whisper [TEXT]
-#
-# Wrapper on `espeak` to mirror macOS's `say` using whisper voice.
-
-if [ $# -lt 1 ]; then
- TEXT="present day <break time='1000ms'/> present time."
-else
- TEXT=$*
-fi
-
-espeak -v +whisper -s 8 -m "$TEXT" 2>/dev/null
-