pacmenu 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/bin/bash
  2. #~ set -xv
  3. cache_dir="/var/cache/pacmenu"
  4. config="$HOME/.config/pacmenu/config"
  5. update=0
  6. menu=0
  7. update_function="update1"
  8. # the default update function, checking for ALL $PATH, pretty-formatting
  9. # for longest package name, is update1().
  10. # update2() is a different way to do this, and not yet functional.
  11. # https://bbs.archlinux.org/viewtopic.php?pid=1661072#p1661072
  12. # disadvantages: only checks for /usr/bin, static pretty-formatting
  13. # (tab at 37 chars, that's the longest package name on my system)
  14. # advantages: much more concise, presumably faster.
  15. version="0.1.1"
  16. lines=22
  17. launch_cmd="$(which dmenu)"
  18. [[ "x$launch_cmd" == "x" ]] && usage "dmenu not found in \$PATH."
  19. launch_cmd="$launch_cmd -l $lines -p "
  20. expac_cmd="$(which expac)"
  21. [[ "x$expac_cmd" == "x" ]] && { echo "expac not found in \$PATH. Install expac, or edit this script."; exit 1; }
  22. terminal_cmd="$XTERMINAL"
  23. # if the env-var XTERMINAL is defined, use that.
  24. ######################### FUNCTIONS #############################
  25. usage() {
  26. [[ "x$1" != "x" ]] && echo -e "$1"
  27. echo "
  28. Usage: pacmenu [-m] [-d dir] [-l launcher] [-c config] [-t term] [-h] [-v]
  29. lists installed packages containing executables in \$PATH, with description.
  30. Choosing a package presents a list of these executables, to choose & execute.
  31. -m Show the menu. This is the default if no options are given.
  32. -u Update cache. Won't show the menu unless specified with -m.
  33. -d Cache directory. Applies to -m and -u options. In it, the files packages
  34. and paths are created. Defaults to $cache_dir.
  35. -l Launcher command. Defaults to $launch_cmd.
  36. -c Read config file. Defaults to ~/.config/pacmenu/config. Sourced by bash.
  37. Command line options override config options.
  38. -t Terminal emulator for final command. Not given, the script uses \$TERMINAL,
  39. if defined. To force direct execution w/o terminal, supply an empty string."
  40. exit 1
  41. }
  42. update1() {
  43. mkdir -p "$cache_dir"
  44. # directory not writeable? abort!
  45. [[ ! -w "$cache_dir" ]] && usage "Directory $cache_dir not writeable!"
  46. [[ -e "$pathsfile" ]] && [[ ! -w "$pathsfile" ]] && usage "File $pathsfile cannot be overwritten!"
  47. paths="${PATH:1}"
  48. paths="${paths//:\//|}"
  49. echo "$paths" > "$pathsfile.tmp"
  50. [[ -e "$pkgfile" ]] && [[ ! -w "$pkgfile" ]] && usage "File $pkgfile cannot be overwritten!"
  51. #~ array_bin=($(cd /var/lib/pacman/local/ && /usr/bin/grep -lsE "$paths" */files))
  52. # list of packages that install files in /usr/bin (assuming that directory names
  53. # in /var/lib/pacman/local always start with package names)
  54. # each array element still contains trailing cruft, but
  55. # it starts with the packagename, which is what we need
  56. oldifs="$IFS"
  57. IFS=$'\n'
  58. array=($($expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" -v paths="$paths" '$0 ~ paths { printf "%s %s\n", $1, $2; }'))
  59. # an array of packages' names & descriptions, that have executables in $PATH
  60. IFS="$oldifs"
  61. #~ count=0
  62. #~ countarraycount=0
  63. # the countarray will hold indices of packages that install files in /usr/bin
  64. longest=0 # used for pretty-formatting (dmenu can't handle tabs?)
  65. for (( count=0; count < ${#array[*]}; count++ )); do
  66. length="${array[count]%% *}"
  67. length="${#length}"
  68. (( length > longest )) && longest=$length
  69. done
  70. # 1st loop: loop through list of packages that install files in /usr/bin
  71. #~ while [[ "${array_bin[countarraycount]}" != "" ]]
  72. #~ do
  73. #~ if [[ "${array_bin[countarraycount]%%/*}" =~ "${array[count]%% *}" ]]
  74. #~ # if the packagename equals one from the array of ALL packages...
  75. #~ then
  76. #~ countarray[((countarraycount++))]=$count
  77. #~ # ... then add the index to the countarray
  78. #~ # while we're at it, get the length of longest package for later pretty-formatting
  79. #~ length="${array[count]%% *}"
  80. #~ length="${#length}"
  81. #~ (( length > longest )) && longest=$length
  82. #~ fi
  83. #~ ((count++))
  84. #~ done
  85. #~ unset array_bin
  86. # 2nd loop: pretty-formatting and write to $pkgfile
  87. for (( count=0; count < ${#array[*]}; count++ ))
  88. do
  89. pkgname="${array[count]%% *}"
  90. description="${array[count]#* }"
  91. length=$((longest - ${#pkgname}))
  92. for (( j=0 ; j<length ; j++ ))
  93. do
  94. pkgname="$pkgname "
  95. done
  96. echo "$pkgname $description"
  97. done > "$pkgfile"
  98. mv -f "$pathsfile.tmp" "$pathsfile"
  99. }
  100. # doesn't work, but worth investigating
  101. # see https://bbs.archlinux.org/viewtopic.php?pid=1661072#p1661072
  102. update2() {
  103. paths="${PATH:1}"
  104. paths="${paths//:\//|}"
  105. echo "$paths" > "$pathsfile"
  106. paths="${paths//\//\\/}"
  107. echo "$paths"
  108. $expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" '/ usr\/bin\/[^$]/ { printf "%-37s %s\n", $1, $2; }' > "$pkgfile"
  109. #~ $expac_cmd -Q '%n\t%d\t%F' | awk -F"\t" '/ "$paths"[^$]/ { printf "%-37s %s\n", $1, $2; }' > "$pkgfile"
  110. }
  111. menu() {
  112. [[ ! -r "$pkgfile" ]] && usage "File $pkgfile is not readable!"
  113. choice="$($launch_cmd -i <"$pkgfile")"
  114. choice="${choice%% *}"
  115. if [[ "x$choice" != "x" ]]
  116. then
  117. choice=($($expac_cmd -l'\n' '%F' "$choice"))
  118. # an array of all files that are installed by the chosen package
  119. # (the variable choice jumps from being a normal variable, then an array,
  120. # and back to a normal var again. it seems bash can take it.)
  121. paths="$(<"$pathsfile")"
  122. # read back paths from file (so that we get the $PATH of the user that
  123. # created the menu file)
  124. # check all files from the array whether they're in $paths AND executable.
  125. # the next step (solved with a case/esac) is to filter out subdirectories
  126. # (yes, some packages have executables & subdirectories mixed all together)
  127. # and empty results, then re-attach the leading /
  128. choice="$(for (( x=0 ; x< ${#choice[*]} ; x++ ))
  129. do
  130. for i in ${paths//|/ }
  131. do
  132. if [[ "${choice[x]}" == "$i"* ]] && [ -x "/${choice[x]}" ]
  133. then
  134. case "${choice[x]/$i\//}" in
  135. *\/*|'') true
  136. ;;
  137. *) echo /${choice[x]}
  138. ;;
  139. esac
  140. fi
  141. done
  142. done | $launch_cmd)"
  143. echo "$choice"
  144. # if the user chose something, execute it either in terminal or directly.
  145. if [[ "x$choice" != "x" ]]
  146. then
  147. if [[ "x$terminal_cmd" != "x" ]]
  148. then
  149. if [[ "$choice" == *\; ]]
  150. then
  151. choice="${choice::-1}"
  152. if [[ "$choice" == *\; ]]
  153. then
  154. choice="$choice$SHELL"
  155. fi
  156. exec $terminal_cmd -e sh -c "$choice" & disown
  157. else
  158. exec "$choice" & disown
  159. fi
  160. else
  161. exec "$choice" & disown
  162. fi
  163. fi
  164. fi
  165. }
  166. ######################### MAIN ######################################
  167. [[ -f "$config" ]] && { . "$config" || usage "Problem sourcing $config"; }
  168. while getopts "mud:l:c:t:hv" opt; do
  169. case "$opt" in
  170. m) menu=1
  171. ;;
  172. u) update=1
  173. #~ case "$OPTARG" in
  174. #~ 1|2) update_function="update$OPTARG"
  175. #~ ;;
  176. #~ *) usage "Please choose Update method 1 or 2"
  177. #~ ;;
  178. #~ esac
  179. ;;
  180. d) cache_dir="$OPTARG"
  181. ;;
  182. l) launch_cmd="$OPTARG"
  183. ;;
  184. c) config="$OPTARG"
  185. [[ ! -r "$config" ]] && usage "Config file $config is not readable!"
  186. ;;
  187. t) terminal_cmd="$OPTARG"
  188. ;;
  189. v) echo "Version: $version"; exit 0
  190. ;;
  191. *) usage
  192. ;;
  193. esac
  194. done
  195. pkgfile="$cache_dir/packages"
  196. pathsfile="$cache_dir/paths"
  197. # default action is to show the menu.
  198. [[ "$update" == "0" ]] && menu=1
  199. if [[ "$menu" == "0" ]]
  200. then
  201. [[ "$update" == "1" ]] && "$update_function" &
  202. else
  203. [[ "$update" == "1" ]] && "$update_function"
  204. [[ "$menu" == "1" ]] && menu
  205. fi
  206. exit 0