Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 800 lines (654 sloc) 19.2 KB
#!/bin/bash
UA="Spinach"
VE="0.5.15"
# urlencode "string1" "string2" ...
# for i in "${urlencode_results[@]}"; do ... ; done
urlencode() {
local s c o
urlencode_results=()
for s; do
o=""
for (( i=0; i < ${#s}; ++i )); do
c="${s:$i:1}"
case "$c" in
[-._~:,A-Za-z0-9]) o+="$c" ;;
*) printf -v e "%%%02x" "'$c"; o+="$e" ;;
esac
done
urlencode_results+=("$o")
done
}
# get p url out.tar.gz
# get r info name
# get r search name
# get rm name1 name2 name3 name4
# curl $(get rp type arg)
get() {
local str out rpc1 rpc2 url
local print=0
local multi=0
local host="aur.archlinux.org"
local rpc="/rpc.php?type=%s&arg=%s"
local multiinfo="/rpc.php?type=multiinfo" #&arg[]=&arg[]=...
case "$1" in
p) str="$2%s%s"; out="$3";;
r) str="$rpc"; out="-"; rpc1="$2"; rpc2="$3";;
rp) str="$rpc"; out="-"; rpc1="$2"; rpc2="$3"; print=1;;
rm) multi=1; print=1;;
*) return 1;
esac
if (( multi )); then
shift # get rid of "rm" option
urlencode "$@"
local args="$(printf "&arg[]=%s" "${urlencode_results[@]}")"
echo "$(printf "https://%s${multiinfo}%s" "$host" "$args")"
else
urlencode "$rpc2"
url="$(printf "https://%s$str" "$host" "$rpc1" "${urlencode_results[0]}")"
((print)) && echo "$url" || curl -sfA "$UA" --compressed "$url" -o "$out"
fi
return $?
}
# msg e error
# msg n notice
# msg w warning
# msg c prefix message
msg() {
local txt="$2"
local prefix=""
case "$1" in
e) prefix="Error";;
n) prefix="Notice";;
w) prefix="Warning";;
c) prefix="$2"; txt="$3";;
*) return 1;;
esac
printf "${Color}%s${NoColor}: %s\n" "$prefix" "$txt" >&2
return 0
}
# set_color
set_color() {
local red=$(tput setaf 1 2>/dev/null)
local green=$(tput setaf 2 2>/dev/null)
local blue=$(tput setaf 4 2>/dev/null)
local nc=$(tput sgr0 2>/dev/null)
if (( UseColor )); then
Color="$red"
UpdateOld="$blue"
UpdateNew="$green"
NoColor="$nc"
else
unset Color NoColor UpdateOld UpdateNew
fi
return 0
}
# command | color
color() {
[[ $Color ]] && echo -e "$(cat | sed -r 's/^([^: \t]+[^:]*): +/\'$Color'\1\'$NoColor': /g')" || cat
return 0
}
# ask "really?" || break
ask() {
local response
local question="$1"
while [[ $response != y && $response != n ]]; do
printf "${Color}y/n${NoColor}: %s " "$question" >&2
read -r response
done
[[ $response == y ]] && return 0
[[ $response == n ]] && return 1
}
# preview name
preview() {
local name="$1"
local question=${2:-View}
(( Noconfirm )) && return 0
if ask "${question} ${name}?"; then
"$EDITOR" "PKGBUILD"
if grep '^install=' "PKGBUILD" &>/dev/null; then
install_file="$(grep '^install=' "PKGBUILD" | cut -d= -f2 | tr -d "'\"")"
if [[ -e $install_file ]]; then
"$EDITOR" "$install_file"
elif ls *.install &>/dev/null; then
msg w "Couldn't determine install file, opening: $(ls *.install)"
"$EDITOR" *.install
else
msg w "Install file specified and couldn't find it. Look in:"
msg c "..." "$(pwd)"
fi
fi
fi
ask "Install $name?"
return $?
}
# run_pacman -S something
run_pacman() {
while true; do
if type sudo &>/dev/null; then
msg c "Running" "sudo pacman $*"
sudo pacman "$@"
else
msg c "Running" "su root -c pacman $*"
su root -c pacman "$@"
fi
(( $? == 0 )) && return 0
ask "Try again?" || return 1
done
}
# search v name
# search q name
search() {
local repo
local verbosity="$1"
local query="$2"
if [[ $verbosity == q ]]; then
(( UseRepos )) && expac "%n" -Ss "$query"
get r search "$query" | jshon -QC -e results -a -e Name -u
else
(( UseRepos )) && repo="$(expac "%r/%n: %d" -Ss "$query")"
[[ -n $repo ]] && color <<< "$repo"
get r search "$query" | \
jshon -QC -e results -a -e Name -u -p -e OutOfDate -u -p -e Description -u | \
paste -s -d " :\n" | sed -r '
s/^([^:]*) null:/\1: /g
s/^([^:]*) [0-9]+:/\1 *: /g
' | sort | color
fi
return 0
}
# info query
info() {
local info gotinfo
local query="$1"
if (( UseRepos )); then
info="$(pacman -Si "$query" 2>/dev/null)"
gotinfo=$?
[[ $info ]] && color <<< "$info"
fi
if ! (( UseRepos )) || (( gotinfo )); then
get r info "$query" | jshon -QC -e results | \
tail -n +2 | head -n -1 | sed -r '
s#\\/#/#g
s/^ "//g
s/": "?/: /g
s/"?,?$//g
s/^URLPath: /URLPath: https:\/\/aur.archlinux.org/g' | color
fi
return 0
}
# get_pkg name [ignore]
get_pkg() {
local url
local result=0
local name="$1"
local ignore=$2
url="$(get r info "$name" | jshon -QC -e results -e URLPath -u)"
[[ -z $ignore ]] && ignore=0
if [[ -e $name ]]; then
if (( ignore )); then
return 0
else
msg e "$name/ exists"
return 1
fi
fi
if [[ -n $Custom && -e $Custom/$name/PKGBUILD ]]; then
msg n "Using PKGBUILD from $Custom"
cp -r "$Custom/$name" ./
result=$?
else
get p "$url" "$name.tar.gz"
tar xzf "$name.tar.gz" &>/dev/null
result=$?
rm "$name.tar.gz" &>/dev/null
fi
(( result == 0 )) && \
msg c "Saved" "$name/" || \
msg c "Not Saved" "$name/"
return $result
}
# get_all name
get_all() {
local dep
local result=0
local name="$1"
local explicit=$2
[[ -z $explicit ]] && explicit=1
if (( explicit )); then
[[ -e $name ]] && { msg n "already downloaded $name/"; return 1; }
get_pkg "$name" || { msg e "could not download $name"; return 1; }
else
get_pkg "$name" 1 || { msg e "could not download $name"; return 1; }
fi
Downloaded+=("$name")
(( explicit )) && \
InstallExplicit+=("$name") || \
InstallDepends+=("$name")
[[ -e $name/PKGBUILD ]] || { msg e "$name/PKGBUILD does not exist"; return 1; }
grep sudo "$name/PKGBUILD" &>/dev/null && \
msg w "The characters 'sudo' are in the PKGBUILD."
grep sudo "$name/"*.install &>/dev/null && \
msg w "The characters 'sudo' are in the install file."
local finished=0;
while ! (( finished )) ; do
finished=1
local depends=()
local makedepends=()
local checkdepends=()
if [[ -e "$name"/.SRCINFO ]]; then
while read -r key _ value _; do
[[ $key == depends ]] && depends+=("$value")
[[ $key == makedepends ]] && makedepends+=("$value")
[[ $key == checkdepends ]] && checkdepends+=("$value")
[[ $key == '' ]] && break
done < "$name"/.SRCINFO
else
# e.g. when using a local repository which you probably trust anyway
if ask "No .SRCINFO, source $name/PKGBUILD?"; then
source <(tr '\n' ' ' < "$name/PKGBUILD" | grep -Eo ' (make|check)?depends=\([^)]*\)' | \
sed -r "s/(['\"])([^><='\"]*)[><=]+[^'\"]*\1/\1\2\1/g" | str_clean)
else
msg e "Must get depends from $name, inspect manually and rerun"
return 1
fi
fi
local deps="$(pacman -T ${depends[*]} ${makedepends[*]} | tr '\n' ' ')"
local provide=""
[[ -n $deps ]] && provide="$(expac "%n\n%S" -S $deps | tr ' ' '\n')"
for dep in $deps; do
grep -q "^$dep$" <<< "$provide" && InstallPacman+=("$dep") && continue
if ! get_all "$dep" 0; then
finished=0
if ask "Modify PKGBUILD for $name?"; then
"$EDITOR" "$name/PKGBUILD"
break 1
else
msg e "Must download dependencies."
(( explicit )) && \
unset InstallExplicit[$((${#InstallExplicit[@]}-1))] || \
unset InstallDepends[$((${#InstallDepends[@]}-1))]
return 1
fi
fi
done
done
}
# notfound
notfound() {
local thread_list=()
local packages="$(pacman -Qmq)"
trap "for t in \"\${thread_list[@]}\"; do kill \$t &>/dev/null; done; echo; exit 1" 0 2 15
for pkg in $packages; do
(( $(get r info "$pkg" | jshon -e resultcount) == 0 )) && msg c "Not found" "$pkg" &
thread_list+=("$!")
thread_wait $Threads
done
thread_wait
trap - 0 2 15
}
# thread_wait $Threads
thread_wait() {
local threads="$1"
[[ -z threads ]] && threads=0
while (( $(jobs|wc -l) > threads )); do
sleep 0.05
jobs &>/dev/null
done &>/dev/null
}
# while read -r pkg; do echo $pkg; done < <(updates)
updates() {
local pkg file info
local count=0
local threads=()
local kill_list=()
local tmp="$(mktemp -u)$$"
info="$(get rp info "")"
trap "kill \"\${kill_list[@]}\" &>/dev/null; rm \"$tmp\"_* &>/dev/null; exit 1" 0 2 15
# ignore list, note the space at the end
local ignorePkgs="$(grep ^IgnorePkg /etc/pacman.conf | sed -e "s/.*=\ //g") "
# get urls, and ignore packages in IgnoreList
local packages="$(pacman -Qmq | while read -r pkg; do
! grep "$pkg " <<< "$ignorePkgs" &>/dev/null && echo $pkg
done)"
for pkg in $packages; do
[[ -z ${threads[$count]} ]] && \
threads[$count]="$pkg" || \
threads[$count]+=" $pkg"
(( count == Threads-1 )) && count=0 || (( ++count ))
done
# create threads
count=0
for pkgs in "${threads[@]}"; do
{
curl -gsfA "$UA" --compressed "$(get rm $pkgs)" 2>/dev/null | \
jshon -QC -e results -a -e Name -u -p -e Version -u | \
paste -sd '\t\n'
} > "$tmp"_"$count" &
kill_list+=("$!")
(( ++count ))
done
# wait
thread_wait
# parse local versions
local pacman="$(expac "%n\n%v" $packages)"
# compare
while read -r pkg; do
local name="$(cut -f1 <<< "$pkg")"
local aur="$(cut -f2 <<< "$pkg")" # AUR version
local loc="$(grep -A 1 "^$name$" <<< "$pacman" | tail -n 1)" # local version
if [[ $aur != $loc && -n $loc ]] && (( $(vercmp "$aur" "$loc") > 0 )); then
echo "$name"
printf "${Color}%s${NoColor} (${UpdateOld}%s${NoColor} => ${UpdateNew}%s${NoColor})\n" \
"$name" "$loc" "$aur" >&2
fi
done < <(cat "$tmp"_* 2>/dev/null)
rm "$tmp"_*
trap - 0 2 15
}
# eval $(str_clean <<< input)
str_clean() {
cat | sed -r 's/\$\([^)]+\)//g
s/`[^`]+`//g
s/;.*//g'
}
# install_pkg "${packages[@]}"
install_pkg() {
editor || return 1
mk_tmp || return 1
cd "$Build"
local pkg
local installAur=()
local installRepo=()
Downloaded=()
InstallPacman=()
InstallDepends=()
InstallExplicit=()
trap "touch \"$Tmp/$KillSudov\"; clean; wait; rm \"$Tmp/$KillSudov\"; exit 1" 0
trap "touch \"$Tmp/$KillSudov\"; clean; wait; rm \"$Tmp/$KillSudov\"; echo; exit 1" 2 15
for pkg; do
if (( UseRepos )) && { expac "%n" -S1 "$pkg" || expac "%n" -Sg "$pkg"; } &>/dev/null; then
installRepo+=("$pkg")
else
if ! (( Needed )) || ! pacman -Qi "$pkg" &>/dev/null; then
installAur+=("$pkg")
fi
fi
done
if (( ${#installRepo[@]} > 0 )); then
(( Needed )) && run_pacman --needed -S "${installRepo[@]}" || \
run_pacman -S "${installRepo[@]}"
fi
build "${installAur[@]}" || return 1
touch "$Tmp/$KillSudov"
clean
wait
rm "$Tmp/$KillSudov"
trap - 0 2 15
}
# install_updates
install_updates() {
editor || return 1
mk_tmp || return 1
cd "$Build"
local update pkg
local packages=()
local upgrade_pkgs=()
Downloaded=()
InstallPacman=()
InstallDepends=()
InstallExplicit=()
[[ -e $Tmp/update.lock ]] && msg e "update lock exists in $Tmp/" && return 1
touch "$Tmp/update.lock"
trap "touch \"$Tmp/$KillSudov\"; clean; rm \"$Tmp/update.lock\" &>/dev/null; wait; rm \"$Tmp/$KillSudov\"; exit 1" 0
trap "touch \"$Tmp/$KillSudov\"; clean; rm \"$Tmp/update.lock\" &>/dev/null; wait; rm \"$Tmp/$KillSudov\"; echo; exit 1" 2 15
while read -r update; do
packages+=("$update")
done < <(updates)
for pkg in "${packages[@]}"; do
{ (( Noconfirm )) || ask "Update $pkg?"; } && upgrade_pkgs+=("$pkg")
done
build "${upgrade_pkgs[@]}" || return 1
rm "$Tmp/update.lock"
touch "$Tmp/$KillSudov"
clean
wait
rm "$Tmp/$KillSudov"
trap - 0 2 15
}
# build package(s) || return 1
build() {
local pkg i PKGEXT
for pkg; do
get_all "$pkg" || return 1
done
(( $# == 0 )) && return 0 # no packages to install
(( ${#InstallExplicit[@]} == 0 )) && return 1 # couldn't find packages
for pkg in "${InstallExplicit[@]}" "${InstallDepends[@]}"; do
cd "$pkg"
preview "$pkg" || { msg e "not building package. Exiting."; return 1; }
cd "$Build"
done
(( ${#InstallPacman[@]} > 0 )) && \
run_pacman --needed --asdeps -S "${InstallPacman[@]}"
if (( UseBatch )) && type sudo &>/dev/null; then
msg c "Running" "sudo -v"
if sudo -v; then
sudov &
else
msg n "Falling back to non-batch mode"
fi
fi
source <(grep ^PKGEXT= /etc/makepkg.conf)
[[ -z $PKGEXT ]] && PKGEXT='.pkg.tar.xz'
for (( i=${#InstallDepends[@]}-1; i >= 0; --i )); do
if ! mk_pkg "${InstallDepends[$i]}" "$PKGEXT" 1; then
msg e "must build depends. Exiting."
return 1
fi
done
for pkg in "${InstallExplicit[@]}"; do
mk_pkg "$pkg" "$PKGEXT" 0
done
}
# mk_pkg dir PKGEXT depends
mk_pkg() {
local i PKGEXT asdeps files
local pkg="$1"
local makepkg_pkgext="$2"
local is_depend=$3
(( is_depend )) && asdeps="--asdeps"
cd "$pkg"
while true; do
if makepkg; then
rm -rf src/ pkg/
break
else
if ! preview "$pkg" "Correct files for"; then
msg e "could not build $pkg"
cd "$Build"
return 1
fi
fi
done
source <(grep ^PKGEXT= PKGBUILD)
[[ -z $PKGEXT ]] && PKGEXT="$makepkg_pkgext"
shopt -s nullglob dotglob
files=("$Build/$pkg"/*$PKGEXT)
shopt -u nullglob dotglob
if (( ${#files[@]} > 0 )); then
if (( UseBatch )); then
run_pacman --noconfirm $asdeps -U "${files[@]}"
else
run_pacman $asdeps -U "${files[@]}"
fi
fi
for i in "${files[@]}"; do
mv "$i" "$Cache/"
done
cd "$Build"
}
# find_updates
find_updates() {
updates >/dev/null
}
# get_updates
get_updates() {
local update
while read -r update; do
get_pkg "$update"
done < <(updates)
}
# sudov &
sudov() {
while [[ ! -e $Tmp/$KillSudov ]]; do
sudo -n -v
sleep $Sleep
done
}
# clean
clean() {
local file
for file in "${Downloaded[@]}"; do
[[ -e $Build/$file ]] && rm -rf "$Build/$file" &>/dev/null
done
}
# editor || return 1
editor() {
local i
[[ -n $EDITOR ]] && return 0
for i in vim emacs vi nano most less more cat; do
if type "$i" &>/dev/null; then
EDITOR="$i"
msg n "No \$EDITOR, using '$i'"
break
fi
done
[[ -z $EDITOR ]] && msg e "set \$EDITOR" && return 1
return 0
}
# mk_tmp || return 1
mk_tmp() {
[[ -z $Temp ]] && Temp=${TMPDIR:-/tmp}
Tmp="$Temp/spinach"
Build="$Tmp/build"
Cache="$Tmp/cache"
if ! mkdir -p "$Tmp" "$Build" "$Cache" &>/dev/null; then
msg e "could not create $Tmp"
return 1
fi
return 0
}
blank() {
[[ -z $1 ]] && msg e "Please specify at least one package." && exit 1
}
chkop() {
[[ -n $Op ]] && msg e "You can only specify one operation." && exit 1
}
usage() {
cat <<EOF
Usage: spinach [options] [operation] [packages]
Options
--[no]col Use Color
--[no]rep Use repos
--[no]bat Use batch mode
--max 10 Set max threads
--edit vim Set \$EDITOR
--custom ./ Local AUR packages
--needed Only install new packages
--noconfirm No questions, just do it
Common Operations
-s[q] pkg Search
-p pkg Print info
-i pkg Install
-u Update
Do-it-yourself Operations
-d pkg Download
-dd pkg Download with depends
-o List updates
-do Download updates
--404 List packages not in AUR
Try \`man spinach' for more information.
EOF
return 0
}
unset Op Temp Custom
Threads=10
Sleep=2
Pkgargs=()
KillSudov="killsudov$$.lock"
Current_directory="$(pwd)"
Noconfirm=0
Needed=0
UseRepos=1
UseColor=1
UseBatch=1
# defaults from config
config=${XDG_CONFIG_HOME:-$HOME/.config}/spinach/config
configEtc=/etc/spinach.conf
if [[ -e $config ]]; then
source "$config"
elif [[ -e $configEtc ]]; then
source "$configEtc"
fi
while [[ -n $1 ]]; do
case "$1" in
# options
"--col") UseColor=1;;
"--nocol") UseColor=0;;
"--rep") UseRepos=1;;
"--norep") UseRepos=0;;
"--bat") UseBatch=1;;
"--nobat") UseBatch=0;;
"--tmp") shift; blank "$1"; Temp="$1";;
"--max") shift; blank "$1"; Threads="$1";;
"--edit") shift; blank "$1"; EDITOR="$1";;
"--custom") shift; blank "$1"; Custom="$1";;
"--nocustom") unset Custom;;
"--needed") Needed=1;;
"--noconfirm") Noconfirm=1;;
# common
"-s") chkop; Op="s";;
"-sq") chkop; Op="sq";;
"-i") chkop; Op="i";;
"-p") chkop; Op="p";;
"-u") chkop; Op="u";;
# diy
"-o") chkop; Op="o";;
"-d") chkop; Op="d";;
"-dd") chkop; Op="dd";;
"-do") chkop; Op="do";;
# other
"--404") chkop; Op="404";;
"--version") echo "$UA/$VE"; exit 0;;
"-h"|"--help") usage; exit 0;;
*) Pkgargs+=("$1");;
esac
shift
done
if [[ $Op ]]; then
# disable color if piped to another command
[[ ! -t 1 ]] && UseColor=0
set_color
# make absolute
[[ -n $Custom && ${Custom:0:1} != / ]] && Custom="$(pwd)/$Custom"
case "$Op" in
u) install_updates; exit 0;;
o) find_updates; exit 0;;
do) get_updates; exit 0;;
i) install_pkg "${Pkgargs[@]}"; exit 0;;
404) notfound; exit 0;;
esac
if (( ${#Pkgargs[@]} > 0 )); then
for pkg in "${Pkgargs[@]}"; do
case "$Op" in
s) search v "$pkg";;
sq) search q "$pkg";;
p) info "$pkg";;
d) get_pkg "$pkg";;
dd) get_all "$pkg";;
esac
done
else
blank
fi
fi
You can’t perform that action at this time.