#!/usr/bin/env bash ############################################################################# # # # GLWTS(Good Luck With That Shit) Public License # # Copyright (c) Every-fucking-one, except the Author # # # # Everyone is permitted to copy, distribute, modify, merge, sell, publish, # # sublicense or whatever the fuck they want with this software but at their # # OWN RISK. # # # # Preamble # # # # The author has absolutely no fucking clue what the code in this project # # does. It might just fucking work or not, there is no third option. # # # # # # GOOD LUCK WITH THAT SHIT PUBLIC LICENSE # # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION # # # # 0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE # # A FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for # # or hold responsible. # # # # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # # DEALINGS IN THE SOFTWARE. # # # # Good luck and Godspeed. # # # ############################################################################# # Default values DEFAULT_CONFIGS_DIR="configs" DEFAULT_MANIFEST_FILE="MANIFEST" # Text styles BOLD=$(tput bold) NORMAL=$(tput sgr0) # Text colors GREEN='\033[1;32m' BLUE='\033[1;36m' YELLOW='\033[1;33m' RED='\033[0;31m' NO_COLOR='\033[0' # Note: First, specify the color and only then the text style logo() { echo -e "${GREEN}${BOLD}" cat << 'EOF' ░█▀▀░█▀█░█▀█░█▀▀░█▀▀░█▀█░█▀▄░█▀▀ ░█░░░█░█░█░█░█▀▀░█░░░█░█░█▀▄░█▀▀ ░▀▀▀░▀▀▀░▀░▀░▀░░░▀▀▀░▀▀▀░▀░▀░▀▀▀ EOF echo -e "${NO_COLOR}${NORMAL}" } about() { echo -e " ${YELLOW}${BOLD}What's that?${NORMAL} This script is designed to manage configuration files. It has two modes of operation: ${BOLD}interactive${NORMAL} and ${BOLD}command line${NORMAL}. To run the script in interactive mode run the script without arguments: ${BOLD}./confcore.sh${NORMAL} To run the script in command line mode, use arguments according to the following scheme: ${BOLD}./confcore.sh${NORMAL} ${BLUE}${BOLD}${NORMAL} ${GREEN}${BOLD}${NORMAL} ${BLUE}${BOLD}Commands:${NORMAL} ${BOLD} * conf-deploy${NORMAL} - deploy configs(create symlinks) from manifest file ${BOLD} * conf-delete${NORMAL} - delete symlinks ${BOLD} * about${NORMAL} - about this script ${GREEN}${BOLD}Args:${NORMAL} ${BOLD} * conf-deploy${NORMAL} - none ${BOLD} * conf-delete${NORMAL} - none ${BOLD} * about${NORMAL} - none ${YELLOW}${BOLD}Global variables:${NORMAL} ${BOLD}* CONFIGS_DIR${NORMAL} - sets the directory where the configs are stored. Default: \"configs\" ${YELLOW}${BOLD}Manifest file:${NORMAL} The manifest file (MANIFEST by default), located in the ${BOLD}CONFIGS_DIR${NORMAL} directory, describes the rules for creating symbolic links for configuration/dotfiles. ${BOLD}Syntax and example:${NORMAL} +-----------------------------------------------------+ | # comment bla-bla-bla | | \$HOME <- .zshrc .tmux.conf fuck-this-shit/.emacs.d | | \$HOME/.config <- nvim | +-----------------------------------------------------+ As we can see, our manifest file supports comments that start with the ${BOLD}\"#\"${NORMAL} character. Next we see the expression for symlink creation, which starts with the directory where the symlinks should be created, the special character combination ${BOLD}\"<-\"${NORMAL} to create a symbolic link, and a list of configuration files for which the links should be created, separated by spaces. In the left part of the expression it is allowed to use variables, for example \$HOME. In the right part of the expression all paths are calculated relative to ${BOLD}CONFIGS_DIR${NORMAL}. Strings not containing ${BOLD}\"<-\"${NORMAL} will be ignored. ${YELLOW}${BOLD}General scheme:${NORMAL} ${BOLD} . |--confcore.sh \`--CONFIG_DIR(default: configs) |--MANIFEST |--dotfile-1 |--dotfile-2 |--dotfile-n ${NORMAL} ${YELLOW}${BOLD}License:${NORMAL} GLWTS(Good Luck With That Shit) Public License Copyright (c) Every-fucking-one, except the Author Everyone is permitted to copy, distribute, modify, merge, sell, publish, sublicense or whatever the fuck they want with this software but at their OWN RISK. Preamble The author has absolutely no fucking clue what the code in this project does. It might just fucking work or not, there is no third option. GOOD LUCK WITH THAT SHIT PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION 0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE A FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for or hold responsible. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Good luck and Godspeed. " } error() { echo -e "${RED}${BOLD}[ERROR]: $1${NORMAL}" echo -e "${BLUE}${BOLD}Backtrace: ${NORMAL}" echo -e "${BLUE}${BOLD}* END${NORMAL}" echo -e "${BLUE}${BOLD}^${NORMAL}" for (( i=0; i<${#FUNCNAME[@]}; i++ )); do echo -e "${BLUE}${BOLD}|${NORMAL}" echo -e "${BLUE}${BOLD}* file:${BASH_SOURCE[1]}\tfunc_call_line:${BASH_LINENO[$i]}\tfunc_name:${FUNCNAME[$i]}" done echo -e "${BLUE}${BOLD}|${NORMAL}" echo -e "${BLUE}${BOLD}* START${NORMAL}" exit 1 } dotconf() { # Internal support functions(starts with underscore: _func_name) _create_symlink() { local targer_dir="$1" local conf_dir="$2" local conf_name="$3" if [ ! -e "$CONFIGS_DIR/$conf_dir/$conf_name" ]; then error "The config <$CONFIGS_DIR/$conf_dir/$conf_name> does not exist." fi if [ -e "$target_dir/$conf_name" ]; then echo -e "${YELLOW}${BOLD}[WARNING]: $conf_dir/$conf_name already exist. Maybe it's a symlink, file or directory.${NORMAL}" local choice="" read -p "${BOLD}Delete and symlink it?(yes/no)${NORMAL}: " choice case "$choice" in "y"|"Y"|"yes"|"Yes"|"YES") rm -rf "$target_dir/$conf_name" ;; *) return ;; esac fi local output=$( { ln -s "$CONFIGS_DIR/$conf_dir/$conf_name" "$target_dir/$conf_name" 2>&1; echo $?; } ) local exit_code=${output##*$'\n'} output=${output%$'\n'*} if [ $exit_code -ne 0 ]; then error "$output" fi echo -e "${GREEN}${BOLD}[OK]: $conf_dir/$conf_name --> $target_dir/$conf_name${NORMAL}" } _delete_symlink() { local target_dir="$1" local conf_dir="$2" local conf_name="$3" local output=$( { rm "$target_dir/$conf_name" 2>&1; echo $?; } ) local exit_code=${output##*$'\n'} output=${output%$'\n'*} if [ $exit_code -ne 0 ]; then echo -e "${YELLOW}${BOLD}[WARNING]: $output${NORMAL}" return fi echo -e "${GREEN}${BOLD}[OK]: $conf_dir/$conf_name -!> $target_dir/$conf_name${NORMAL}" } _get_all_configs() { local confs="" for key in "${!dirs_and_confs[@]}"; do for conf in ${dirs_and_confs["$key"]}; do confs+="$(basename "$conf") " done done for element in $(ls -A "$1"); do if ! ([[ "$confs" =~ [[:space:]]"$element"[[:space:]] ]] || [[ "$confs" =~ ^"$element"[[:space:]] ]] || [[ "$confs" =~ [[:space:]]"$element"$ ]]); then if [ -d "$1/$element" ]; then all_configs+=($element) _get_all_configs "$1/$element" else all_configs+=($element) fi fi done } local operation=$1 case $operation in "conf-deploy") operation="_create_symlink" ;; "conf-delete") operation="_delete_symlink" ;; *) error "The operation <$operation> does not exist." ;; esac local manifest_file="$CONFIGS_DIR/$DEFAULT_MANIFEST_FILE" if [ ! -f "$manifest_file" ]; then error "The manifest file <$manifest_file> does not exist." fi declare -A local dirs_and_confs IFS=$'\n' for row in $(<$manifest_file); do if [[ "$row" =~ ^#.* || ! "$row" == *"<-"* ]]; then continue fi local target_dir=$(eval echo $(echo "$row" | awk -F'<-' '{print $1}' | sed 's/^ *//; s/ *$//; s/"//g;') | sed 's/ /\\ /g') local confs=$(echo $row | awk -F'<-' '{print $2}' | sed 's/"//g;') dirs_and_confs["$target_dir"]="$confs" done unset IFS for target_dir in "${!dirs_and_confs[@]}"; do for confs in ${dirs_and_confs["$target_dir"]}; do local conf_dir=$(dirname "$confs") local conf_name=$(basename "$confs") $operation "$target_dir" "$conf_dir" "$conf_name" done done local all_configs=() _get_all_configs "$CONFIGS_DIR" for dir in ${!dirs_and_confs[@]}; do for file in $(ls -A "$dir"); do if [ -L "$dir/$file" ]; then for key in ${!dirs_and_confs[@]}; do local founded=false for elem in ${dirs_and_confs["$key"]}; do if [[ "$file" == "$(basename $elem)" ]]; then founded=true fi done if [ "$founded" == true ]; then if [[ ! "$dir" == "$key" ]]; then _delete_symlink "$dir" "" "$file" fi fi done for conf in "${all_configs[@]}"; do if [[ "$file" == "$conf" ]]; then _delete_symlink "$dir" "" "$file" fi done fi done done } confcore() { logo declare -A local operations_and_desc operations_and_desc["conf-deploy"]="deploy configs(create symlinks) from manifest file" operations_and_desc["conf-delete"]="delete symlinks" operations_and_desc["about"]="about this script" declare -A local operations_and_func operations_and_func["conf-deploy"]="dotconf conf-deploy" operations_and_func["conf-delete"]="dotconf conf-delete" operations_and_func["about"]="about" local operation="" local args="" if [ -z "$CONFIGS_DIR" ]; then CONFIGS_DIR="$(realpath .)/$DEFAULT_CONFIGS_DIR" fi if [ ! -d "$CONFIGS_DIR" ]; then about error "The directory with configs <$CONFIGS_DIR> does not exist." fi case $# in "0") echo -e "${YELLOW}${BOLD}Select the operation: ${NORMAL}" for key in "${!operations_and_desc[@]}"; do echo -e "${BOLD}* $key${NORMAL} - ${operations_and_desc[$key]}" done read -p "${BOLD}Operation${NORMAL}: " operation ;; *) operation="$1" shift args="$@" ;; esac if [[ ! -v operations_and_func[$operation] ]]; then error "The operaton <$operation> does not exist." fi ${operations_and_func[$operation]} "$args" } confcore "$@"