]> git.sev.monster Git - dotfiles.git/blob - bin/apkv
d8035bdc64932739d090725d74dc5fe8f1e0f994
[dotfiles.git] / bin / apkv
1 #!/bin/sh
2 # TODO: add duplicate checker to seeif a pkg is in multiple virtual pkgs
3
4 prog=$(basename $0)
5 usage() {
6     echo "\
7 apkv: Quickly add and remove packages to/from Alpine virtual packages.
8
9 Usage:
10     $prog add <packages> <virtual>
11         Add <packages> to virtual package <virtual>. If the virtual package
12         does not exist, it will be created.
13
14     $prog del <packages> <virtual>
15         Remove <packages> from virtual package <virtual>. Empty virtual
16         packages are removed from world. The shorthand '$prog del * <virtual>' can
17         be used to empty <virtual>.
18
19     $prog list [-o | packages]
20         List virtual packages and their contents.
21             packages  List these virtual packages. If empty, list all.
22             -o        Only list orphan packages that are not owned by any
23                       virtual package. Orphans are automatically shown when
24                       listing all packages.
25
26     $prog help
27         Show this help.
28
29 Virtual packages must be prefixed with a dot to be managed with apkv. Virtual
30 package names passed as arguments will automatically be prefixed with dots if
31 they are not present.
32
33 Methods can be shortened to their closest unambiguous forms, e.g. a, d, or l.
34 However, due to implementation specifics, any string starting with those forms
35 will work; that functionality is unspecified and is not guaranteed.
36
37 For example, the following are equivalent, with the first being preferred:
38     $prog add syslinux .virt
39     $prog a syslinux virt
40     $prog abcdefg syslinux virt
41
42 "
43 }
44
45 prepend_dot() {
46     case $1 in
47         .*) printf "$1";;
48         *)  printf ".$1";;
49     esac
50 }
51
52 show_reqs() {
53     _pkg="$1"
54     shift >/dev/null
55     [ -z "$_pkg" ] && return 1
56     if [ $# -gt 0 ]; then
57         # override reqs list for apkv add
58         _list=$(echo "$(for x do echo $x; done)" | sort | uniq)
59     else
60         apk info -e "$_pkg" >/dev/null || return 1
61         _list=$(apk info -R "$_pkg" | awk 'NR > 1 && NF' | sort)
62     fi
63     echo -e "\e[1m$_pkg\e[0m:" $_list
64 }
65
66 method="$1"
67 shift >/dev/null
68 case $method in
69     l*) method=list;;
70     a*) method=add;;
71     d*) method=del;;
72     h*) usage; exit;;
73     -*) usage; exit;;
74     *)  [ -n "$method" ] && echo "Invalid method $method" >&2; usage; exit 1;;
75 esac
76
77 if [ "$method" = list ]; then
78     if [ $# -eq 0 -o "$1" = '-o' ]; then
79         if [ "$1" = '-o' -a $# -gt 1 ]; then
80             echo "$method with -o switch does not accept arguments" >&2
81             usage
82             exit 1
83         fi
84         # ignore getopt's --, we don't care if an arg begins with - anyway
85         [ "$1" = '--' ] && switch >/dev/null
86
87         all=$(apk info | sort)
88         # show packages if no args
89         if [ $# -eq 0 ]; then
90             for x in $(echo "$all" | grep '^\.'); do
91                 show_reqs "$(prepend_dot "$x")"
92             done
93         fi
94         # set all child packages of virtual packages as owned
95         # OPTIMIZE: getting deps of virtual packages is slow, it would be
96         #           great to use apk info -R from show_reqs in orphan loop
97         # NOTE: since we don't have arrays in sh, set individual vars instead
98         for v in $(echo "$all" | grep '^\.'); do
99             for c in $(apk info -R $v | tail +2 | grep -v '^\.'); do
100                 # NOTE: use tr because shell may not have ksh-derived ${c//}
101                 # XXX: have not verified if package name may have more illegal
102                 #      chars for variable name past those already replaced, and
103                 #      replacing chars may result in variable collision—but
104                 #      official apk repos do not have any package names that
105                 #      would cause issues
106                 eval "__$(echo $c | tr -s '.:+-' _ | tr -s '<=~>@' "\n" | head -1)=1"
107             done
108         done
109         # check world against owned packages
110         # TODO: notify user if package is both in world and virtual package
111         orphans=$(
112             for x in $(cat /etc/apk/world | grep -v '^\.'); do
113                 eval "[ \${__$(
114                          echo $x | tr -s '.:+-' _ | tr -s '<=~>@' "\n" | head -1
115                      )-0} -eq 0 ]" && echo $x
116             done
117         )
118         if [ -z "$orphans" ]; then
119             echo "No orphan packages found."
120         else
121             show_reqs orphans $orphans
122         fi
123     else
124         for x in $*; do
125             show_reqs "$(prepend_dot "$x")"
126         done
127     fi
128     exit
129 fi
130
131 ## add/del
132 # get and validate packages
133 pkgs=
134 virt=
135 for x; do
136     if [ -n "$pkgs" ]; then
137         pkgs="$pkgs $virt"
138     elif [ -n "$virt" ]; then
139         pkgs=$virt
140     fi
141     virt=$x
142 done
143 if [ -z "$pkgs" ]; then
144     echo 'Not enough arguments' >&2
145     usage
146     exit 1
147 fi
148 virt=$(prepend_dot $virt)
149 # exit if database locked (99) or no package found to delete from (1)
150 apk info -e "$virt" >/dev/null
151 code=$?
152 if [ $code -eq 99 -o \( $method = del -a $code -ne 0 \) ]; then
153     exit $code
154 fi
155
156 # we have validated our args, so let's prepare to run apk: get sudo command if
157 # it exists and we are not already root
158 # TODO: support doas
159 if [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then
160     sudo=$(command -v sudo)
161 fi
162
163 # handle quick delete shorthand mentioned in help
164 if [ $method = del -a '*' = "$pkgs" ]; then
165     $sudo apk del "$virt"
166     exit $?
167 fi
168
169 currpkgs=$(apk info -R "$virt" | awk 'NR > 1 && NF')
170 case $method in
171     # NOTE: $(echo) construct uses word splitting to remove newlines
172     add) currpkgs="$(echo $currpkgs) $pkgs";;
173     del) # XXX: there's probably a more efficient way to do this
174          for x in $pkgs; do
175              currpkgs=$(echo "$currpkgs" | grep -vFiw $x)
176          done;;
177 esac
178
179 if [ -z "$currpkgs" ]; then
180     # virtual package is empty
181     $sudo apk del "$virt"
182     code=$?
183 else
184     show_reqs "$virt" $currpkgs
185     $sudo apk add -t "$virt" $currpkgs
186     code=$?
187     if [ $method = add -a $code -eq 0 ]; then
188         # remove packages from world if they are being moved to a virtual one
189         del=
190         for x in $pkgs; do
191             #apk_adb.c: [!]name[<,<=,<~,=,~,>~,>=,>,><]ver, @ for pinning
192             if grep -qE "^$x($|[<=~>@])" /etc/apk/world; then
193                 del="$del $x"
194             fi
195         done
196         if [ -n "$del" ]; then
197             echo "Removing packages from world:$del"
198             $sudo apk del $del
199         fi
200     fi
201 fi
202 # pass along sudo/apk exit code
203 exit $code
204
205 # vi:sw=4:sts=4:et
This page took 0.037353 seconds and 2 git commands to generate.