123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- #!/bin/bash
- #~ set -xv
- cache_dir="/var/cache/pacmenu"
- config="$HOME/.config/pacmenu/config"
- update=0
- menu=0
- update_function="update1"
- # the default update function, checking for ALL $PATH, pretty-formatting
- # for longest package name, is update1().
- # update2() is a different way to do this, and not yet functional.
- # https://bbs.archlinux.org/viewtopic.php?pid=1661072#p1661072
- # disadvantages: only checks for /usr/bin, static pretty-formatting
- # (tab at 37 chars, that's the longest package name on my system)
- # advantages: much more concise, presumably faster.
- version="0.1.1"
- lines=22
- launch_cmd="$(which dmenu)"
- [[ "x$launch_cmd" == "x" ]] && usage "dmenu not found in \$PATH."
- launch_cmd="$launch_cmd -l $lines -p "
- expac_cmd="$(which expac)"
- [[ "x$expac_cmd" == "x" ]] && { echo "expac not found in \$PATH. Install expac, or edit this script."; exit 1; }
- terminal_cmd="$XTERMINAL"
- # if the env-var XTERMINAL is defined, use that.
- ######################### FUNCTIONS #############################
- usage() {
- [[ "x$1" != "x" ]] && echo -e "$1"
- echo "
- Usage: pacmenu [-m] [-d dir] [-l launcher] [-c config] [-t term] [-h] [-v]
- lists installed packages containing executables in \$PATH, with description.
- Choosing a package presents a list of these executables, to choose & execute.
- -m Show the menu. This is the default if no options are given.
- -u Update cache. Won't show the menu unless specified with -m.
- -d Cache directory. Applies to -m and -u options. In it, the files packages
- and paths are created. Defaults to $cache_dir.
- -l Launcher command. Defaults to $launch_cmd.
- -c Read config file. Defaults to ~/.config/pacmenu/config. Sourced by bash.
- Command line options override config options.
- -t Terminal emulator for final command. Not given, the script uses \$TERMINAL,
- if defined. To force direct execution w/o terminal, supply an empty string."
- exit 1
- }
- update1() {
- mkdir -p "$cache_dir"
- # directory not writeable? abort!
- [[ ! -w "$cache_dir" ]] && usage "Directory $cache_dir not writeable!"
- [[ -e "$pathsfile" ]] && [[ ! -w "$pathsfile" ]] && usage "File $pathsfile cannot be overwritten!"
- paths="${PATH:1}"
- paths="${paths//:\//|}"
- echo "$paths" > "$pathsfile.tmp"
- [[ -e "$pkgfile" ]] && [[ ! -w "$pkgfile" ]] && usage "File $pkgfile cannot be overwritten!"
- #~ array_bin=($(cd /var/lib/pacman/local/ && /usr/bin/grep -lsE "$paths" */files))
- # list of packages that install files in /usr/bin (assuming that directory names
- # in /var/lib/pacman/local always start with package names)
- # each array element still contains trailing cruft, but
- # it starts with the packagename, which is what we need
- oldifs="$IFS"
- IFS=$'\n'
- array=($($expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" -v paths="$paths" '$0 ~ paths { printf "%s %s\n", $1, $2; }'))
- # an array of packages' names & descriptions, that have executables in $PATH
- IFS="$oldifs"
- #~ count=0
- #~ countarraycount=0
- # the countarray will hold indices of packages that install files in /usr/bin
- longest=0 # used for pretty-formatting (dmenu can't handle tabs?)
- for (( count=0; count < ${#array[*]}; count++ )); do
- length="${array[count]%% *}"
- length="${#length}"
- (( length > longest )) && longest=$length
- done
- # 1st loop: loop through list of packages that install files in /usr/bin
- #~ while [[ "${array_bin[countarraycount]}" != "" ]]
- #~ do
- #~ if [[ "${array_bin[countarraycount]%%/*}" =~ "${array[count]%% *}" ]]
- #~ # if the packagename equals one from the array of ALL packages...
- #~ then
- #~ countarray[((countarraycount++))]=$count
- #~ # ... then add the index to the countarray
- #~ # while we're at it, get the length of longest package for later pretty-formatting
- #~ length="${array[count]%% *}"
- #~ length="${#length}"
- #~ (( length > longest )) && longest=$length
- #~ fi
- #~ ((count++))
- #~ done
- #~ unset array_bin
- # 2nd loop: pretty-formatting and write to $pkgfile
- for (( count=0; count < ${#array[*]}; count++ ))
- do
- pkgname="${array[count]%% *}"
- description="${array[count]#* }"
- length=$((longest - ${#pkgname}))
- for (( j=0 ; j<length ; j++ ))
- do
- pkgname="$pkgname "
- done
- echo "$pkgname $description"
- done > "$pkgfile"
- mv -f "$pathsfile.tmp" "$pathsfile"
- }
- # doesn't work, but worth investigating
- # see https://bbs.archlinux.org/viewtopic.php?pid=1661072#p1661072
- update2() {
- paths="${PATH:1}"
- paths="${paths//:\//|}"
- echo "$paths" > "$pathsfile"
- paths="${paths//\//\\/}"
- echo "$paths"
- $expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" '/ usr\/bin\/[^$]/ { printf "%-37s %s\n", $1, $2; }' > "$pkgfile"
- #~ $expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" '/ "$paths"[^$]/ { printf "%-37s %s\n", $1, $2; }' > "$pkgfile"
- }
- menu() {
- [[ ! -r "$pkgfile" ]] && usage "File $pkgfile is not readable!"
- choice="$($launch_cmd -i <"$pkgfile")"
- choice="${choice%% *}"
- if [[ "x$choice" != "x" ]]
- then
- choice=($($expac_cmd -l'\n' '%F' "$choice"))
- # an array of all files that are installed by the chosen package
- # (the variable choice jumps from being a normal variable, then an array,
- # and back to a normal var again. it seems bash can take it.)
- paths="$(<"$pathsfile")"
- # read back paths from file (so that we get the $PATH of the user that
- # created the menu file)
- # check all files from the array whether they're in $paths AND executable.
- # the next step (solved with a case/esac) is to filter out subdirectories
- # (yes, some packages have executables & subdirectories mixed all together)
- # and empty results, then re-attach the leading /
- choice="$(for (( x=0 ; x< ${#choice[*]} ; x++ ))
- do
- for i in ${paths//|/ }
- do
- if [[ "${choice[x]}" == "$i"* ]] && [ -x "/${choice[x]}" ]
- then
- case "${choice[x]/$i\//}" in
- *\/*|'') true
- ;;
- *) echo /${choice[x]}
- ;;
- esac
- fi
- done
- done | $launch_cmd)"
- echo "$choice"
- # if the user chose something, execute it either in terminal or directly.
- if [[ "x$choice" != "x" ]]
- then
- if [[ "x$terminal_cmd" != "x" ]]
- then
- if [[ "$choice" == *\; ]]
- then
- choice="${choice::-1}"
- if [[ "$choice" == *\; ]]
- then
- choice="$choice$SHELL"
- fi
- exec $terminal_cmd -e sh -c "$choice" & disown
- else
- exec "$choice" & disown
- fi
- else
- exec "$choice" & disown
- fi
- fi
- fi
- }
- ######################### MAIN ######################################
- [[ -f "$config" ]] && { . "$config" || usage "Problem sourcing $config"; }
- while getopts "mud:l:c:t:hv" opt; do
- case "$opt" in
- m) menu=1
- ;;
- u) update=1
- #~ case "$OPTARG" in
- #~ 1|2) update_function="update$OPTARG"
- #~ ;;
- #~ *) usage "Please choose Update method 1 or 2"
- #~ ;;
- #~ esac
- ;;
- d) cache_dir="$OPTARG"
- ;;
- l) launch_cmd="$OPTARG"
- ;;
- c) config="$OPTARG"
- [[ ! -r "$config" ]] && usage "Config file $config is not readable!"
- ;;
- t) terminal_cmd="$OPTARG"
- ;;
- v) echo "Version: $version"; exit 0
- ;;
- *) usage
- ;;
- esac
- done
- pkgfile="$cache_dir/packages"
- pathsfile="$cache_dir/paths"
- # default action is to show the menu.
- [[ "$update" == "0" ]] && menu=1
- if [[ "$menu" == "0" ]]
- then
- [[ "$update" == "1" ]] && "$update_function" &
- else
- [[ "$update" == "1" ]] && "$update_function"
- [[ "$menu" == "1" ]] && menu
- fi
- exit 0
|