update CVS update script to account for Dead revisions
[shellsnippets/shellsnippets.git] / mksh / wtf
1 #!/bin/mksh
2 myver='$MirOS: src/usr.bin/wtf/wtf,v 1.44 2020/06/06 23:44:34 tg Exp $'
3 # $NetBSD: wtf,v 1.7 2000/11/21 00:18:52 soren Exp $
4 #-
5 # Copyright © 2002, 2003, 2004, 2006, 2007, 2008, 2010, 2011,
6 #             2012, 2014, 2015, 2017, 2019, 2020
7 #       mirabilos <m@mirbsd.org>
8 #
9 # Provided that these terms and disclaimer and all copyright notices
10 # are retained or reproduced in an accompanying document, permission
11 # is granted to deal in this work without restriction, including un‐
12 # limited rights to use, publicly perform, distribute, sell, modify,
13 # merge, give away, or sublicence.
14 #
15 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
16 # the utmost extent permitted by applicable law, neither express nor
17 # implied; without malicious intent or gross negligence. In no event
18 # may a licensor, author or contributor be held liable for indirect,
19 # direct, other damage, loss, or other issues arising in any way out
20 # of dealing in the work, even if advised of the possibility of such
21 # damage or existence of a defect, except proven that it results out
22 # of said person’s immediate fault when using the work as intended.
23
24 export LC_ALL=C.UTF-8 POSIXLY_CORRECT=1
25 unset LANGUAGE GREP_OPTIONS GREP_COLOR GREP_COLORS
26
27 acronyms=${ACRONYMDB:-/usr/share/misc/acronyms}
28
29 usage() {
30         print -u2 "usage: ${0##*/} [±AadKkPpwy] [-f dbfile] [is[t]] <acronym> [...]"
31         exit 1
32 }
33
34 # easier with nameref, but we support older mksh
35 function apush {
36         eval "$1[n$1++]=\$2"
37 }
38
39 use_acronyms=-1
40 use_dict=-1
41 use_mank=-1
42 use_ports=-1
43 use_whatis=-1
44 use_why=-1
45 hasopt=0
46 show_ver=0
47 while getopts "Aadf:hKkPpt:Vwy" ch; do
48         case $ch {
49         (+A)    hasopt=1 use_acronyms=0 use_dict=0 use_mank=0
50                 use_ports=0 use_whatis=0 use_why=0 ;;
51         (A)     hasopt=1 use_acronyms=1 use_dict=2 use_mank=2
52                 use_ports=2 use_whatis=1 use_why=1 ;;
53         (+a)    hasopt=1 use_acronyms=0 ;;
54         (a)     hasopt=1 use_acronyms=1 ;;
55         (+d)    hasopt=1 use_dict=0 ;;
56         (d)     hasopt=1 use_dict=1 ;;
57         (f)     acronyms=$OPTARG ;;
58         (+K)    hasopt=1 use_mank=0 ;;
59         (K)     hasopt=1 use_mank=2 ;;
60         (+k)    hasopt=1 use_mank=0 ;;
61         (k)     hasopt=1 use_mank=1 ;;
62         (+P)    hasopt=1 use_ports=0 ;;
63         (P)     hasopt=1 use_ports=2 ;;
64         (+p)    hasopt=1 use_ports=0 ;;
65         (p)     hasopt=1 use_ports=1 ;;
66         (t)     ;;
67         (V)     show_ver=1 ;;
68         (+w)    hasopt=1 use_whatis=0 ;;
69         (w)     hasopt=1 use_whatis=1 ;;
70         (+y)    hasopt=1 use_why=0 ;;
71         (y)     hasopt=1 use_why=1 ;;
72         (*)     usage ;;
73         }
74 done
75 shift $((OPTIND - 1))
76
77 if (( hasopt )); then
78         (( use_acronyms = (use_acronyms == -1) ? 0 : use_acronyms ))
79         (( use_dict = (use_dict == -1) ? 0 : use_dict ))
80         (( use_mank = (use_mank == -1) ? 0 : use_mank ))
81         (( use_ports = (use_ports == -1) ? 0 : use_ports ))
82         (( use_whatis = (use_whatis == -1) ? 0 : use_whatis ))
83         (( use_why = (use_why == -1) ? 0 : use_why ))
84 else
85         use_acronyms=1
86         use_dict=0
87         use_ports=0
88         use_mank=0
89         use_whatis=0
90         use_why=0
91 fi
92
93 if (( show_ver )); then
94         print -ru2 -- "$myver"
95         if (( use_acronyms )); then
96                 exec <"$acronyms"
97                 if ! IFS= read -r line || [[ $line != '  '* ]] || \
98                     ! IFS= read -r line || [[ $line != ' @(#)'* ]]; then
99                         print -ru2 "E: acronyms database ${acronyms@Q} too old"
100                         exit 1
101                 fi
102                 print -ru2 -- "${line# ????}"
103                 print -nu2 'Counting, please be patient…'
104                 last= nacr=0 nexp=0 lots=${EPOCHREALTIME%?????}
105                 while IFS= read -r line; do
106                         [[ $line = *'   '* ]] || continue
107                         let ++nexp
108                         line=${line%%   *}
109                         [[ $line = "$last" ]] || let ++nacr
110                         last=$line
111                         [[ $lots = ${EPOCHREALTIME%?????} ]] && continue
112                         print -nu2 \\rwtf knows at least $nacr acronyms with $nexp expansions
113                         lots=${EPOCHREALTIME%?????}
114                 done
115                 print -u2 \\rwtf currently knows about $nacr acronyms with $nexp expansions
116         fi
117         exit 0
118 fi
119
120 (( $# > 1 )) && [[ $1 = is?(t) ]] && shift
121 (( $# < 1 )) && usage
122
123 if (( use_ports )); then
124         if [[ -s /usr/ports/INDEX ]]; then
125                 # MirPorts Framework, OpenBSD ports tree
126                 binpkgs=ports
127                 function ports_acquire_filtered {
128                         local a b c d e
129                         local -l x y=$1
130
131                         while IFS='|' read a b c d e; do
132                                 x=$a
133                                 [[ $x = *"$y"* ]] && \
134                                     print -r -- "$a|${d%% \(uses*}"
135                         done </usr/ports/INDEX
136                 }
137                 function ports_acquire_unfiltered {
138                         local a b c d e
139                         local -l x y=$1
140
141                         while IFS='|' read a b c d e; do
142                                 x=$a$d
143                                 [[ $x = *"$y"* ]] && \
144                                     print -r -- "$a|${d%% \(uses*}"
145                         done </usr/ports/INDEX
146                 }
147         elif command -v yum >/dev/null; then
148                 # Red Hat Yellowdog Updater Modified
149                 binpkgs=RPMs
150                 function ports_acquire_filtered {
151                         local -l x y=$1
152
153                         yum search -q -- "$1" | \
154                             tr '\n' '\ 1' | sed 's/\ 1 *: / /g' | tr '\ 1' '\n' | \
155                             while read a b c; do
156                                 x=$a
157                                 [[ $x = *"$y"* ]] && print -r -- "$a|$c"
158                         done
159                 }
160                 function ports_acquire_unfiltered {
161                         yum search -q -- "$1" | \
162                             tr '\n' '\ 1' | sed 's/\ 1 *: / /g' | tr '\ 1' '\n' | \
163                             while read a b c; do
164                                 print -r -- "$a|$c"
165                         done
166                 }
167         elif command -v apt-cache >/dev/null; then
168                 # Debian Advanced Packaging Tool
169                 binpkgs=packages
170                 function ports_acquire_filtered {
171                         local -l x y=$1
172
173                         apt-cache search -- "$1" | while read a b c; do
174                                 x=$a
175                                 [[ $x = *"$y"* ]] && print -r -- "$a|$c"
176                         done
177                 }
178                 function ports_acquire_unfiltered {
179                         apt-cache search -- "$1" | while read a b c; do
180                                 print -r -- "$a|$c"
181                         done
182                 }
183         else
184                 use_ports=0
185         fi
186         case $use_ports {
187         (1)     alias ports_acquire=ports_acquire_filtered
188                 pkgmatch='basename' ;;
189         (2)     alias ports_acquire=ports_acquire_unfiltered
190                 pkgmatch='name or description' ;;
191         (*)     use_ports=0 ;;
192         }
193 fi
194
195 function mank_acquire_filtered {
196         local -l x y=$1
197
198         man -k -- "$1" | while IFS= read -r line; do
199                 x=${line%%')'*}
200                 [[ $x = *"$y"* ]] && print -r -- "$line"
201         done
202 }
203 function mank_acquire_unfiltered {
204         man -k -- "$1"
205 }
206 function mansort {
207         local l a b c
208         local -L24 x
209
210         while IFS= read -r l; do
211                 if [[ $l != +([!\(\)])'('+([!\)])')'*([  ])'-'* ]]; then
212                         print -r -- "~\ 1$l"
213                         continue
214                 fi
215                 a=${l%%')'*}
216                 c=${l#*')'}
217                 c=${c##*([       ])-*([  ])}
218                 b=${a##*'('}
219                 a=${a%'('*}
220                 a=${a%%*([       ])}
221                 a="$a ($b)"
222                 x=$a
223                 if [[ $x = "$a"+( ) ]]; then
224                         a=$x
225                 else
226                         a+=' '
227                 fi
228                 print -r -- " ${b}\ 1${a}- $c"
229         done | sort -u | sed 's/^[^\ 1]*\ 1//'
230 }
231 case $use_mank {
232 (1)     alias mank_acquire=mank_acquire_filtered
233         manmatch='manpage name or alias' ;;
234 (2)     alias mank_acquire=mank_acquire_unfiltered
235         manmatch='names or description' ;;
236 (*)     use_mank=0 ;;
237 }
238
239 if (( use_why )); then
240         if command -v dpkg-query >/dev/null; then
241                 # Debian packages
242                 function do_why {
243                         set -o noglob
244                         local pkgname n pre dep rec sug enh
245                         local DEP REC SUG ENH
246                         local -i nDEP nREC nSUG nENH
247
248                         for pkgname in $(dpkg-query -W \
249                             --showformat='${Package}\n' "$@" | \
250                             sed 's/:.*$//' | sort -u); do
251                                 set -A DEP; nDEP=0
252                                 set -A REC; nREC=0
253                                 set -A SUG; nSUG=0
254                                 set -A ENH; nENH=0
255                                 dpkg-query -W --showformat='${Package}\ 3${Pre-Depends}\ 3${Depends}\ 3${Recommends}\ 3${Suggests}\n' | \
256                                     grep -Fw -- "$pkgname" | \
257                                     sed -e 's/ //g' -e 's/([^)]*)//g' | \
258                                     sort -u |&
259                                 while IFS='\ 3' read -pr n pre dep rec sug; do
260                                         do_why1r "$pkgname" "$n" DEP "$pre"
261                                         do_why1r "$pkgname" "$n" DEP "$dep"
262                                         do_why1r "$pkgname" "$n" REC "$rec"
263                                         do_why1r "$pkgname" "$n" SUG "$sug"
264                                 done
265                                 for enh in $(dpkg-query -W \
266                                     --showformat='${Enhances}\n' "$pkgname" | \
267                                     sed -e 's/ //g' -e 's/([^)]*)//g' | \
268                                     tr ',|' '\n\n' | sort -u); do
269                                         apush ENH "${enh%%':'*} ="
270                                 done
271                                 n=\ 1
272                                 (( nDEP )) && do_why1p "$pkgname" Dependencies on "${DEP[@]}"
273                                 (( nREC )) && do_why1p "$pkgname" Recommends on "${REC[@]}"
274                                 (( nSUG )) && do_why1p "$pkgname" Suggests on "${SUG[@]}"
275                                 (( nENH )) && do_why1p "$pkgname" Enhanced by "${ENH[@]}"
276                         done
277                         set +o noglob  # for old mksh versions
278                 }
279                 function do_why1r {
280                         local f x y z
281
282                         IFS=,
283                         set -A y -- $4
284                         IFS=$' \t\n'
285                         for x in "${y[@]}"; do
286                                 if [[ $x = *'|'* ]]; then
287                                         IFS='|'
288                                         set -A z -- $x
289                                         IFS=$' \t\n'
290                                         f=0
291                                         for x in "${z[@]}"; do
292                                                 if [[ ${x%%':'*} = "$1" ]]; then
293                                                         (( f |= 1 ))
294                                                 else
295                                                         (( f |= 2 ))
296                                                 fi
297                                         done
298                                         if (( f == 3 )); then
299                                                 apush "$3" "${2%%':'*} |"
300                                                 return
301                                         elif (( f == 1 )); then
302                                                 apush "$3" "${2%%':'*} ="
303                                                 return
304                                         fi
305                                 elif [[ ${x%%':'*} = "$1" ]]; then
306                                         apush "$3" "${2%%':'*} ="
307                                         return
308                                 fi
309                         done
310                 }
311                 function do_why_pkgfmt {
312                         print -r -- "$2$(dpkg-query -W "$1" | tr '\n' \ 2 | sed \
313                             -e 's/\t\([^\ 2]*\)\ 2/ (= \1), /g' \
314                             -e 's/ (= )/ [no version]/g' \
315                             -e 's/, $//')$3"
316                 }
317                 function do_why1p {
318                         local h=$(do_why_pkgfmt "$1" "  - $2 $3 “" "”:")$'\n'
319                         shift 3
320                         local x y z
321
322                         for x in "$@"; do
323                                 print -r -- "$x"
324                         done | sort -u |&
325                         while IFS= read -pr y; do
326                                 z=${y:(-1)}
327                                 y=${y% ?}
328                                 # checks for doublettes; order important:
329                                 # - dep before rec before sug before enh
330                                 # - ‘=’ before ‘|’
331                                 [[ $n = *"\ 1${y}\ 1"* ]] && continue
332                                 n+="${y}\ 1"
333                                 [[ $z = '|' ]] || z=' '
334                                 do_why_pkgfmt "$y" "$h$z"
335                                 h=
336                         done
337                 }
338         else
339                 use_why=0
340         fi
341 fi
342
343 if (( use_acronyms )); then
344         # read case-folding code
345         if ! IFS= read -r line <"$acronyms" || [[ $line != '  '* ]]; then
346                 print -ru2 "E: acronyms database ${acronyms@Q} too old"
347                 exit 1
348         fi
349         set -A ucsrch -- $line
350
351         # create sorted input array, uppercased/folded
352         s='set -sA stsrch --'
353         i=0
354         # now: "$@"=("$0" foo bar baz)
355         for target in "$@"; do
356                 typeset -u tgsrch=$target
357                 [[ $tgsrch = *[A-Z].* || $tgsrch = .*[!.-]* ]] && \
358                     tgsrch=${tgsrch//.}
359                 for p in "${ucsrch[@]}"; do
360                         eval 'tgsrch=${tgsrch//'"$p}"
361                 done
362                 s+=" ${tgsrch@Q}=$((++i))"
363         done
364         eval "$s"
365         # now: stsrch=(BAR=2 BAZ=3 FOO=1)
366
367         # create output mapping, remove mapping number from stsrch
368         set -A omsrch
369         tgsrch=
370         i=0 n=-1
371         for s in "${stsrch[@]}"; do
372                 p=${s%=*}
373                 if [[ $p = $tgsrch ]]; then
374                         # this is a repeat
375                         unset stsrch[i++]
376                 else
377                         stsrch[i++]=$p
378                         tgsrch=$p
379                         let ++n
380                 fi
381                 (( omsrch[${s##*=}] = n ))
382         done
383         set -A stsrch -- "${stsrch[@]}"
384         # now: stsrch=(BAR BAZ FOO) omsrch[1]=2 omsrch[2]=0 omsrch[3]=1
385
386         # shorten search time
387         set -A grepcmd
388         i=-1
389         for s in "${stsrch[@]}"; do
390                 grepcmd[++i]=-e
391                 grepcmd[++i]=${s@/[\\.\[\^\$\*]/\\$KSH_MATCH}$'\t'
392         done
393         grep "${grepcmd[@]}" <"$acronyms" |&
394         unset grepcmd
395
396         # look up acronyms
397         set -A acrout
398         i=-1
399         for s in "${stsrch[@]}"; do
400                 let ++i
401                 while :; do
402                         if [[ $line = "$s       "* ]]; then
403                                 acrout[i]+=$'\n'${line#*        }
404                         elif [[ $line > "$s     " ]]; then
405                                 continue 2
406                         fi
407                         if ! IFS= read -pr line; then
408                                 i=-1
409                                 break 2
410                         fi
411                 done
412         done
413         # avoid SIGPIPE
414         (( i == -1 )) || read -prN-1 line
415         line=
416
417         i=0
418 fi
419
420 rv=0
421 for target in "$@"; do
422         if (( use_ports )); then
423                 p=$(ports_acquire "$target")
424                 if [[ -n $p ]]; then
425                         print -r "  - $binpkgs matching “$target” in $pkgmatch:"
426                         print -r -- "$p" | sort -u | column -ts'|'
427                 fi
428         fi
429
430         (( use_why )) && do_why "$target"
431
432         if (( use_mank )); then
433                 p=$(mank_acquire "$target")
434                 if [[ -n $p ]]; then
435                         print -r "  - manual pages matching “$target” in $manmatch:"
436                         print -r -- "$p" | mansort
437                 fi
438         fi
439
440         if (( use_acronyms )); then
441                 n=${omsrch[++i]}
442                 s=${acrout[n]}
443                 tgsrch=${stsrch[n]}
444                 if [[ -n $s ]]; then
445                         print -r -- "   $tgsrch:$s"
446                 else
447                         print -ru2 Gee… I don’t know what “"$tgsrch"” means…
448                         (( rv |= 1 ))
449                 fi
450         fi
451
452         (( use_dict || use_whatis )) && print "  - other information sources"
453
454         (( use_dict )) && if command -v dict >/dev/null; then
455                 dict -- "$target" || (( rv |= 2 ))
456         else
457                 (( use_dict == 2 )) || (( rv |= 4 ))
458                 (( use_whatis )) || print -r "Oops, no dict client found."
459         fi
460
461         if (( use_whatis )); then
462                 w=$(man -f -- "$target" | mansort) || \
463                     w="Oops, no manual page found for “$target”."
464                 print -r -- "$w"
465         fi
466 done
467 exit $rv