+#!/bin/sh
+
+usage() {
+ echo "\
+apkv: Quickly add and remove packages to/from Alpine virtual packages.
+
+Usage:
+ $0 add <packages> <virtual>
+ Add <packages> to virtual package <virtual>. If the virtual package
+ does not exist, it will be created.
+ $0 del <packages> <virtual>
+ Remove <packages> from virtual package <virtual>. Empty virtual
+ packages are removed from world. The shorthand '$0 del * <virtual>' can
+ be used to empty <virtual>.
+ $0 list [packages]
+ List all or specific virtual packages and their contents.
+ $0 help
+ Show this help.
+
+Virtual packages must be prefixed with a dot to be managed with apkv. If one is
+not present, it will be added automatically.
+
+Methods can be shortened to their closest unambiguous forms, e.g. a, d, or l.
+However, due to implementation specifics, any string starting with those forms
+will work; that functionality is unspecified and is not guaranteed.
+
+For example, the following are equivalent:
+ apkv add syslinux .virt
+ apkv a syslinux virt
+ apkv abcdefg syslinux .virt
+
+"
+}
+
+prepend_dot() {
+ case $1 in
+ .*) printf "$1";;
+ *) printf ".$1";;
+ esac
+}
+
+show_reqs() {
+ _pkg="$1"
+ shift >/dev/null
+ [ -z "$_pkg" ] && return 1
+ if [ $# -gt 0 ]; then
+ _list=$(echo "$(for x do echo $x; done)" | sort | uniq)
+ else
+ apk info -e "$_pkg" >/dev/null || return 1
+ _list=$(apk info -R "$_pkg" | awk 'NR > 1 && NF' | sort)
+ fi
+ echo -e "\e[1m$_pkg\e[0m:" $_list
+}
+
+method="$1"
+shift >/dev/null
+case $method in
+ l*) for x in ${*:-$(apk info | grep '^\.')}; do
+ show_reqs "$(prepend_dot "$x")"
+ done
+ exit;;
+ a*) method=add;;
+ d*) method=del;;
+ h*) usage; exit;;
+ -*) usage; exit;;
+ *) [ -n "$method" ] && echo "Invalid method $method" >&2; usage; exit 1;;
+esac
+
+## add/del
+# get and validate packages
+pkgs=
+virt=
+for x; do
+ if [ -n "$pkgs" ]; then
+ pkgs="$pkgs $virt"
+ elif [ -n "$virt" ]; then
+ pkgs=$virt
+ fi
+ virt=$x
+done
+if [ -z "$pkgs" ]; then
+ echo 'Not enough arguments' >&2
+ usage
+ exit 1
+fi
+virt=$(prepend_dot $virt)
+# exit if database locked (99) or no package found to delete from (1)
+apk info -e "$virt" >/dev/null
+code=$?
+if [ $code -eq 99 -o \( $method = del -a $code -ne 0 \) ]; then
+ exit $code
+fi
+
+# we have validated our args, so let's prepare to run apk: get sudo command if
+# it exists and we are not already root
+# TODO: support doas
+if [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then
+ sudo=$(command -v sudo)
+fi
+
+# handle quick delete shorthand mentioned in help
+if [ $method = del -a '*' = "$pkgs" ]; then
+ $sudo apk del "$virt"
+ exit $?
+fi
+
+currpkgs=$(apk info -R "$virt" | awk 'NR > 1 && NF')
+case $method in
+ # $(echo) construct is to use word splitting to normalize whitespace
+ add) currpkgs="$(echo $currpkgs) $pkgs";;
+ del) # XXX: there's probably a more efficient way to do this
+ for x in "$pkgs"; do
+ currpkgs=$(echo "$currpkgs" | grep -vFiw $x)
+ done;;
+esac
+
+if [ -z "$currpkgs" ]; then
+ # virtual package is empty
+ $sudo apk del "$virt"
+else
+ show_reqs "$virt" $currpkgs
+ $sudo apk add -t "$virt" $currpkgs
+fi
+# pass along sudo/apk exit code
+exit $?
+
+# vi:sw=4:sts=4:et