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