Skip to content

Instantly share code, notes, and snippets.

@l3laze
Created August 27, 2017 21:33
Show Gist options
  • Save l3laze/ba63e3a9dafed50b9d28041a10cd4673 to your computer and use it in GitHub Desktop.
Save l3laze/ba63e3a9dafed50b9d28041a10cd4673 to your computer and use it in GitHub Desktop.
A little Bash "dependency manager" with download/install/cache powers. Beta at best.
#!/usr/bin/env bash
#
# lilman - A little "dependency manager" with download/cache/install powers.
#
# Follows the suggested exit code range of 0 & 64-113 from http://www.tldp.org/LDP/abs/html/exitcodes.html
#
# Note: This is beta at best; still trying to work out some problems and may actually rewrite this (again) completely.
# Note 2: Moved to a Gist because it doesn't need it's own repo as far as I'm concerned.
# Include source file and line number in xtrace messages
# From http://wiki.bash-hackers.org/scripting/debuggingtips#making_xtrace_more_useful
# export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# Enable xtrace for debugging.
# set -x
# Enable "exit on first error"
# set -e
function rtfm() {
echo -e "Usage:\\n\\tlilman [ -n <string> ]* [ -o <string> ]* [ -s <string>]* [ -t <string> ]* [ ... ]\\n\\n\
[ -c | --cacheloc <path> ]\\t\\tWhere to cache the dependency.\\n\
[ -d | --downloadloc <path> ]\\t\\tThe directory to git clone or download the dependency to.\\n\
[ -h | --help ]\\t\\t\\tDisplay this message.\\n\
[ -i | --installloc <path> ]\\t\\tThe directory to unpack/move the dependency to.\\n\
[ -l | --homeloc <path>]\\t\\tThe directory to use as lilman's home folder.\\n\
*[ -n | --name <string>]\\t\\tThe name of the dependency as it will be installed.\\n\
*[ -o | --downloadname <string>]\\tThe name of the downloaded file.\\n\
*[ -s | --source <URL | GIT URI>]\\tA URL to download from using 'curl' or a git URL to 'git clone'.\\n\
*[ -t | --type <URL | GIT>]\\t\\tThe dependency type, either 'URL' or 'GIT', case insensitive.\\n\
[ -m | --mode <args>]\\t\\t\\tMode flags to change operation. Options are:\\n\\t\\t\
[ d ]\\t\\t-\\tDon't delete download after installing/caching; keep it.\\n\\t\\t\
[ q ]\\t\\t-\\tQuiet mode.\\n\\t\\t\
[ r ]\\t\\t-\\tRenew mode; ignore cache, download a new copy of dependency.\\n\\t\\t\
[ t ]\\t\\t-\\tTest mode -- runs internal test suite.\\n\\n\
[ u ]\\t\\t-\\tUnpack mode; if the download is archived, unpack it to install.\\n\\t\\t\
Where * = Required except for \"test mode\" and when using -h || --help, in which case other args are ignored.\\n"
}
function run_script() {
local lmhomedef
local lmcachedef
local lmdownloadsdef
local lminstalldef
local lmhome
local lmcache
local lmdownloads
local lminstall
local lmispacked
local lmunpack
local lmkeepdl
local lmrenew
local lmquiet
local logfile
local key
lmhomedef=~/Desktop/lil-man
lmcachedef=$lmhomedef/cache
lmdownloadsdef=$lmhomedef/downloads
lminstalldef=$lmhomedef/install
lmispacked=false
lmunpack=false
lmkeepdl=false
lmrenew=false
lmquiet=false
logfile=$lmhomedef/log.txt
#-------------------------
#
# Parse arguments.
#
if [[ $# -eq 0 ]]; then
# Display usage..
echo "Error! -- no args!"
rtfm
exit 64
else
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-c | --cacheloc)
lmcache="$2"
shift
;;
-d | --downloadloc)
lmdownloads="$2"
shift
;;
-i | --installloc)
lminstall="$2"
shift
;;
-l | --homeloc)
lmhome="$2"
shift
;;
-h | --help)
rtfm
exit 64;
;;
-n | --name)
lmname="$2"
shift
;;
-s | --source)
lmsource="$2"
shift
;;
-t | --type)
lmsrctype=$(echo "$2" | tr '[:upper:]' '[:lower:]')
case $lmsrctype in
git)
;;
url)
;;
*)
echo -e "\\n\\nError! Invalid dependency type: $2"
echo -e "Valid dependency types are 'GIT' and 'URL', case insensitive.\\n\\n"
exit 65;
esac
shift
;;
-o | --downloadname)
lmdlname="$2"
shift
;;
-m | --mode)
key2=$(echo "$2" | tr '[:upper:]' '[:lower:]')
if [[ "$key2" == *"d"* ]]; then
lmkeepdl=true
fi
if [[ "$key2" == *"r"* ]]; then
lmrenew=true
fi
if [[ "$key2" == *"q"* ]]; then
lmquiet=true
fi
if [[ "$key2" == *"u"* ]]; then
lmunpack=true
fi
if [[ "$key2" == *"t"* ]]; then
lmtest=true
fi
shift
;;
*)
clear
echo -e "\\n\\nError! Unknown option: $1\\n\\n"
rtfm
exit 64;
;;
esac
shift # To get past the -o/-option.
done
if [[ "$lmtest" == true ]]; then
echo -e "Mode = \"Test\"."
do_tests
exit 0
fi
if [[ -z "$lmname" || "$lmname" == "" ]]; then
echo "You must set the name of the package."
exit 66;
fi
if [[ -z "$lmdlname" || "$lmdlname" == "" ]]; then
echo "You must set a download name to get the package $lmname."
exit 67;
else
[[ "$lmquiet" == false && ! -z "$lmpkdtype" ]] && echo "Checking if package $lmdlname is archived..."
if [[ "$lmdlname" == *".tar.gz"* ]]; then
lmispacked=true
lmpkdtype=".tar.gz"
elif [[ "$lmdlname" == *".zip"* ]]; then
lmispacked=true
lmpkdtype=".zip"
else
[[ "$lmquiet" == false ]] && echo "Package is either not archived, or is of an unsupported type: $lmdlname."
fi
fi
if [[ -z "$lmsource" || "$lmsource" == "" ]]; then
echo "You must set a source to get the package $lmname."
exit 68;
fi
if [[ -z "$lmsrctype" || "$lmsrctype" == "" ]]; then
echo "Error! You must set a dependency source type to get the package $lmname."
exit 69; # Giggity.
fi
if [[ -z "$lmhome" || "$lmhome" == "" ]]; then
[[ "$lmquiet" == false ]] && echo "Using default home directory \"$lmhomedef\"."
lmhome="$lmhomedef"
fi
if [[ -z "$lmcache" || "$lmcache" == "" ]]; then
[[ "$lmquiet" == false ]] && echo "Using default cache directory \"$lmcachedef\"."
lmcache="$lmcachedef"
fi
if [[ -z "$lmdownloads" || "$lmdownloads" == "" ]]; then
[[ "$lmquiet" == false ]] && echo "Using default download directory \"$lmdownloadsdef\"."
lmdownloads="$lmdownloadsdef"
fi
if [[ -z "$lminstall" || "$lminstall" == "" ]]; then
[[ "$lmquiet" == false ]] && echo "Using default install directory \"$lmcachedef\"."
lminstall="$lminstalldef"
fi
if [[ "$lmispacked" == true ]]; then
[[ "$lmquiet" == false ]] && echo "Package is archived ($lmpkdtype); will unarchive if in unpack mode (-m u)".
fi
if [[ "$lmkeepdl" == true ]]; then
[[ "$lmquiet" == false ]] && echo "Mode = \"Keep download\". The download will not be deleted after caching the dependency."
fi
if [[ "$lmrenew" == true ]]; then
[[ "$lmquiet" == false ]] && echo "Mode = \"Renew\". Dependency will be downloaded again and replace cached copy."
fi
if [[ "$lmunpack" == true ]]; then
[[ "$lmquiet" == false ]] && echo "Mode = \"Unpack\". If the package is archived it will be unarchived before installation."
fi
if [[ "$lmquiet" == false ]]; then
local status
status="\\n---- Status ----\\n"
status+="Name\\t\\t=\\t$lmname\\n"
status+="DL Name\\t\\t=\\t$lmdlname\\n"
status+="Type\\t\\t=\\t$lmsrctype\\n"
status+="Source\\t\\t=\\t$lmsource\\n"
status+="Download Dir\\t=\\t$lmdownloads\\n"
status+="Install Dir\\t=\\t$lminstall\\n"
status+="Cache Dir\\t=\\t$lmcache\\n"
status+="Keep DL\\t\\t=\\t$lmkeepdl\\n"
status+="Quiet Mode\\t=\\t$lmquiet\\n"
status+="Renew Mode\\t=\\t$lmrenew\\n"
status+="Unpack Mode\\t=\\t$lmunpack\\n"
status+="Test Mode\\t=\\t$lmtest\\n"
echo "$status"
fi
fi
[[ -e "$lmhome" ]] || mkdir "$lmhome"
[[ -e "$lmcache" ]] || mkdir "$lmcache"
[[ -e "$lmdownloads" ]] || mkdir "$lmdownloads"
[[ -e "$lminstall" ]] || mkdir "$lminstall"
if [[ ! -e "$lmdownloads/$lmdlname" && ! -e "$lmcache/$lmname.tar.gz" ]]; then
if [[ "$lmsrctype" == "url" ]]; then
[[ "$lmquiet" == false ]] && echo "Package doesn't exist; downloading it."
curl -Lso "$lmdownloads/$lmdlname" "$lmsource"
elif [[ "$lmsrctype" == "git" ]]; then
[[ "$lmquiet" == false ]] && echo "Package doesn't exist; 'git clone'-ing it."
git clone "$lmsource" "$lmdownloads/$lmdlname" >> "$logfile" 2>&1
else
echo "Invalid source type: \"$lmsrctype\"."
fi
fi
if [[ "$lmispacked" == true && "$lmunpack" == true && -e "$lmdownloads/$lmdlname" ]]; then
[[ "$lmquiet" == false ]] && echo "Unpacking archive $lmdlname and installing as package $lmname."
if [[ "$lmpkdtype" == ".zip" ]]; then
unzip -oq "$lmdownloads/$lmdlname" -d "$lminstall"
elif [[ "$lmpkdtype" == ".tar.gz" ]]; then
tar -xzf "$lmdownloads/$lmdlname" -C "$lminstall"
else
echo "Invalid packed type: \"$lmpkdtype\"."
fi
fi
if [[ ! -e "$lmcache/$lmname.tar.gz" ]]; then
echo "Caching $lmname as $lmname.tar.gz..."
if [[ "$lmpkdtype" == *".tar.gz"* ]]; then
[[ "$lmquiet" == false ]] && echo "Downloaded package was a tarball; caching it..."
cp -X "$lmdownloads/$lmdlname" "$lmcache/$lmdlname"
[[ "$lmquiet" == false && "$lmkeepdl" == false ]] && { echo "Removing download $lmdlname..."; rm -f "$lmdownloads/$lmdlname"; }
else
cd "$lmcache" || { echo "Error! -- Couldn't cd to \"$lmcache\"!"; exit 70; }
[[ "$lmquiet" == false ]] && echo "Archiving installed package $lmname in cache..."
tar -czf "$lmname.tar.gz" -C "$lmdownloads" "$lmdlname"
[[ "$lmquiet" == false && "$lmkeepdl" == false ]] && { echo "Removing download $lmdlname..."; rm -f "$lmdownloads/$lmdlname"; }
cd - > /dev/null || { echo "Error! -- Couldn't cd to \"${-}\"!"; exit 71; }
fi
else
[[ "$lmquiet" == false ]] && echo "$lmname is already cached!"
fi
if [[ ! -e "$lminstall/$lmname" && ! -e "$lmdownloads/$lmdlname" && -e "$lmcache/$lmname.tar.gz" ]]; then
cd "$lmcache" || { echo "Error! -- Couldn't cd to \"$lmcache\"!"; exit 70; }
[[ "$lmquiet" == false ]] && echo "Installing $lmname from cached copy..."
tar -xzf "$lmname".tar.gz -C "$lminstall"
cd - > /dev/null || { echo "Error! -- Couldn't cd to \"${-}\"!"; exit 71; }
elif [[ -e "$lmdownloads/$lmdlname" ]]; then
[[ "$lmquiet" == false ]] && echo "Installing $lmname from downloaded package..."
mv -f "$lmdownloads/$lmdlname" "$lminstall/$lmname"
else
[[ "$lmquiet" == false ]] && echo "$lmname is already installed!"
fi
}
#-------------------------
# Test suite for lilman.
#
#
# Function cleanup()
# Removes the home folder and all files so test is on fresh files.
#
function cleanup() {
rm -rf ~/Desktop/lil-man/downloads
rm -rf ~/Desktop/lil-man/install
rm -rf ~/Desktop/lil-man/cache
return 0
}
function run_test() {
local testsrun=$7
[[ ! -e ~/Desktop/lil-man ]] && mkdir ~/Desktop/lil-man
if [[ "$testsrun" != 1 ]]; then
printf "%*s\\n\\n" "$termwidth" | tr ' ' '-' >> "$logfile"
fi
printf "Test # %s \\t" "${testsrun}"
echo -e "Test # ${testsrun}\\n\"$1 $2->$3 (Options: $4; $5)\"\\n" >> "$logfile"
[[ -z "$1" || -z "$2" || -z "$3" || -z "$4" || -z "$5" || -z "$6" || -z "$7" ]] && { echo -e "\\xE2\\x9D\\x8C (bad test args)."; echo -e "\\nResult: bad test args\\n" >> "$logfile"; ((hadfailure++)); return 64; }
( $6 ) >> "$logfile"
if [[ -e ~/Desktop/lil-man/install/"$1" && -e ~/Desktop/lil-man/cache/"$1.$3" ]]; then
if [[ "$4" == *"d"* && -e ~/Desktop/lil-man/downloads/"$1.$2" ||
"$4" != *"d"* && ! -e ~/Desktop/lil-man/downloads/"$1.$2" ||
"$4" == *"D"* && -e ~/Desktop/lil-man/downloads/"$1.$2" ||
"$4" != *"D"* && ! -e ~/Desktop/lil-man/downloads/"$1.$2"
]]; then
echo -e " = $?\\t\\xE2\\x9C\\x94"
echo -e "\\nResult: 0\\n" >> "$logfile"
return 0
elif [[ "$4" != *"d"* && ! -e ~/Desktop/lil-man/downloads/"$1.$2" ||
"$4" != *"D"* && ! -e ~/Desktop/lil-man/downloads/"$1.$2"
]]; then
echo -e " = $?\\t\\xE2\\x9D\\x8C"
echo -e "\\nResult: $?\\n" >> "$logfile"
((hadfailure++))
return 65
fi
fi
}
function finishtesting() {
local passed
passed=$(( ${1} - ${2} )) # testsrun - hadfailure = diff
echo "$passed/$1 passed."
}
function do_tests() {
#
# Script vars
#
set -e
local logfile
local testsrun
local termwidth
local hadfailure
local result
logfile=~/Desktop/lil-man/log.txt
testsrun=0
hadfailure=0
termwidth=$( tput cols )
#
# Create a new log file.
#
echo "Removing old log file (\"$logfile\")."
rm -f "$logfile"
#
# Self!, do cleanup
#
cleanup
((testsrun++))
run_test "lilman" "lilman" "tar.gz" "-m ud" "do cleanup" "./lilman -n lilman -t git -s https://github.com/l3laze/lilman.git -o lilman -m ud" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.tar.gz, no cleanup, keep download.
#
((testsrun++))
run_test "7za" "tar.gz" "tar.gz" "-m ud" "no cleanup" "./lilman -n 7za -t urL -s https://www.dropbox.com/s/qsdei4vp65ob1gn/7za.tar.gz?dl=0 -o 7za.tar.gz -m ud" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.tar.gz
#
cleanup
((testsrun++))
run_test "7za" "tar.gz" "tar.gz" "-m ud" "do cleanup" "./lilman -n 7za -t urL -s https://www.dropbox.com/s/qsdei4vp65ob1gn/7za.tar.gz?dl=0 -o 7za.tar.gz -m ud" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.tar.gz, do cleanup, don't keep download.
#
cleanup
((testsrun++))
run_test "7za" "tar.gz" "tar.gz" "-m u" "do cleanup, don't keep download" "./lilman -n 7za -t urL -s https://www.dropbox.com/s/qsdei4vp65ob1gn/7za.tar.gz?dl=0 -o 7za.tar.gz -m u" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.tar.gz, no cleanup, don't keep download.
#
((testsrun++))
run_test "7za" "tar.gz" "tar.gz" "-m u" "no cleanup, don't keep download" "./lilman -n 7za -t urL -s https://www.dropbox.com/s/qsdei4vp65ob1gn/7za.tar.gz?dl=0 -o 7za.tar.gz -m u" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.tar.gz, do cleanup, don't keep download.
#
cleanup
((testsrun++))
run_test "7za" "tar.gz" "tar.gz" "-m u" "do cleanup, don't keep download" "./lilman -n 7za -t urL -s https://www.dropbox.com/s/qsdei4vp65ob1gn/7za.tar.gz?dl=0 -o 7za.tar.gz -m u" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.zip, no cleanup beforehand
#
((testsrun++))
run_test "7za" "zip" "tar.gz" "-m UD" "no cleanup" "./lilman -n 7za -t url -s https://www.dropbox.com/s/5pfof15g4vccr6d/7za.zip?dl=0 -o 7za.zip -m ud" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
#
# 7za.zip, do cleanup
#
cleanup
((testsrun++))
run_test "7za" "zip" "tar.gz" "-m ud" "do cleanup" "./lilman -n 7za -t url -s https://www.dropbox.com/s/5pfof15g4vccr6d/7za.zip?dl=0 -o 7za.zip -m ud" "$testsrun"
result=$?
[[ "$result" != 0 ]] && ((hadfailure++))
finishtesting $testsrun $hadfailure
}
run_script "$@"
function test_globals() {
local areSet=0
local vars
local item
local value
vars=( "$lmhomedef" "$lmcachedef" "$lmhomedef" "$lminstalldef" "$lmhome" "$lmcache" )
vars+=( "$lmdownloads" "$lminstall" "$lmunpack" "$lmkeepdl" "$lmrenew" "$lmquiet" )
vars+=( "$logfile" "$termwidth" "$line" "$len" "$result" "$testsrun" "$hadfailure" )
vars+=( "$files" "$options" "$cmd" "$status" )
for(( item = 0; item < ${#vars[@]}; item++ )); do
value="${vars[ item ]}"
if [[ ! -z "$value" ]]; then
((areSet++))
fi
done
echo "Globals set: $areSet/${#vars[@]}"
}
test_globals
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment